From 788944b8de71e0a334f645ab6610a08783c5f1da Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Mon, 4 Dec 2017 20:56:59 +0200 Subject: [PATCH 1/6] use EnumMeta to mark enum classes --- mypy/nodes.py | 15 --------------- mypy/semanal.py | 7 +++++-- mypy/subtypes.py | 4 ---- test-data/unit/check-enum.test | 15 ++++++++++++--- test-data/unit/lib-stub/enum.pyi | 5 ++++- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 406820619974..b01d4cc057f2 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -81,9 +81,6 @@ def get_column(self) -> int: LITERAL_TYPE = 1 LITERAL_NO = 0 -# Hard coded name of Enum baseclass. -ENUM_BASECLASS = "enum.Enum" - node_kinds = { LDEF: 'Ldef', GDEF: 'Gdef', @@ -2085,7 +2082,6 @@ def calculate_mro(self) -> None: mro = linearize_hierarchy(self) assert mro, "Could not produce a MRO at all for %s" % (self,) self.mro = mro - self.is_enum = self._calculate_is_enum() def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]': declared = self.declared_metaclass @@ -2108,17 +2104,6 @@ def is_metaclass(self) -> bool: return (self.has_base('builtins.type') or self.fullname() == 'abc.ABCMeta' or self.fallback_to_any) - def _calculate_is_enum(self) -> bool: - """ - If this is "enum.Enum" itself, then yes, it's an enum. - If the flag .is_enum has been set on anything in the MRO, it's an enum. - """ - if self.fullname() == ENUM_BASECLASS: - return True - if self.mro: - return any(type_info.is_enum for type_info in self.mro) - return False - def has_base(self, fullname: str) -> bool: """Return True if type has a base type with the specified name. diff --git a/mypy/semanal.py b/mypy/semanal.py index e9d71eb4ecf3..4716498233f0 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1083,8 +1083,6 @@ def analyze_base_classes(self, defn: ClassDef) -> None: # the MRO. Fix MRO if needed. if info.mro and info.mro[-1].fullname() != 'builtins.object': info.mro.append(self.object_type().type) - if defn.info.is_enum and defn.type_vars: - self.fail("Enum class cannot be generic", defn) def update_metaclass(self, defn: ClassDef) -> None: """Lookup for special metaclass declarations, and update defn fields accordingly. @@ -1221,6 +1219,11 @@ def analyze_metaclass(self, defn: ClassDef) -> None: # do not declare explicit metaclass, but it's harder to catch at this stage if defn.metaclass is not None: self.fail("Inconsistent metaclass structure for '%s'" % defn.name, defn) + else: + if defn.info.metaclass_type.type.fullname() == 'enum.EnumMeta': + defn.info.is_enum = True + if defn.type_vars: + self.fail("Enum class cannot be generic", defn) def object_type(self) -> Instance: return self.named_type('__builtins__.object') diff --git a/mypy/subtypes.py b/mypy/subtypes.py index e5034cd50bc1..06ea420e4f61 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -183,10 +183,6 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(item, AnyType): return True if isinstance(item, Instance): - # Special-case enum since we don't have better way of expressing it - if (is_named_instance(left, 'enum.EnumMeta') - and is_named_instance(item, 'enum.Enum')): - return True return is_named_instance(item, 'builtins.object') if isinstance(right, CallableType): # Special case: Instance can be a subtype of Callable. diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 30984ad07c46..24ac491fc402 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -6,10 +6,19 @@ class Medal(Enum): gold = 1 silver = 2 bronze = 3 +reveal_type(Medal.bronze) # E: Revealed type is '__main__.Medal' m = Medal.gold -m = 1 -[out] -main:7: error: Incompatible types in assignment (expression has type "int", variable has type "Medal") +m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") + +[case testEnumFromEnumMetaBasics] +from enum import EnumMeta +class Medal(metaclass=EnumMeta): + gold = 1 + silver = "hello" + bronze = None +reveal_type(Medal.bronze) # E: Revealed type is '__main__.Medal' +m = Medal.gold +m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") [case testEnumNameAndValue] from enum import Enum diff --git a/test-data/unit/lib-stub/enum.pyi b/test-data/unit/lib-stub/enum.pyi index facf519bd5df..2bef9fc01c17 100644 --- a/test-data/unit/lib-stub/enum.pyi +++ b/test-data/unit/lib-stub/enum.pyi @@ -1,6 +1,9 @@ from typing import Any, TypeVar, Union -class Enum: +class EnumMeta(type): + pass + +class Enum(metaclass=EnumMeta): def __new__(cls, value: Any) -> None: pass def __repr__(self) -> str: pass def __str__(self) -> str: pass From 3c16eead5930c6e9ce7c7235d50ee5b6a2366992 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 25 Feb 2018 23:57:11 +0200 Subject: [PATCH 2/6] test subclass of EnumMeta-based class --- test-data/unit/check-enum.test | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 24ac491fc402..90d9cb4f6a14 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -20,6 +20,17 @@ reveal_type(Medal.bronze) # E: Revealed type is '__main__.Medal' m = Medal.gold m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") +[case testEnumFromEnumMetaSubclass] +from enum import EnumMeta +class Achievement(metaclass=EnumMeta): pass +class Medal(Achievement): + gold = 1 + silver = "hello" + bronze = None +reveal_type(Medal.bronze) # E: Revealed type is '__main__.Medal' +m = Medal.gold +m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") + [case testEnumNameAndValue] from enum import Enum class Truth(Enum): From d8a7e7766a8ef61030efc54c6da45daef3978610 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Mon, 26 Feb 2018 00:48:49 +0200 Subject: [PATCH 3/6] test Enum class cannot be generic --- test-data/unit/check-enum.test | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 90d9cb4f6a14..546d6498785a 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -31,6 +31,13 @@ reveal_type(Medal.bronze) # E: Revealed type is '__main__.Medal' m = Medal.gold m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") +[case testEnumFromEnumMetaGeneric] +from enum import EnumMeta +from typing import Generic, TypeVar +T = TypeVar("T") +class Medal(Generic[T], metaclass=EnumMeta): # E: Enum class cannot be generic + q = None + [case testEnumNameAndValue] from enum import Enum class Truth(Enum): From b421be36e022ddf628076103d050cbc5e8ee2e14 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 1 Mar 2018 13:06:38 +0200 Subject: [PATCH 4/6] support EnumMeta subclasses --- mypy/semanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 88d8801d99d5..6a10668c9b9c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1274,7 +1274,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None: if defn.metaclass is not None: self.fail("Inconsistent metaclass structure for '%s'" % defn.name, defn) else: - if defn.info.metaclass_type.type.fullname() == 'enum.EnumMeta': + if defn.info.metaclass_type.type.has_base('enum.EnumMeta'): defn.info.is_enum = True if defn.type_vars: self.fail("Enum class cannot be generic", defn) From a1d61e7cd7d63fde298fd8d82cc4ef84318d5bed Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 8 Apr 2018 22:13:21 +0300 Subject: [PATCH 5/6] use dummy constructor and explain why --- test-data/unit/check-enum.test | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 546d6498785a..450fe8f60a35 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -16,6 +16,9 @@ class Medal(metaclass=EnumMeta): gold = 1 silver = "hello" bronze = None + # Without __init__ the definition fails at runtime, but we want to verify that mypy + # uses `enum.EnumMeta` and not `enum.Enum` as the definition of what is enum. + def __init__(self, *args): pass reveal_type(Medal.bronze) # E: Revealed type is '__main__.Medal' m = Medal.gold m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") @@ -27,6 +30,8 @@ class Medal(Achievement): gold = 1 silver = "hello" bronze = None + # See comment in testEnumFromEnumMetaBasics + def __init__(self, *args): pass reveal_type(Medal.bronze) # E: Revealed type is '__main__.Medal' m = Medal.gold m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") From 029fd323117a74e3db727463df1f4d97212b4cf8 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 8 Apr 2018 22:30:53 +0300 Subject: [PATCH 6/6] propagate changes to testClassBasedEnum_typeinfo --- test-data/unit/merge.test | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index de9869e76a1d..a7dd72fab270 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -1440,7 +1440,8 @@ TypeInfo<0>( Bases(enum.Enum<1>) Mro(target.A<0>, enum.Enum<1>, builtins.object<2>) Names( - X<3> (builtins.int<4>))) + X<3> (builtins.int<4>)) + MetaclassType(enum.EnumMeta<5>)) ==> TypeInfo<0>( Name(target.A) @@ -1448,4 +1449,5 @@ TypeInfo<0>( Mro(target.A<0>, enum.Enum<1>, builtins.object<2>) Names( X<3> (builtins.int<4>) - Y<5> (builtins.int<4>))) + Y<6> (builtins.int<4>)) + MetaclassType(enum.EnumMeta<5>))