From e30f9b1b730dd10675e3aacbca7e226298357cea Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 26 Aug 2022 21:01:49 +0100 Subject: [PATCH] Fix crash on bare Final in dataclass --- mypy/plugin.py | 4 ++++ mypy/plugins/dataclasses.py | 19 ++++++++++++++++++- test-data/unit/check-dataclasses.test | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/mypy/plugin.py b/mypy/plugin.py index dc31130df991..00a2af82969f 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -407,6 +407,10 @@ def final_iteration(self) -> bool: def is_stub_file(self) -> bool: raise NotImplementedError + @abstractmethod + def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Type | None: + raise NotImplementedError + # A context for querying for configuration data about a module for # cache invalidation purposes. diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 095967dc3fa1..5ab283469913 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -368,7 +368,7 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: if isinstance(node, TypeAlias): ctx.api.fail( - ("Type aliases inside dataclass definitions " "are not supported at runtime"), + ("Type aliases inside dataclass definitions are not supported at runtime"), node, ) # Skip processing this node. This doesn't match the runtime behaviour, @@ -426,6 +426,23 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: is_kw_only = bool(ctx.api.parse_bool(field_kw_only_param)) known_attrs.add(lhs.name) + + if sym.type is None and node.is_final and node.is_inferred: + # This is a special case, assignment like x: Final = 42 is classified + # annotated above, but mypy strips the `Final` turning it into x = 42. + # We do not support inferred types in dataclasses, so we can try inferring + # type for simple literals, and otherwise require an explicit type + # argument for Final[...]. + typ = ctx.api.analyze_simple_literal_type(stmt.rvalue, is_final=True) + if typ: + node.type = typ + else: + ctx.api.fail( + "Need type argument for Final[...] with non-literal default in dataclass", + stmt, + ) + node.type = AnyType(TypeOfAny.from_error) + attrs.append( DataclassAttribute( name=lhs.name, diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index d49a3a01e82d..37aeea934278 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1796,3 +1796,21 @@ t: Two reveal_type(t.__match_args__) # E: "Two" has no attribute "__match_args__" \ # N: Revealed type is "Any" [builtins fixtures/dataclasses.pyi] + +[case testFinalInDataclass] +from dataclasses import dataclass +from typing import Final + +@dataclass +class FirstClass: + FIRST_CONST: Final = 3 # OK + +@dataclass +class SecondClass: + SECOND_CONST: Final = FirstClass.FIRST_CONST # E: Need type argument for Final[...] with non-literal default in dataclass + +reveal_type(FirstClass().FIRST_CONST) # N: Revealed type is "Literal[3]?" +FirstClass().FIRST_CONST = 42 # E: Cannot assign to final attribute "FIRST_CONST" +reveal_type(SecondClass().SECOND_CONST) # N: Revealed type is "Literal[3]?" +SecondClass().SECOND_CONST = 42 # E: Cannot assign to final attribute "SECOND_CONST" +[builtins fixtures/dataclasses.pyi]