diff --git a/mypy/nodes.py b/mypy/nodes.py index 4a5c7240fa83..23b0c3c25fc2 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3232,12 +3232,18 @@ def get_method(self, name: str) -> FuncBase | Decorator | None: for cls in self.mro: if name in cls.names: node = cls.names[name].node - if isinstance(node, FuncBase): - return node - elif isinstance(node, Decorator): # Two `if`s make `mypyc` happy - return node - else: - return None + elif possible_redefinitions := sorted( + [n for n in cls.names.keys() if n.startswith(f"{name}-redefinition")] + ): + node = cls.names[possible_redefinitions[-1]].node + else: + continue + if isinstance(node, FuncBase): + return node + elif isinstance(node, Decorator): # Two `if`s make `mypyc` happy + return node + else: + return None return None def calculate_metaclass_type(self) -> mypy.types.Instance | None: diff --git a/mypy/semanal.py b/mypy/semanal.py index f36149076fe6..7efd0d3e0153 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -6561,7 +6561,12 @@ def add_symbol_table_node( if not is_same_symbol(old, new): if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)): self.add_redefinition(names, name, symbol) - if not (isinstance(new, (FuncDef, Decorator)) and self.set_original_def(old, new)): + if isinstance(old, Var) and is_init_only(old): + if old.has_explicit_value: + self.fail("InitVar with default value cannot be redefined", context) + elif not ( + isinstance(new, (FuncDef, Decorator)) and self.set_original_def(old, new) + ): self.name_already_defined(name, context, existing) elif name not in self.missing_names[-1] and "*" not in self.missing_names[-1]: names[name] = symbol @@ -7575,3 +7580,10 @@ def halt(self, reason: str = ...) -> NoReturn: return isinstance(stmt, PassStmt) or ( isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr) ) + + +def is_init_only(node: Var) -> bool: + return ( + isinstance(type := get_proper_type(node.type), Instance) + and type.type.fullname == "dataclasses.InitVar" + ) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 0f726242b25b..04e86ddb1431 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2476,6 +2476,61 @@ class Child(Base): y: int [builtins fixtures/dataclasses.pyi] +[case testDataclassesInitVarsWithProperty] +from dataclasses import InitVar, dataclass, field + +@dataclass +class Test: + foo: InitVar[str] + _foo: str = field(init=False) + + def __post_init__(self, foo: str) -> None: + self._foo = foo + + @property + def foo(self) -> str: + return self._foo + + @foo.setter + def foo(self, value: str) -> None: + self._foo = value + +reveal_type(Test) # N: Revealed type is "def (foo: builtins.str) -> __main__.Test" +test = Test(42) # E: Argument 1 to "Test" has incompatible type "int"; expected "str" +test = Test("foo") +test.foo +[builtins fixtures/dataclasses.pyi] + +[case testDataclassesDefaultValueInitVarWithProperty] +from dataclasses import InitVar, dataclass, field + +@dataclass +class Test: + foo: InitVar[str] = "foo" + _foo: str = field(init=False) + + def __post_init__(self, foo: str) -> None: + self._foo = foo + + @property # E: InitVar with default value cannot be redefined + def foo(self) -> str: + return self._foo + +[builtins fixtures/dataclasses.pyi] + +[case testDataclassesWithProperty] +from dataclasses import dataclass + +@dataclass +class Test: + @property + def foo(self) -> str: + return "a" + + @foo.setter + def foo(self, value: str) -> None: + pass +[builtins fixtures/dataclasses.pyi] [case testDataclassInheritanceWorksWithExplicitOverridesAndOrdering] # flags: --enable-error-code explicit-override