diff --git a/mypy/checker.py b/mypy/checker.py index 1a8b22aaa79e..5bc7809eb7c4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -50,6 +50,7 @@ type_object_type, analyze_decorator_or_funcbase_access, ) +from mypy.semanal_enum import ENUM_BASES, ENUM_SPECIAL_PROPS from mypy.typeops import ( map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union, erase_def_to_union_or_bound, erase_to_union_or_bound, coerce_to_literal, @@ -2522,13 +2523,14 @@ def check_compatibility_final_super(self, node: Var, self.msg.cant_override_final(node.name, base.name, node) return False if node.is_final: + if base.fullname in ENUM_BASES and node.name in ENUM_SPECIAL_PROPS: + return True self.check_if_final_var_override_writable(node.name, base_node, node) return True def check_if_final_var_override_writable(self, name: str, - base_node: - Optional[Node], + base_node: Optional[Node], ctx: Context) -> None: """Check that a final variable doesn't override writeable attribute. diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a857606e4579..93df9c612126 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -14,6 +14,7 @@ has_any_from_unimported_type, check_for_explicit_any, set_any_tvars, expand_type_alias, make_optional_type, ) +from mypy.semanal_enum import ENUM_BASES from mypy.types import ( Type, AnyType, CallableType, Overloaded, NoneType, TypeVarType, TupleType, TypedDictType, Instance, ErasedType, UnionType, @@ -1000,9 +1001,7 @@ def check_callable_call(self, ret_type = get_proper_type(callee.ret_type) if callee.is_type_obj() and isinstance(ret_type, Instance): callable_name = ret_type.type.fullname - if (isinstance(callable_node, RefExpr) - and callable_node.fullname in ('enum.Enum', 'enum.IntEnum', - 'enum.Flag', 'enum.IntFlag')): + if isinstance(callable_node, RefExpr) and callable_node.fullname in ENUM_BASES: # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). return callee.ret_type, callee diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 53241304cf48..ea9a02f5b41e 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -18,15 +18,13 @@ from mypy.typeops import make_simplified_union from mypy.nodes import TypeInfo from mypy.subtypes import is_equivalent +from mypy.semanal_enum import ENUM_BASES -# Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use -# enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes. -ENUM_PREFIXES: Final = {"enum.Enum", "enum.IntEnum", "enum.Flag", "enum.IntFlag"} -ENUM_NAME_ACCESS: Final = {"{}.name".format(prefix) for prefix in ENUM_PREFIXES} | { - "{}._name_".format(prefix) for prefix in ENUM_PREFIXES +ENUM_NAME_ACCESS: Final = {"{}.name".format(prefix) for prefix in ENUM_BASES} | { + "{}._name_".format(prefix) for prefix in ENUM_BASES } -ENUM_VALUE_ACCESS: Final = {"{}.value".format(prefix) for prefix in ENUM_PREFIXES} | { - "{}._value_".format(prefix) for prefix in ENUM_PREFIXES +ENUM_VALUE_ACCESS: Final = {"{}.value".format(prefix) for prefix in ENUM_BASES} | { + "{}._value_".format(prefix) for prefix in ENUM_BASES } diff --git a/mypy/semanal.py b/mypy/semanal.py index fe3151ce6cd2..1d4a64421391 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -116,7 +116,7 @@ ) from mypy.semanal_namedtuple import NamedTupleAnalyzer from mypy.semanal_typeddict import TypedDictAnalyzer -from mypy.semanal_enum import EnumCallAnalyzer +from mypy.semanal_enum import EnumCallAnalyzer, ENUM_BASES from mypy.semanal_newtype import NewTypeAnalyzer from mypy.reachability import ( infer_reachability_of_if_statement, infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE, @@ -1554,8 +1554,7 @@ def configure_base_classes(self, self.fail('Cannot subclass "NewType"', defn) if ( base.type.is_enum - and base.type.fullname not in ( - 'enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag') + and base.type.fullname not in ENUM_BASES and base.type.names and any(not isinstance(n.node, (FuncBase, Decorator)) for n in base.type.names.values()) diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index 07e8e048decd..5682f66298f6 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -4,6 +4,7 @@ """ from typing import List, Tuple, Optional, Union, cast +from typing_extensions import Final from mypy.nodes import ( Expression, Context, TypeInfo, AssignmentStmt, NameExpr, CallExpr, RefExpr, StrExpr, @@ -13,6 +14,15 @@ from mypy.semanal_shared import SemanticAnalyzerInterface from mypy.options import Options +# Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use +# enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes. +ENUM_BASES: Final = frozenset(( + 'enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag', +)) +ENUM_SPECIAL_PROPS: Final = frozenset(( + 'name', 'value', '_name_', '_value_', '_order_', '__order__', +)) + class EnumCallAnalyzer: def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None: @@ -62,7 +72,7 @@ class A(enum.Enum): if not isinstance(callee, RefExpr): return None fullname = callee.fullname - if fullname not in ('enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag'): + if fullname not in ENUM_BASES: return None items, values, ok = self.parse_enum_call_args(call, fullname.split('.')[-1]) if not ok: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 21350c030186..dd94bb6f82ef 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1392,7 +1392,7 @@ Medal.silver = 2 # E: Cannot assign to final attribute "silver" from enum import Enum class Types(Enum): key = 0 - value = 1 # E: Cannot override writable attribute "value" with a final one + value = 1 [case testEnumReusedKeys] @@ -1677,3 +1677,28 @@ class A(Enum): class Inner: pass class B(A): pass # E: Cannot inherit from final class "A" [builtins fixtures/bool.pyi] + + +[case testEnumFinalSpecialProps] +# https://github.com/python/mypy/issues/11699 +from enum import Enum, IntEnum + +class E(Enum): + name = 'a' + value = 'b' + _name_ = 'a1' + _value_ = 'b2' + _order_ = 'X Y' + __order__ = 'X Y' + +class EI(IntEnum): + name = 'a' + value = 1 + _name_ = 'a1' + _value_ = 2 + _order_ = 'X Y' + __order__ = 'X Y' + +E._order_ = 'a' # E: Cannot assign to final attribute "_order_" +EI.value = 2 # E: Cannot assign to final attribute "value" +[builtins fixtures/bool.pyi]