Skip to content

Commit e36a5f6

Browse files
committed
Fix error on instance property and init-only variable with the same name in a dataclass
1 parent fb31409 commit e36a5f6

File tree

3 files changed

+63
-7
lines changed

3 files changed

+63
-7
lines changed

mypy/nodes.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3165,12 +3165,18 @@ def get_method(self, name: str) -> FuncBase | Decorator | None:
31653165
for cls in self.mro:
31663166
if name in cls.names:
31673167
node = cls.names[name].node
3168-
if isinstance(node, FuncBase):
3169-
return node
3170-
elif isinstance(node, Decorator): # Two `if`s make `mypyc` happy
3171-
return node
3172-
else:
3173-
return None
3168+
elif possible_redefinitions := sorted(
3169+
[n for n in cls.names.keys() if n.startswith(f"{name}-redefinition")]
3170+
):
3171+
node = cls.names[possible_redefinitions[-1]].node
3172+
else:
3173+
continue
3174+
if isinstance(node, FuncBase):
3175+
return node
3176+
elif isinstance(node, Decorator): # Two `if`s make `mypyc` happy
3177+
return node
3178+
else:
3179+
return None
31743180
return None
31753181

31763182
def calculate_metaclass_type(self) -> mypy.types.Instance | None:

mypy/semanal.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6211,7 +6211,10 @@ def add_symbol_table_node(
62116211
if not is_same_symbol(old, new):
62126212
if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)):
62136213
self.add_redefinition(names, name, symbol)
6214-
if not (isinstance(new, (FuncDef, Decorator)) and self.set_original_def(old, new)):
6214+
if not (
6215+
is_init_only(old)
6216+
or (isinstance(new, (FuncDef, Decorator)) and self.set_original_def(old, new))
6217+
):
62156218
self.name_already_defined(name, context, existing)
62166219
elif name not in self.missing_names[-1] and "*" not in self.missing_names[-1]:
62176220
names[name] = symbol
@@ -7195,3 +7198,11 @@ def halt(self, reason: str = ...) -> NoReturn:
71957198
return isinstance(stmt, PassStmt) or (
71967199
isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr)
71977200
)
7201+
7202+
7203+
def is_init_only(node: SymbolNode | None) -> bool:
7204+
return (
7205+
isinstance(node, Var)
7206+
and isinstance(type := get_proper_type(node.type), Instance)
7207+
and type.type.fullname == "dataclasses.InitVar"
7208+
)

test-data/unit/check-dataclasses.test

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,3 +2475,42 @@ class Base:
24752475
class Child(Base):
24762476
y: int
24772477
[builtins fixtures/dataclasses.pyi]
2478+
2479+
[case testDataclassesInitVarsWithProperty]
2480+
from dataclasses import InitVar, dataclass, field
2481+
2482+
@dataclass
2483+
class Test:
2484+
foo: InitVar[str]
2485+
_foo: str = field(init=False)
2486+
2487+
def __post_init__(self, foo: str) -> None:
2488+
self._foo = foo
2489+
2490+
@property
2491+
def foo(self) -> str:
2492+
return self._foo
2493+
2494+
@foo.setter
2495+
def foo(self, value: str) -> None:
2496+
self._foo = value
2497+
2498+
reveal_type(Test) # N: Revealed type is "def (foo: builtins.str) -> __main__.Test"
2499+
test = Test(42) # E: Argument 1 to "Test" has incompatible type "int"; expected "str"
2500+
test = Test("foo")
2501+
test.foo
2502+
[builtins fixtures/dataclasses.pyi]
2503+
2504+
[case testDataclassesWithProperty]
2505+
from dataclasses import dataclass
2506+
2507+
@dataclass
2508+
class Test:
2509+
@property
2510+
def foo(self) -> str:
2511+
return "a"
2512+
2513+
@foo.setter
2514+
def foo(self, value: str) -> None:
2515+
pass
2516+
[builtins fixtures/dataclasses.pyi]

0 commit comments

Comments
 (0)