diff --git a/crates/pyrefly_types/src/callable.rs b/crates/pyrefly_types/src/callable.rs index 5cb59b6abf..dd6b036821 100644 --- a/crates/pyrefly_types/src/callable.rs +++ b/crates/pyrefly_types/src/callable.rs @@ -864,6 +864,7 @@ pub enum FunctionKind { Dataclass, DataclassField, DataclassReplace, + CopyReplace, DataclassAsdict, /// `attr.fields(C)` / `attrs.fields(C)`. AttrsFields, @@ -1269,6 +1270,7 @@ impl FunctionKind { ("dataclasses", None, "dataclass") => Self::Dataclass, ("dataclasses", None, "field") => Self::DataclassField, ("dataclasses", None, "replace") => Self::DataclassReplace, + ("copy", None, "replace") => Self::CopyReplace, ("dataclasses", None, "asdict") => Self::DataclassAsdict, ("attr" | "attrs", None, "fields") => Self::AttrsFields, ("attr" | "attrs", None, "fields_dict") => Self::AttrsFieldsDict, @@ -1314,6 +1316,7 @@ impl FunctionKind { Self::Dataclass => ModuleName::dataclasses(), Self::DataclassField => ModuleName::dataclasses(), Self::DataclassReplace => ModuleName::dataclasses(), + Self::CopyReplace => ModuleName::from_str("copy"), Self::DataclassAsdict => ModuleName::dataclasses(), Self::AttrsFields => ModuleName::attr(), Self::AttrsFieldsDict => ModuleName::attr(), @@ -1352,6 +1355,7 @@ impl FunctionKind { Self::Dataclass => Cow::Owned(Name::new_static("dataclass")), Self::DataclassField => Cow::Owned(Name::new_static("field")), Self::DataclassReplace => Cow::Owned(Name::new_static("replace")), + Self::CopyReplace => Cow::Owned(Name::new_static("replace")), Self::DataclassAsdict => Cow::Owned(Name::new_static("asdict")), Self::AttrsFields => Cow::Owned(Name::new_static("fields")), Self::AttrsFieldsDict => Cow::Owned(Name::new_static("fields_dict")), @@ -1390,6 +1394,7 @@ impl FunctionKind { Self::Dataclass => None, Self::DataclassField => None, Self::DataclassReplace => None, + Self::CopyReplace => None, Self::DataclassAsdict => None, Self::AttrsFields => None, Self::AttrsFieldsDict => None, diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index f4ebf9a419..8c8e317c28 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -1994,11 +1994,14 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { // against attribute names, including `init=False` fields. Both require an attrs class. Some(CalleeKind::Function( kind @ (FunctionKind::DataclassReplace + | FunctionKind::CopyReplace | FunctionKind::AttrsEvolve | FunctionKind::AttrsAssoc), )) => { let replace_kind = match kind { - FunctionKind::DataclassReplace => ReplaceKind::Replace, + FunctionKind::DataclassReplace | FunctionKind::CopyReplace => { + ReplaceKind::Replace + } FunctionKind::AttrsEvolve => ReplaceKind::Evolve, FunctionKind::AttrsAssoc => ReplaceKind::Assoc, _ => unreachable!("guarded by the enclosing match arm"), diff --git a/pyrefly/lib/test/dataclasses.rs b/pyrefly/lib/test/dataclasses.rs index 5af3098a5d..821f741cd1 100644 --- a/pyrefly/lib/test/dataclasses.rs +++ b/pyrefly/lib/test/dataclasses.rs @@ -138,6 +138,29 @@ replace(f, z=3) # E: Unexpected keyword argument `z` "#, ); +testcase!( + test_copy_replace, + TestEnv::new_with_version(PythonVersion::new(3, 13, 0)), + r#" +import copy +from copy import replace +from dataclasses import dataclass +from typing import assert_type + +@dataclass +class Foo: + x: int + y: str + +f = Foo(1, "a") + +assert_type(copy.replace(f, x=2), Foo) +replace(f, y="b") +copy.replace(f, x="wrong") # E: Argument `Literal['wrong']` is not assignable to parameter `x` with type `int` in function `Foo.__replace__` +replace(f, z=3) # E: Unexpected keyword argument `z` + "#, +); + testcase!( test_replace_initvar_default, r#"