diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ef38cc3a0dcf..fc9d0dd86074 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -919,6 +919,18 @@ def analyze_var( result = AnyType(TypeOfAny.special_form) fullname = f"{var.info.fullname}.{name}" hook = mx.chk.plugin.get_attribute_hook(fullname) + + if var.info.is_enum and not mx.is_lvalue: + if name in var.info.enum_members and name not in {"name", "value"}: + enum_literal = LiteralType(name, fallback=itype) + result = itype.copy_modified(last_known_value=enum_literal) + elif ( + isinstance(p_result := get_proper_type(result), Instance) + and p_result.type.fullname == "enum.nonmember" + and p_result.args + ): + # Unwrap nonmember similar to class-level access + result = p_result.args[0] if result and not (implicit or var.info.is_protocol and is_instance_var(var)): result = analyze_descriptor_access(result, mx) if hook: diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index d21b21fb39f8..0be2e083b6dd 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -144,7 +144,7 @@ def _implements_new(info: TypeInfo) -> bool: def enum_member_callback(ctx: mypy.plugin.FunctionContext) -> Type: """By default `member(1)` will be inferred as `member[int]`, we want to improve the inference to be `Literal[1]` here.""" - if ctx.arg_types or ctx.arg_types[0]: + if ctx.arg_types and ctx.arg_types[0]: arg = get_proper_type(ctx.arg_types[0][0]) proper_return = get_proper_type(ctx.default_return_type) if ( diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index d034fe1a6f5f..3bcf9745a801 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1953,7 +1953,8 @@ class A(Enum): x: int def method(self) -> int: pass class B(A): - x = 1 # E: Cannot override writable attribute "x" with a final one + x = 1 # E: Cannot override writable attribute "x" with a final one \ + # E: Incompatible types in assignment (expression has type "B", base class "A" defined the type as "int") class A1(Enum): x: int = 1 # E: Enum members must be left unannotated \ @@ -1971,8 +1972,8 @@ class B2(A2): # E: Cannot extend enum with existing members: "A2" class A3(Enum): x: Final[int] # type: ignore class B3(A3): - x = 1 # E: Cannot override final attribute "x" (previously declared in base class "A3") - + x = 1 # E: Cannot override final attribute "x" (previously declared in base class "A3") \ + # E: Incompatible types in assignment (expression has type "B3", base class "A3" defined the type as "int") [builtins fixtures/bool.pyi] [case testEnumNotFinalWithMethodsAndUninitializedValuesStub] @@ -1984,14 +1985,16 @@ class A(Enum): # E: Detected enum "lib.A" in a type stub with zero members. The # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members x: int class B(A): - x = 1 # E: Cannot override writable attribute "x" with a final one + x = 1 # E: Cannot override writable attribute "x" with a final one \ + # E: Incompatible types in assignment (expression has type "B", base class "A" defined the type as "int") class C(Enum): x = 1 class D(C): # E: Cannot extend enum with existing members: "C" \ # E: Detected enum "lib.D" in a type stub with zero members. There is a chance this is due to a recent change in the semantics of enum membership. If so, use `member = value` to mark an enum member, instead of `member: type` \ # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members - x: int # E: Cannot assign to final name "x" + x: int # E: Incompatible types in assignment (expression has type "int", base class "C" defined the type as "C") \ + # E: Cannot assign to final name "x" [builtins fixtures/bool.pyi] [case testEnumNotFinalWithMethodsAndUninitializedValuesStubMember] @@ -2419,6 +2422,49 @@ def some_a(a: A): reveal_type(a) # N: Revealed type is "Literal[__main__.A.x]" [builtins fixtures/dict.pyi] +[case testEnumAccessFromInstance] +# flags: --python-version 3.11 --warn-unreachable +# This was added in 3.11 +from enum import Enum, member, nonmember + +class A(Enum): + x = 1 + y = member(2) + z = nonmember(3) + +reveal_type(A.x) # N: Revealed type is "Literal[__main__.A.x]?" +reveal_type(A.y) # N: Revealed type is "Literal[__main__.A.y]?" +reveal_type(A.z) # N: Revealed type is "builtins.int" + +reveal_type(A.x.x) # N: Revealed type is "Literal[__main__.A.x]?" +reveal_type(A.x.x.x) # N: Revealed type is "Literal[__main__.A.x]?" +reveal_type(A.x.y) # N: Revealed type is "Literal[__main__.A.y]?" +reveal_type(A.x.y.y) # N: Revealed type is "Literal[__main__.A.y]?" +reveal_type(A.x.z) # N: Revealed type is "builtins.int" + +reveal_type(A.y.x) # N: Revealed type is "Literal[__main__.A.x]?" +reveal_type(A.y.y) # N: Revealed type is "Literal[__main__.A.y]?" +reveal_type(A.y.z) # N: Revealed type is "builtins.int" + +A.z.x # E: "int" has no attribute "x" + +class B(Enum): + x = 1 + value = 2 + +reveal_type(B.x) # N: Revealed type is "Literal[__main__.B.x]?" +reveal_type(B.x.value) # N: Revealed type is "Literal[2]?" +reveal_type(B.x.x.value) # N: Revealed type is "Literal[2]?" +B.x.value.value # E: "int" has no attribute "value" +B.x.value.value.value # E: "int" has no attribute "value" +reveal_type(B.value) # N: Revealed type is "Literal[__main__.B.value]?" +reveal_type(B.value.x) # N: Revealed type is "Literal[__main__.B.x]?" +reveal_type(B.value.x.x) # N: Revealed type is "Literal[__main__.B.x]?" +reveal_type(B.value.x.value) # N: Revealed type is "Literal[2]?" +B.value.x.value.value # E: "int" has no attribute "value" +B.value.value.value # E: "int" has no attribute "value" +[builtins fixtures/dict.pyi] + [case testErrorOnAnnotatedMember] from enum import Enum diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 4c170ec4753f..5f2194114d5e 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5675,10 +5675,12 @@ class FinalEnum(Enum): [builtins fixtures/isinstance.pyi] [out] main:3: error: Cannot override writable attribute "x" with a final one +main:3: error: Incompatible types in assignment (expression has type "Ok", base class "RegularEnum" defined the type as "int") main:4: error: Cannot extend enum with existing members: "FinalEnum" main:5: error: Cannot override final attribute "x" (previously declared in base class "FinalEnum") [out2] main:3: error: Cannot override writable attribute "x" with a final one +main:3: error: Incompatible types in assignment (expression has type "Ok", base class "RegularEnum" defined the type as "int") main:4: error: Cannot extend enum with existing members: "FinalEnum" main:5: error: Cannot override final attribute "x" (previously declared in base class "FinalEnum")