diff --git a/mypy/nodes.py b/mypy/nodes.py index d69ff10346c3..169b532a914d 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3368,12 +3368,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, SYMBOL_FUNCBASE_TYPES): - 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, SYMBOL_FUNCBASE_TYPES): + 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 87aef2595caf..4d6a9b4f9481 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -6753,7 +6753,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 @@ -7813,3 +7818,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 2ead202bd6af..c34711f42c07 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2494,6 +2494,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