diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index ce0f45967152..16e8891e5f57 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Iterable, List, cast -from typing_extensions import Final +from typing_extensions import Final, Literal import mypy.plugin # To avoid circular imports. from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type @@ -756,13 +756,14 @@ def _add_init( ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute], adder: MethodAdder, - method_name: str, + method_name: Literal["__init__", "__attrs_init__"], ) -> None: """Generate an __init__ method for the attributes and add it to the class.""" - # Convert attributes to arguments with kw_only arguments at the end of + # Convert attributes to arguments with kw_only arguments at the end of # the argument list pos_args = [] kw_only_args = [] + sym_table = ctx.cls.info.names for attribute in attributes: if not attribute.init: continue @@ -770,6 +771,13 @@ def _add_init( kw_only_args.append(attribute.argument(ctx)) else: pos_args.append(attribute.argument(ctx)) + + # If the attribute is Final, present in `__init__` and has + # no default, make sure it doesn't error later. + if not attribute.has_default and attribute.name in sym_table: + sym_node = sym_table[attribute.name].node + if isinstance(sym_node, Var) and sym_node.is_final: + sym_node.final_set_in_init = True args = pos_args + kw_only_args if all( # We use getattr rather than instance checks because the variable.type diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index 4d27d5f39d1e..f555f2ea7011 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1834,3 +1834,36 @@ class Sub(Base): # This matches runtime semantics reveal_type(Sub) # N: Revealed type is "def (*, name: builtins.str, first_name: builtins.str, last_name: builtins.str) -> __main__.Sub" [builtins fixtures/property.pyi] + +[case testFinalInstanceAttribute] +from attrs import define +from typing import Final + +@define +class C: + a: Final[int] + +reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C" + +C(1).a = 2 # E: Cannot assign to final attribute "a" + +[builtins fixtures/property.pyi] + +[case testFinalInstanceAttributeInheritance] +from attrs import define +from typing import Final + +@define +class C: + a: Final[int] + +@define +class D(C): + b: Final[str] + +reveal_type(D) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> __main__.D" + +D(1, "").a = 2 # E: Cannot assign to final attribute "a" +D(1, "").b = "2" # E: Cannot assign to final attribute "b" + +[builtins fixtures/property.pyi] \ No newline at end of file