From 2378b57dcba25d1e97f049520733ff97855d5f49 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 5 Jan 2022 21:49:28 +0300 Subject: [PATCH 1/6] WIP --- mypy/checker.py | 38 +++++++++++++++++++++++++++++++++++++- mypy/stubtest.py | 16 +--------------- mypy/util.py | 19 +++++++++++++++++++ 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6f8ab758cc94..cee223ed0d98 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -86,7 +86,7 @@ from mypy import state, errorcodes as codes from mypy.traverser import has_return_statement, all_return_statements from mypy.errorcodes import ErrorCode -from mypy.util import is_typeshed_file +from mypy.util import is_typeshed_file, is_dunder, is_sunder T = TypeVar('T') @@ -1833,6 +1833,10 @@ def visit_class_def(self, defn: ClassDef) -> None: # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) if typ.is_protocol and typ.defn.type_vars: self.check_protocol_variance(defn) + if not defn.has_incompatible_baseclass and defn.info.is_enum: + for base in defn.info.mro[1:-1]: # we don't need self and `object` + if base.is_enum and base.fullname not in ENUM_BASES: + self.check_final_enum(defn, base) def check_final_deletable(self, typ: TypeInfo) -> None: # These checks are only for mypyc. Only perform some checks that are easier @@ -1890,6 +1894,38 @@ def check_init_subclass(self, defn: ClassDef) -> None: # all other bases have already been checked. break + def check_final_enum(self, defn: ClassDef, base: TypeInfo) -> None: + for sym in base.names.values(): + if self.is_final_enum_value(sym): + self.fail('Cannot extend enum with members: "{}"'.format(base.fullname)) + break + + def is_final_enum_value(self, sym: SymbolTableNode) -> bool: + if isinstance(sym.node, (FuncBase, Decorator)): + return False # A method is fine + if not isinstance(sym.node, Var): + return True # Can be a class or anything else + + # Now, only `Var` is left, we need to check: + # 1. Private name like in `__prop = 1` + # 2. Dunder name like `__hash__ = some_hasher` + # 3. Sunder name like `_order_ = 'a, b, c'` + # 4. If it is a method / descriptor like in `method = classmethod(func)` + if ( + is_private(sym.node.name) + or is_dunder(sym.node.name) + or is_sunder(sym.node.name) + ): + return False + + if sym.node.type and is_subtype(sym.node.type) + + # if self.is_stub: + # return True + # if sym.node.has_explicit_value: + # return True + return False + def check_protocol_variance(self, defn: ClassDef) -> None: """Check that protocol definition is compatible with declared variances of type variables. diff --git a/mypy/stubtest.py b/mypy/stubtest.py index cb005c4bd52c..47e68fb83980 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -25,7 +25,7 @@ from mypy import nodes from mypy.config_parser import parse_config_file from mypy.options import Options -from mypy.util import FancyFormatter, bytes_to_human_readable_repr +from mypy.util import FancyFormatter, bytes_to_human_readable_repr, is_dunder class Missing: @@ -899,20 +899,6 @@ def verify_typealias( ) -SPECIAL_DUNDERS = ("__init__", "__new__", "__call__", "__init_subclass__", "__class_getitem__") - - -def is_dunder(name: str, exclude_special: bool = False) -> bool: - """Returns whether name is a dunder name. - - :param exclude_special: Whether to return False for a couple special dunder methods. - - """ - if exclude_special and name in SPECIAL_DUNDERS: - return False - return name.startswith("__") and name.endswith("__") - - def is_probably_a_function(runtime: Any) -> bool: return ( isinstance(runtime, (types.FunctionType, types.BuiltinFunctionType)) diff --git a/mypy/util.py b/mypy/util.py index 9f620e823e91..d22ddc4aafd1 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -46,6 +46,25 @@ "C:\\Python27\\python.exe", ] +SPECIAL_DUNDERS: Final = frozenset(( + "__init__", "__new__", "__call__", "__init_subclass__", "__class_getitem__", +)) + + +def is_dunder(name: str, exclude_special: bool = False) -> bool: + """Returns whether name is a dunder name. + + :param exclude_special: Whether to return False for a couple special dunder methods. + + """ + if exclude_special and name in SPECIAL_DUNDERS: + return False + return name.startswith("__") and name.endswith("__") + + +def is_sunder(name: str) -> bool: + return not is_dunder(name) and name.startswith('_') and name.endswith('_') + def split_module_names(mod_name: str) -> List[str]: """Return the module and all parent module names. From ea552acb35ccfa6608250c3d6eebaa968a414b8c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 13 Jan 2022 14:43:23 +0300 Subject: [PATCH 2/6] Move `final` detection for `Enum` in `checker.py`, refs #11850 --- mypy/checker.py | 21 ++++--- mypy/semanal.py | 26 --------- test-data/unit/check-enum.test | 102 ++++++++++++++++++++++++++------- 3 files changed, 94 insertions(+), 55 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cee223ed0d98..6f63037ef466 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -24,7 +24,7 @@ Context, Decorator, PrintStmt, BreakStmt, PassStmt, ContinueStmt, ComparisonExpr, StarExpr, EllipsisExpr, RefExpr, PromoteExpr, Import, ImportFrom, ImportAll, ImportBase, TypeAlias, - ARG_POS, ARG_STAR, LITERAL_TYPE, LDEF, MDEF, GDEF, + ARG_POS, ARG_STAR, ARG_STAR2, ARG_NAMED, LITERAL_TYPE, LDEF, MDEF, GDEF, CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr, is_final_node, ARG_NAMED, MatchStmt) from mypy import nodes @@ -1897,7 +1897,12 @@ def check_init_subclass(self, defn: ClassDef) -> None: def check_final_enum(self, defn: ClassDef, base: TypeInfo) -> None: for sym in base.names.values(): if self.is_final_enum_value(sym): - self.fail('Cannot extend enum with members: "{}"'.format(base.fullname)) + self.fail( + 'Cannot extend enum with existing members: "{}"'.format( + base.name, + ), + defn, + ) break def is_final_enum_value(self, sym: SymbolTableNode) -> bool: @@ -1915,15 +1920,15 @@ def is_final_enum_value(self, sym: SymbolTableNode) -> bool: is_private(sym.node.name) or is_dunder(sym.node.name) or is_sunder(sym.node.name) + # TODO: make sure that `x = @class/staticmethod(func)` + # and `x = property(prop)` both work correctly. + # Now they are incorrectly counted as enum members. + or isinstance(sym.node.type, FunctionLike) ): return False - if sym.node.type and is_subtype(sym.node.type) - - # if self.is_stub: - # return True - # if sym.node.has_explicit_value: - # return True + if self.is_stub or sym.node.has_explicit_value: + return True return False def check_protocol_variance(self, defn: ClassDef) -> None: diff --git a/mypy/semanal.py b/mypy/semanal.py index 7b9348eb21e8..b18cce912d52 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1554,13 +1554,6 @@ def configure_base_classes(self, elif isinstance(base, Instance): if base.type.is_newtype: self.fail('Cannot subclass "NewType"', defn) - if self.enum_has_final_values(base): - # This means that are trying to subclass a non-default - # Enum class, with defined members. This is not possible. - # In runtime, it will raise. We need to mark this type as final. - # However, methods can be defined on a type: only values can't. - # We also don't count values with annotations only. - base.type.is_final = True base_types.append(base) elif isinstance(base, AnyType): if self.options.disallow_subclassing_any: @@ -1598,25 +1591,6 @@ def configure_base_classes(self, return self.calculate_class_mro(defn, self.object_type) - def enum_has_final_values(self, base: Instance) -> bool: - if ( - base.type.is_enum - and base.type.fullname not in ENUM_BASES - and base.type.names - and base.type.defn - ): - for sym in base.type.names.values(): - if isinstance(sym.node, (FuncBase, Decorator)): - continue # A method - if not isinstance(sym.node, Var): - return True # Can be a class - if self.is_stub_file or sym.node.has_explicit_value: - # Corner case: assignments like `x: int` are fine in `.py` files. - # But, not is `.pyi` files, because we don't know - # if there's aactually a value or not. - return True - return False - def configure_tuple_base_class(self, defn: ClassDef, base: TupleType, diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index aac97c6c69af..0716b13d1a0d 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1469,22 +1469,22 @@ class NonEmptyFlag(Flag): class NonEmptyIntFlag(IntFlag): x = 1 -class ErrorEnumWithValue(NonEmptyEnum): # E: Cannot inherit from final class "NonEmptyEnum" +class ErrorEnumWithValue(NonEmptyEnum): # E: Cannot extend enum with existing members: "NonEmptyEnum" x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyEnum") -class ErrorIntEnumWithValue(NonEmptyIntEnum): # E: Cannot inherit from final class "NonEmptyIntEnum" +class ErrorIntEnumWithValue(NonEmptyIntEnum): # E: Cannot extend enum with existing members: "NonEmptyIntEnum" x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyIntEnum") -class ErrorFlagWithValue(NonEmptyFlag): # E: Cannot inherit from final class "NonEmptyFlag" +class ErrorFlagWithValue(NonEmptyFlag): # E: Cannot extend enum with existing members: "NonEmptyFlag" x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyFlag") -class ErrorIntFlagWithValue(NonEmptyIntFlag): # E: Cannot inherit from final class "NonEmptyIntFlag" +class ErrorIntFlagWithValue(NonEmptyIntFlag): # E: Cannot extend enum with existing members: "NonEmptyIntFlag" x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyIntFlag") -class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot inherit from final class "NonEmptyEnum" +class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot extend enum with existing members: "NonEmptyEnum" pass -class ErrorIntEnumWithoutValue(NonEmptyIntEnum): # E: Cannot inherit from final class "NonEmptyIntEnum" +class ErrorIntEnumWithoutValue(NonEmptyIntEnum): # E: Cannot extend enum with existing members: "NonEmptyIntEnum" pass -class ErrorFlagWithoutValue(NonEmptyFlag): # E: Cannot inherit from final class "NonEmptyFlag" +class ErrorFlagWithoutValue(NonEmptyFlag): # E: Cannot extend enum with existing members: "NonEmptyFlag" pass -class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot inherit from final class "NonEmptyIntFlag" +class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot extend enum with existing members: "NonEmptyIntFlag" pass [builtins fixtures/bool.pyi] @@ -1588,13 +1588,17 @@ class NonEmptyIntFlag(IntFlag): class NonEmptyEnumMeta(EnumMeta): x = 1 -class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot inherit from final class "NonEmptyEnum" +class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot inherit from final class "NonEmptyEnum" \ + # E: Cannot extend enum with existing members: "NonEmptyEnum" pass -class ErrorIntEnumWithoutValue(NonEmptyIntEnum): # E: Cannot inherit from final class "NonEmptyIntEnum" +class ErrorIntEnumWithoutValue(NonEmptyIntEnum): # E: Cannot inherit from final class "NonEmptyIntEnum" \ + # E: Cannot extend enum with existing members: "NonEmptyIntEnum" pass -class ErrorFlagWithoutValue(NonEmptyFlag): # E: Cannot inherit from final class "NonEmptyFlag" +class ErrorFlagWithoutValue(NonEmptyFlag): # E: Cannot inherit from final class "NonEmptyFlag" \ + # E: Cannot extend enum with existing members: "NonEmptyFlag" pass -class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot inherit from final class "NonEmptyIntFlag" +class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot inherit from final class "NonEmptyIntFlag" \ + # E: Cannot extend enum with existing members: "NonEmptyIntFlag" pass class ErrorEnumMetaWithoutValue(NonEmptyEnumMeta): # E: Cannot inherit from final class "NonEmptyEnumMeta" pass @@ -1692,13 +1696,13 @@ class NonEmptyIntFlag(IntFlag): x = 1 def method(self) -> None: pass -class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot inherit from final class "NonEmptyEnum" +class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot extend enum with existing members: "NonEmptyEnum" pass -class ErrorIntEnumWithoutValue(NonEmptyIntEnum): # E: Cannot inherit from final class "NonEmptyIntEnum" +class ErrorIntEnumWithoutValue(NonEmptyIntEnum): # E: Cannot extend enum with existing members: "NonEmptyIntEnum" pass -class ErrorFlagWithoutValue(NonEmptyFlag): # E: Cannot inherit from final class "NonEmptyFlag" +class ErrorFlagWithoutValue(NonEmptyFlag): # E: Cannot extend enum with existing members: "NonEmptyFlag" pass -class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot inherit from final class "NonEmptyIntFlag" +class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot extend enum with existing members: "NonEmptyIntFlag" pass [builtins fixtures/bool.pyi] @@ -1707,7 +1711,7 @@ from enum import Enum class A(Enum): class Inner: pass -class B(A): pass # E: Cannot inherit from final class "A" +class B(A): pass # E: Cannot extend enum with existing members: "A" [builtins fixtures/bool.pyi] [case testEnumFinalSpecialProps] @@ -1765,12 +1769,12 @@ class B(A): class A1(Enum): x: int = 1 -class B1(A1): # E: Cannot inherit from final class "A1" +class B1(A1): # E: Cannot extend enum with existing members: "A1" pass class A2(Enum): x = 2 -class B2(A2): # E: Cannot inherit from final class "A2" +class B2(A2): # E: Cannot extend enum with existing members: "A2" pass # We leave this `Final` without a value, @@ -1788,12 +1792,12 @@ import lib from enum import Enum class A(Enum): x: int -class B(A): # E: Cannot inherit from final class "A" +class B(A): # E: Cannot extend enum with existing members: "A" x = 1 # E: Cannot override writable attribute "x" with a final one class C(Enum): x = 1 -class D(C): # E: Cannot inherit from final class "C" +class D(C): # E: Cannot extend enum with existing members: "C" x: int # E: Cannot assign to final name "x" [builtins fixtures/bool.pyi] @@ -1811,3 +1815,59 @@ reveal_type(A.int.value) # N: Revealed type is "Literal[1]?" reveal_type(A.bool.value) # N: Revealed type is "Literal[False]?" reveal_type(A.tuple.value) # N: Revealed type is "Tuple[Literal[1]?]" [builtins fixtures/tuple.pyi] + +[case testFinalWithPrivateAssignment] +import enum +class Some(enum.Enum): + __priv = 1 + +class Other(Some): # Should pass + pass +[builtins fixtures/tuple.pyi] + + +[case testFinalWithDunderAssignment] +import enum +class Some(enum.Enum): + __some__ = 1 + +class Other(Some): # Should pass + pass +[builtins fixtures/tuple.pyi] + + +[case testFinalWithSunderAssignment] +import enum +class Some(enum.Enum): + _some_ = 1 + +class Other(Some): # Should pass + pass +[builtins fixtures/tuple.pyi] + + +[case testFinalWithMethodAssignment] +import enum +from typing import overload +class Some(enum.Enum): + def lor(self, other) -> bool: + pass + + ror = lor + +class Other(Some): # Should pass + pass + + +class WithOverload(enum.IntEnum): + @overload + def meth(self, arg: int) -> int: pass + @overload + def meth(self, arg: str) -> str: pass + def meth(self, arg): pass + + alias = meth + +class SubWithOverload(WithOverload): # Should pass + pass +[builtins fixtures/tuple.pyi] From 3a34b4897808f148b06921668f59e7d95d45e13d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 13 Jan 2022 15:11:39 +0300 Subject: [PATCH 3/6] Fixes CI --- mypy/checker.py | 4 ++-- mypy/semanal.py | 6 +++--- mypy/stubtest.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6f63037ef466..bedae6bf159b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -24,7 +24,7 @@ Context, Decorator, PrintStmt, BreakStmt, PassStmt, ContinueStmt, ComparisonExpr, StarExpr, EllipsisExpr, RefExpr, PromoteExpr, Import, ImportFrom, ImportAll, ImportBase, TypeAlias, - ARG_POS, ARG_STAR, ARG_STAR2, ARG_NAMED, LITERAL_TYPE, LDEF, MDEF, GDEF, + ARG_POS, ARG_STAR, ARG_NAMED, LITERAL_TYPE, LDEF, MDEF, GDEF, CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr, is_final_node, ARG_NAMED, MatchStmt) from mypy import nodes @@ -1923,7 +1923,7 @@ def is_final_enum_value(self, sym: SymbolTableNode) -> bool: # TODO: make sure that `x = @class/staticmethod(func)` # and `x = property(prop)` both work correctly. # Now they are incorrectly counted as enum members. - or isinstance(sym.node.type, FunctionLike) + or isinstance(get_proper_type(sym.node.type), FunctionLike) ): return False diff --git a/mypy/semanal.py b/mypy/semanal.py index b18cce912d52..3bc1d35663c1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -78,11 +78,11 @@ typing_extensions_aliases, EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr, ParamSpecExpr, EllipsisExpr, TypeVarLikeExpr, FuncBase, implicit_module_attrs, - MatchStmt + MatchStmt, ) from mypy.patterns import ( AsPattern, OrPattern, ValuePattern, SequencePattern, - StarredPattern, MappingPattern, ClassPattern + StarredPattern, MappingPattern, ClassPattern, ) from mypy.tvar_scope import TypeVarLikeScope from mypy.typevars import fill_typevars @@ -123,7 +123,7 @@ ) from mypy.semanal_namedtuple import NamedTupleAnalyzer from mypy.semanal_typeddict import TypedDictAnalyzer -from mypy.semanal_enum import EnumCallAnalyzer, ENUM_BASES +from mypy.semanal_enum import EnumCallAnalyzer from mypy.semanal_newtype import NewTypeAnalyzer from mypy.reachability import ( infer_reachability_of_if_statement, infer_reachability_of_match_statement, diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 47e68fb83980..d0f62dfc3ed2 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -25,7 +25,7 @@ from mypy import nodes from mypy.config_parser import parse_config_file from mypy.options import Options -from mypy.util import FancyFormatter, bytes_to_human_readable_repr, is_dunder +from mypy.util import FancyFormatter, bytes_to_human_readable_repr, is_dunder, SPECIAL_DUNDERS class Missing: From 55beda19cdc9d78ca8ff8062a57859e1d26999fb Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Fri, 14 Jan 2022 12:58:01 +0300 Subject: [PATCH 4/6] Fixes CI --- test-data/unit/check-incremental.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index ec1dff4977d4..1a73f9f33274 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5627,9 +5627,9 @@ class FinalEnum(Enum): [builtins fixtures/isinstance.pyi] [out] main:3: error: Cannot override writable attribute "x" with a final one -main:4: error: Cannot inherit from final class "FinalEnum" +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:4: error: Cannot inherit from final class "FinalEnum" +main:4: error: Cannot extend enum with existing members: "FinalEnum" main:5: error: Cannot override final attribute "x" (previously declared in base class "FinalEnum") From 69af50c4393df02984e0c01714aa2975efd66cdb Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 17 Jan 2022 20:00:19 +0300 Subject: [PATCH 5/6] Address review --- mypy/util.py | 3 ++- test-data/unit/check-enum.test | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index d22ddc4aafd1..cd1e15afb4cf 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -54,7 +54,8 @@ def is_dunder(name: str, exclude_special: bool = False) -> bool: """Returns whether name is a dunder name. - :param exclude_special: Whether to return False for a couple special dunder methods. + Args: + exclude_special: Whether to return False for a couple special dunder methods. """ if exclude_special and name in SPECIAL_DUNDERS: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 0716b13d1a0d..2af57da9cbdc 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1825,7 +1825,6 @@ class Other(Some): # Should pass pass [builtins fixtures/tuple.pyi] - [case testFinalWithDunderAssignment] import enum class Some(enum.Enum): @@ -1835,7 +1834,6 @@ class Other(Some): # Should pass pass [builtins fixtures/tuple.pyi] - [case testFinalWithSunderAssignment] import enum class Some(enum.Enum): @@ -1845,7 +1843,6 @@ class Other(Some): # Should pass pass [builtins fixtures/tuple.pyi] - [case testFinalWithMethodAssignment] import enum from typing import overload From f9cbe7f3d93d5fb28f387fe483b4eaf20bd93351 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 18 Jan 2022 19:41:23 +0300 Subject: [PATCH 6/6] Fixes CI --- mypy/checker.py | 2 +- mypy/semanal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index bedae6bf159b..2f99b9b4fece 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -26,7 +26,7 @@ Import, ImportFrom, ImportAll, ImportBase, TypeAlias, ARG_POS, ARG_STAR, ARG_NAMED, LITERAL_TYPE, LDEF, MDEF, GDEF, CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr, - is_final_node, ARG_NAMED, MatchStmt) + is_final_node, MatchStmt) from mypy import nodes from mypy import operators from mypy.literals import literal, literal_hash, Key diff --git a/mypy/semanal.py b/mypy/semanal.py index 3bc1d35663c1..a7c5eda732af 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -77,7 +77,7 @@ REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_source_versions, typing_extensions_aliases, EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr, - ParamSpecExpr, EllipsisExpr, TypeVarLikeExpr, FuncBase, implicit_module_attrs, + ParamSpecExpr, EllipsisExpr, TypeVarLikeExpr, implicit_module_attrs, MatchStmt, ) from mypy.patterns import (