diff --git a/mypy/semanal.py b/mypy/semanal.py index abb58245ae3f..6f3e0c85ae86 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3647,7 +3647,7 @@ def analyze_lvalue( has_explicit_value=has_explicit_value, ) elif isinstance(lval, MemberExpr): - self.analyze_member_lvalue(lval, explicit_type, is_final) + self.analyze_member_lvalue(lval, explicit_type, is_final, has_explicit_value) if explicit_type and not self.is_self_member_ref(lval): self.fail("Type cannot be declared in assignment to non-self attribute", lval) elif isinstance(lval, IndexExpr): @@ -3824,7 +3824,9 @@ def analyze_tuple_or_list_lvalue(self, lval: TupleExpr, explicit_type: bool = Fa has_explicit_value=True, ) - def analyze_member_lvalue(self, lval: MemberExpr, explicit_type: bool, is_final: bool) -> None: + def analyze_member_lvalue( + self, lval: MemberExpr, explicit_type: bool, is_final: bool, has_explicit_value: bool + ) -> None: """Analyze lvalue that is a member expression. Arguments: @@ -3853,12 +3855,18 @@ def analyze_member_lvalue(self, lval: MemberExpr, explicit_type: bool, is_final: and explicit_type ): self.attribute_already_defined(lval.name, lval, cur_node) - # If the attribute of self is not defined in superclasses, create a new Var, ... + if self.type.is_protocol and has_explicit_value and cur_node is not None: + # Make this variable non-abstract, it would be safer to do this only if we + # are inside __init__, but we do this always to preserve historical behaviour. + if isinstance(cur_node.node, Var): + cur_node.node.is_abstract_var = False if ( + # If the attribute of self is not defined, create a new Var, ... node is None - or (isinstance(node.node, Var) and node.node.is_abstract_var) + # ... or if it is defined as abstract in a *superclass*. + or (cur_node is None and isinstance(node.node, Var) and node.node.is_abstract_var) # ... also an explicit declaration on self also creates a new Var. - # Note that `explicit_type` might has been erased for bare `Final`, + # Note that `explicit_type` might have been erased for bare `Final`, # so we also check if `is_final` is passed. or (cur_node is None and (explicit_type or is_final)) ): diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index da0b7feb4831..9a68651ed5f6 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2037,3 +2037,16 @@ Foo( present_5=5, ) [builtins fixtures/dataclasses.pyi] + +[case testProtocolNoCrash] +from typing import Protocol, Union, ClassVar +from dataclasses import dataclass, field + +DEFAULT = 0 + +@dataclass +class Test(Protocol): + x: int + def reset(self) -> None: + self.x = DEFAULT +[builtins fixtures/dataclasses.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 4c2560641d97..6976b8ee0a39 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4054,3 +4054,14 @@ class P(Protocol): [file lib.py] class C: ... + +[case testAllowDefaultConstructorInProtocols] +from typing import Protocol + +class P(Protocol): + x: int + def __init__(self, x: int) -> None: + self.x = x + +class C(P): ... +C(0) # OK