From 324db7f2f7eb0e55e78ae05e76d1bcec0a5322a7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 30 Jan 2025 16:03:00 +0300 Subject: [PATCH 1/2] Fix "not callable" issue for `@dataclass(frozen=True)` with `Final` attr --- mypy/checkmember.py | 6 ++++-- test-data/unit/check-dataclasses.test | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index f6b5e6be2c538..04380d1daadd2 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -755,8 +755,10 @@ def is_instance_var(var: Var) -> bool: var.name in var.info.names and var.info.names[var.name].node is var and not var.is_classvar - # variables without annotations are treated as classvar - and not var.is_inferred + # variables without annotations are treated as classvar, + # but not when they are annotated as `a: Final = 1`, + # since it will be inferenced as `Literal[1]` + and not (var.is_inferred and not var.is_final) ) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 2e7259e4de0a0..8c891a2f86ad5 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2553,3 +2553,18 @@ class X(metaclass=DCMeta): class Y(X): a: int # E: Covariant override of a mutable attribute (base class "X" defined the type as "Optional[int]", expression has type "int") [builtins fixtures/tuple.pyi] + + +[case testFrozenWithFinal] +from dataclasses import dataclass +from typing import Final + +@dataclass(frozen=True) +class My: + a: Final = 1 + b: Final[int] = 2 + +m = My() +reveal_type(m.a) # N: Revealed type is "Literal[1]?" +reveal_type(m.b) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] From d26fd3441586f22d02223e90c6a0b59504dbc195 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 5 Feb 2025 14:37:46 +0300 Subject: [PATCH 2/2] Address review --- mypy/checkmember.py | 6 ++---- mypy/plugins/dataclasses.py | 2 ++ test-data/unit/check-dataclasses.test | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 04380d1daadd2..f6b5e6be2c538 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -755,10 +755,8 @@ def is_instance_var(var: Var) -> bool: var.name in var.info.names and var.info.names[var.name].node is var and not var.is_classvar - # variables without annotations are treated as classvar, - # but not when they are annotated as `a: Final = 1`, - # since it will be inferenced as `Literal[1]` - and not (var.is_inferred and not var.is_final) + # variables without annotations are treated as classvar + and not var.is_inferred ) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 6e0e222723567..acb785aad70a8 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -768,6 +768,8 @@ def _freeze(self, attributes: list[DataclassAttribute]) -> None: if sym_node is not None: var = sym_node.node if isinstance(var, Var): + if var.is_final: + continue # do not turn `Final` attrs to `@property` var.is_property = True else: var = attr.to_var(info) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 8c891a2f86ad5..26c81812ab628 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2564,7 +2564,15 @@ class My: a: Final = 1 b: Final[int] = 2 +reveal_type(My.a) # N: Revealed type is "Literal[1]?" +reveal_type(My.b) # N: Revealed type is "builtins.int" +My.a = 1 # E: Cannot assign to final attribute "a" +My.b = 2 # E: Cannot assign to final attribute "b" + m = My() reveal_type(m.a) # N: Revealed type is "Literal[1]?" reveal_type(m.b) # N: Revealed type is "builtins.int" + +m.a = 1 # E: Cannot assign to final attribute "a" +m.b = 2 # E: Cannot assign to final attribute "b" [builtins fixtures/tuple.pyi]