From 819015a8e7fe10a8b5c0377835352e3016be0c8a Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 13:51:46 +0300 Subject: [PATCH 01/15] Detect metaclass conflicts, refs #13563 --- mypy/checker.py | 21 +++++++++++++++++++ test-data/unit/check-classes.test | 35 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 1f4cb8bc7b3a..a4bb4a30e0d2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2042,6 +2042,7 @@ def visit_class_def(self, defn: ClassDef) -> None: if not defn.has_incompatible_baseclass: # Otherwise we've already found errors; more errors are not useful self.check_multiple_inheritance(typ) + self.check_metaclass_compatibility(typ) self.check_final_deletable(typ) if defn.decorators: @@ -2381,6 +2382,26 @@ class C(B, A[int]): ... # this is unsafe because... if not ok: self.msg.base_class_definitions_incompatible(name, base1, base2, ctx) + def check_metaclass_compatibility(self, typ: TypeInfo) -> None: + """Ensures that metaclasses of all parent types are compatible.""" + metaclasses = [ + entry.metaclass_type + for entry in typ.mro[1:-1] + if entry.metaclass_type + and not is_named_instance(entry.metaclass_type, "builtins.type") + ] + if not metaclasses: + return + if typ.metaclass_type is not None and all( + is_subtype(typ.metaclass_type, meta) for meta in metaclasses + ): + return + self.fail( + "Metaclass conflict: the metaclass of a derived class must be " + "a (non-strict) subclass of the metaclasses of all its bases", + typ, + ) + def visit_import_from(self, node: ImportFrom) -> None: self.check_import(node) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 55f368979158..74ddb5a6be12 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6663,6 +6663,41 @@ class MyMetaClass(type): class MyClass(metaclass=MyMetaClass): pass +[case testMetaclassConflict] +class MyMeta1(type): ... +class MyMeta2(type): ... +class MyMeta3(type): ... +class A(metaclass=MyMeta1): ... +class B(metaclass=MyMeta2): ... +class C(metaclass=type): ... +class A1(A): ... +class E: ... + +class CorrectMeta(MyMeta1, MyMeta2): ... +class CorrectSubclass1(A1, B, E, metaclass=CorrectMeta): ... +class CorrectSubclass2(A, B, E, metaclass=CorrectMeta): ... +class CorrectSubclass3(B, A, metaclass=CorrectMeta): ... + +class ChildOfCorrectSubclass1(CorrectSubclass1): ... + +class CorrectWithType1(C, A1): ... +class CorrectWithType2(B, C): ... + +class Conflict1(A1, B, E): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict2(A, B): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict3(B, A): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + +class ChildOfConflict1(Conflict3): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class ChildOfConflict2(Conflict3, metaclass=CorrectMeta): ... + +class ConflictingMeta(MyMeta1, MyMeta3): ... +class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Inconsistent metaclass structure for "Conflict4" \ + # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + +class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Inconsistent metaclass structure for "ChildOfCorrectButWrongMeta" \ + # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + ... + [case testGenericOverride] from typing import Generic, TypeVar, Any From 0373bfec431273f0f1f927f54224e62df811c33f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 14:07:43 +0300 Subject: [PATCH 02/15] Add known exceptions --- mypy/checker.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index a4bb4a30e0d2..9c8105d87488 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2384,6 +2384,14 @@ class C(B, A[int]): ... # this is unsafe because... def check_metaclass_compatibility(self, typ: TypeInfo) -> None: """Ensures that metaclasses of all parent types are compatible.""" + if ( + typ.is_metaclass() + or typ.is_protocol + or typ.is_named_tuple + or typ.typeddict_type is not None + ): + return # Reasonable exceptions from this check + metaclasses = [ entry.metaclass_type for entry in typ.mro[1:-1] From 57a8521ecf1999e41d9604ca6ef0f33d4b19eda8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 14:51:49 +0300 Subject: [PATCH 03/15] Remove old metaclass error in semanal.py --- docs/source/metaclasses.rst | 10 +++++++--- mypy/checker.py | 1 + mypy/semanal.py | 6 +----- test-data/unit/check-classes.test | 14 ++++++-------- test-data/unit/fine-grained.test | 2 -- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/docs/source/metaclasses.rst b/docs/source/metaclasses.rst index a5d16aa722fd..43efdd8d5fd4 100644 --- a/docs/source/metaclasses.rst +++ b/docs/source/metaclasses.rst @@ -72,12 +72,16 @@ so it's better not to combine metaclasses and class hierarchies: class A1(metaclass=M1): pass class A2(metaclass=M2): pass - class B1(A1, metaclass=M2): pass # Mypy Error: Inconsistent metaclass structure for "B1" + class B1(A1, metaclass=M2): pass # Mypy Error: metaclass conflict "B1" # At runtime the above definition raises an exception # TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - # Same runtime error as in B1, but mypy does not catch it yet - class B12(A1, A2): pass + class B12(A1, A2): pass # Mypy Error: metaclass conflict "B12" + + # This can be solved via a common metaclass subtype: + class CorrectMeta(M1, M2): pass + class B2(A1, A2, metaclass=CorrectMeta): pass # OK, runtime is also OK + * Mypy does not understand dynamically-computed metaclasses, such as ``class A(metaclass=f()): ...`` diff --git a/mypy/checker.py b/mypy/checker.py index 9c8105d87488..ac18db061dbb 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2388,6 +2388,7 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None: typ.is_metaclass() or typ.is_protocol or typ.is_named_tuple + or typ.is_enum or typ.typeddict_type is not None ): return # Reasonable exceptions from this check diff --git a/mypy/semanal.py b/mypy/semanal.py index 65b883793907..baebe3c6a25f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2103,12 +2103,8 @@ def analyze_metaclass(self, defn: ClassDef) -> None: # TODO: add a metaclass conflict check if there is another metaclass. abc_meta = self.named_type_or_none("abc.ABCMeta", []) if abc_meta is not None: # May be None in tests with incomplete lib-stub. + # We check metaclass correctness in `checker.py` defn.info.metaclass_type = abc_meta - if defn.info.metaclass_type is None: - # Inconsistency may happen due to multiple baseclasses even in classes that - # do not declare explicit metaclass, but it's harder to catch at this stage - if defn.metaclass is not None: - self.fail(f'Inconsistent metaclass structure for "{defn.name}"', defn) else: if defn.info.metaclass_type.type.has_base("enum.EnumMeta"): defn.info.is_enum = True diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 74ddb5a6be12..434aa2a6e052 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4351,7 +4351,7 @@ class C(B): class X(type): pass class Y(type): pass class A(metaclass=X): pass -class B(A, metaclass=Y): pass # E: Inconsistent metaclass structure for "B" +class B(A, metaclass=Y): pass [case testMetaclassNoTypeReveal] class M: @@ -5213,8 +5213,8 @@ class CD(six.with_metaclass(M)): pass # E: Multiple metaclass definitions class M1(type): pass class Q1(metaclass=M1): pass @six.add_metaclass(M) -class CQA(Q1): pass # E: Inconsistent metaclass structure for "CQA" -class CQW(six.with_metaclass(M, Q1)): pass # E: Inconsistent metaclass structure for "CQW" +class CQA(Q1): pass +class CQW(six.with_metaclass(M, Q1)): pass [builtins fixtures/tuple.pyi] [case testSixMetaclassAny] @@ -5319,7 +5319,7 @@ class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not sup class M1(type): pass class Q1(metaclass=M1): pass -class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Inconsistent metaclass structure for "CQW" +class CQW(future.utils.with_metaclass(M, Q1)): pass [builtins fixtures/tuple.pyi] [case testFutureMetaclassAny] @@ -6691,11 +6691,9 @@ class ChildOfConflict1(Conflict3): ... # E: Metaclass conflict: the metaclass o class ChildOfConflict2(Conflict3, metaclass=CorrectMeta): ... class ConflictingMeta(MyMeta1, MyMeta3): ... -class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Inconsistent metaclass structure for "Conflict4" \ - # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases -class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Inconsistent metaclass structure for "ChildOfCorrectButWrongMeta" \ - # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases ... [case testGenericOverride] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 6a9b060e9f07..8f401ed9016c 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2968,7 +2968,6 @@ class M(type): pass [out] == -a.py:3: error: Inconsistent metaclass structure for "D" [case testFineMetaclassDeclaredUpdate] import a @@ -2984,7 +2983,6 @@ class M(type): pass class M2(type): pass [out] == -a.py:3: error: Inconsistent metaclass structure for "D" [case testFineMetaclassRemoveFromClass] import a From ff21a17683804b8035ee9d9d85aa393fa2d4a671 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 15:50:11 +0300 Subject: [PATCH 04/15] Fix crash --- mypy/semanal.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index baebe3c6a25f..0054d11ba75e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2047,6 +2047,7 @@ def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool: return False def analyze_metaclass(self, defn: ClassDef) -> None: + # We check metaclass conflicts in `checker.py` if defn.metaclass: metaclass_name = None if isinstance(defn.metaclass, NameExpr): @@ -2103,13 +2104,11 @@ def analyze_metaclass(self, defn: ClassDef) -> None: # TODO: add a metaclass conflict check if there is another metaclass. abc_meta = self.named_type_or_none("abc.ABCMeta", []) if abc_meta is not None: # May be None in tests with incomplete lib-stub. - # We check metaclass correctness in `checker.py` defn.info.metaclass_type = abc_meta - else: - 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) + if defn.info.metaclass_type and 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) # # Imports From 6ad5e12eac7e9468051d953eafda38055d9806aa Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 16:21:12 +0300 Subject: [PATCH 05/15] Fix tests --- test-data/unit/check-classes.test | 8 ++++---- test-data/unit/fine-grained.test | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 434aa2a6e052..06ea64139ca1 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4351,7 +4351,7 @@ class C(B): class X(type): pass class Y(type): pass class A(metaclass=X): pass -class B(A, metaclass=Y): pass +class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [case testMetaclassNoTypeReveal] class M: @@ -5213,8 +5213,8 @@ class CD(six.with_metaclass(M)): pass # E: Multiple metaclass definitions class M1(type): pass class Q1(metaclass=M1): pass @six.add_metaclass(M) -class CQA(Q1): pass -class CQW(six.with_metaclass(M, Q1)): pass +class CQA(Q1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class CQW(six.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [builtins fixtures/tuple.pyi] [case testSixMetaclassAny] @@ -5319,7 +5319,7 @@ class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not sup class M1(type): pass class Q1(metaclass=M1): pass -class CQW(future.utils.with_metaclass(M, Q1)): pass +class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [builtins fixtures/tuple.pyi] [case testFutureMetaclassAny] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 8f401ed9016c..e9aa94b5026f 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2968,6 +2968,7 @@ class M(type): pass [out] == +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [case testFineMetaclassDeclaredUpdate] import a @@ -2983,6 +2984,7 @@ class M(type): pass class M2(type): pass [out] == +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [case testFineMetaclassRemoveFromClass] import a From 0a8d425420ae958da4f11539a648707e9025b737 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Fri, 2 Sep 2022 16:36:40 -0700 Subject: [PATCH 06/15] Fix spurious unreachable and disallow-any errors from deferred passes (#13575) This diff: - Fixes #8129 - Fixes #13043 - Fixes #13167 For more concise repros of these various issues, see the modified test files. But in short, there were two broad categories of errors: 1. Within the deferred pass, we tend to infer 'Any' for the types of different variables instead of the actual type. This interacts badly with our unreachable and disallow-any checks and causes spurious errors. Arguably, the better way of handling this error is to only collect errors during the final pass. I briefly experimented with this approach, but was unable to find a clean, efficient, and non-disruptive way of implementing this. So, I settled for sprinkling in a few more `not self.current_node_deferred` checks. 2. The `self.msg.disallowed_any_type(...)` call is normally guarded behind a `not self.chk.current_node_deferred` check. However, we were bypassing this check for `except` block assignments because we were deliberately setting that flag to False to work around some bug. For more context, see #2290. It appears we no longer need this patch anymore. I'm not entirely sure why, but I'm guessing we tightened and fixed the underlying problem with deferred passes some time during the past half-decade. --- mypy/checker.py | 12 ++++-------- test-data/unit/check-flags.test | 16 ++++++++++++++++ test-data/unit/check-statements.test | 12 ++++++++++++ test-data/unit/check-unreachable-code.test | 17 +++++++++++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1f4cb8bc7b3a..dbd1adfb42e3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1203,7 +1203,9 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str | None) -> return_type = get_proper_type(return_type) if self.options.warn_no_return: - if not isinstance(return_type, (NoneType, AnyType)): + if not self.current_node_deferred and not isinstance( + return_type, (NoneType, AnyType) + ): # Control flow fell off the end of a function that was # declared to return a non-None type and is not # entirely pass/Ellipsis/raise NotImplementedError. @@ -2431,6 +2433,7 @@ def should_report_unreachable_issues(self) -> bool: return ( self.in_checked_function() and self.options.warn_unreachable + and not self.current_node_deferred and not self.binder.is_unreachable_warning_suppressed() ) @@ -4179,14 +4182,7 @@ def visit_try_without_finally(self, s: TryStmt, try_frame: bool) -> None: # To support local variables, we make this a definition line, # causing assignment to set the variable's type. var.is_inferred_def = True - # We also temporarily set current_node_deferred to False to - # make sure the inference happens. - # TODO: Use a better solution, e.g. a - # separate Var for each except block. - am_deferring = self.current_node_deferred - self.current_node_deferred = False self.check_assignment(var, self.temp_node(t, var)) - self.current_node_deferred = am_deferring self.accept(s.handlers[i]) var = s.vars[i] if var: diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 11229465eac4..9fdd5ea2232c 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -366,6 +366,22 @@ def f() -> NoReturn: # E: Implicit return in function which does not return non_trivial_function = 1 [builtins fixtures/dict.pyi] +[case testNoReturnImplicitReturnCheckInDeferredNode] +# flags: --warn-no-return +from typing import NoReturn + +def exit() -> NoReturn: ... + +def force_forward_reference() -> int: + return 4 + +def f() -> NoReturn: + x + exit() + +x = force_forward_reference() +[builtins fixtures/exception.pyi] + [case testNoReturnNoWarnNoReturn] # flags: --warn-no-return from mypy_extensions import NoReturn diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index c26e9672056b..9b571cb20c0d 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -945,6 +945,18 @@ x = f() main:10: note: Revealed type is "builtins.int" main:15: note: Revealed type is "builtins.str" +[case testExceptionVariableWithDisallowAnyExprInDeferredNode] +# flags: --disallow-any-expr +def f() -> int: + x + try: + pass + except Exception as ex: + pass + return 0 +x = f() +[builtins fixtures/exception.pyi] + [case testArbitraryExpressionAsExceptionType] import typing a = BaseException diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 289d042d8790..64736e55e2dd 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1397,3 +1397,20 @@ a or a # E: Right operand of "or" is never evaluated 1 and a and 1 # E: Right operand of "and" is never evaluated a and a # E: Right operand of "and" is never evaluated [builtins fixtures/exception.pyi] + +[case testUnreachableFlagWithTerminalBranchInDeferredNode] +# flags: --warn-unreachable +from typing import NoReturn + +def assert_never(x: NoReturn) -> NoReturn: ... + +def force_forward_ref() -> int: + return 4 + +def f(value: None) -> None: + x + if value is not None: + assert_never(value) + +x = force_forward_ref() +[builtins fixtures/exception.pyi] From fd2d68435bfb1e7e250c435e5f782d8325523614 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 3 Sep 2022 12:20:28 +0300 Subject: [PATCH 07/15] Treat `ABCMeta` subtypes as abstract metaclasses (#13562) Closes #13561 Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- mypy/semanal_classprop.py | 2 +- test-data/unit/check-classes.test | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mypy/semanal_classprop.py b/mypy/semanal_classprop.py index 654a29c38d08..88265565c58e 100644 --- a/mypy/semanal_classprop.py +++ b/mypy/semanal_classprop.py @@ -95,7 +95,7 @@ def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: E # implement some methods. typ.abstract_attributes = sorted(abstract) if is_stub_file: - if typ.declared_metaclass and typ.declared_metaclass.type.fullname == "abc.ABCMeta": + if typ.declared_metaclass and typ.declared_metaclass.type.has_base("abc.ABCMeta"): return if typ.is_protocol: return diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 55f368979158..56253db7f053 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5497,6 +5497,13 @@ class E(Protocol): # OK, is a protocol class F(E, Protocol): # OK, is a protocol pass +# Custom metaclass subclassing `ABCMeta`, see #13561 +class CustomMeta(ABCMeta): + pass + +class G(A, metaclass=CustomMeta): # Ok, has CustomMeta as a metaclass + pass + [file b.py] # All of these are OK because this is not a stub file. from abc import ABCMeta, abstractmethod @@ -5525,6 +5532,12 @@ class E(Protocol): class F(E, Protocol): pass +class CustomMeta(ABCMeta): + pass + +class G(A, metaclass=CustomMeta): + pass + [case testClassMethodOverride] from typing import Callable, Any From dfbaff74f1d05c2597f48105b3c0cf974066c1fa Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 3 Sep 2022 20:33:33 +0300 Subject: [PATCH 08/15] Defer all types whos metaclass is not ready (#13579) --- mypy/semanal.py | 76 +++++++++++++++++++------------ test-data/unit/check-classes.test | 42 +++++++++++++++++ test-data/unit/check-modules.test | 14 ++++++ 3 files changed, 103 insertions(+), 29 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 65b883793907..757632e43f38 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -606,14 +606,18 @@ def add_implicit_module_attrs(self, file_node: MypyFile) -> None: if not sym: continue node = sym.node - assert isinstance(node, TypeInfo) + if not isinstance(node, TypeInfo): + self.defer(node) + return typ = Instance(node, [self.str_type()]) elif name == "__annotations__": sym = self.lookup_qualified("__builtins__.dict", Context(), suppress_errors=True) if not sym: continue node = sym.node - assert isinstance(node, TypeInfo) + if not isinstance(node, TypeInfo): + self.defer(node) + return typ = Instance(node, [self.str_type(), AnyType(TypeOfAny.special_form)]) else: assert t is not None, f"type should be specified for {name}" @@ -1374,7 +1378,7 @@ def analyze_class(self, defn: ClassDef) -> None: defn.base_type_exprs.extend(defn.removed_base_type_exprs) defn.removed_base_type_exprs.clear() - self.update_metaclass(defn) + self.infer_metaclass_and_bases_from_compat_helpers(defn) bases = defn.base_type_exprs bases, tvar_defs, is_protocol = self.clean_up_bases_and_infer_type_variables( @@ -1390,20 +1394,25 @@ def analyze_class(self, defn: ClassDef) -> None: self.defer() self.analyze_class_keywords(defn) - result = self.analyze_base_classes(bases) - - if result is None or self.found_incomplete_ref(tag): + bases_result = self.analyze_base_classes(bases) + if bases_result is None or self.found_incomplete_ref(tag): # Something was incomplete. Defer current target. self.mark_incomplete(defn.name, defn) return - base_types, base_error = result + base_types, base_error = bases_result if any(isinstance(base, PlaceholderType) for base, _ in base_types): # We need to know the TypeInfo of each base to construct the MRO. Placeholder types # are okay in nested positions, since they can't affect the MRO. self.mark_incomplete(defn.name, defn) return + declared_metaclass, should_defer = self.get_declared_metaclass(defn.name, defn.metaclass) + if should_defer or self.found_incomplete_ref(tag): + # Metaclass was not ready. Defer current target. + self.mark_incomplete(defn.name, defn) + return + if self.analyze_typeddict_classdef(defn): if defn.info: self.setup_type_vars(defn, tvar_defs) @@ -1422,7 +1431,7 @@ def analyze_class(self, defn: ClassDef) -> None: with self.scope.class_scope(defn.info): self.configure_base_classes(defn, base_types) defn.info.is_protocol = is_protocol - self.analyze_metaclass(defn) + self.recalculate_metaclass(defn, declared_metaclass) defn.info.runtime_protocol = False for decorator in defn.decorators: self.analyze_class_decorator(defn, decorator) @@ -1968,7 +1977,7 @@ def calculate_class_mro( if hook: hook(ClassDefContext(defn, FakeExpression(), self)) - def update_metaclass(self, defn: ClassDef) -> None: + def infer_metaclass_and_bases_from_compat_helpers(self, defn: ClassDef) -> None: """Lookup for special metaclass declarations, and update defn fields accordingly. * six.with_metaclass(M, B1, B2, ...) @@ -2046,30 +2055,33 @@ def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool: visited.add(base.type) return False - def analyze_metaclass(self, defn: ClassDef) -> None: - if defn.metaclass: + def get_declared_metaclass( + self, name: str, metaclass_expr: Expression | None + ) -> tuple[Instance | None, bool]: + """Returns either metaclass instance or boolean whether we should defer.""" + declared_metaclass = None + if metaclass_expr: metaclass_name = None - if isinstance(defn.metaclass, NameExpr): - metaclass_name = defn.metaclass.name - elif isinstance(defn.metaclass, MemberExpr): - metaclass_name = get_member_expr_fullname(defn.metaclass) + if isinstance(metaclass_expr, NameExpr): + metaclass_name = metaclass_expr.name + elif isinstance(metaclass_expr, MemberExpr): + metaclass_name = get_member_expr_fullname(metaclass_expr) if metaclass_name is None: - self.fail(f'Dynamic metaclass not supported for "{defn.name}"', defn.metaclass) - return - sym = self.lookup_qualified(metaclass_name, defn.metaclass) + self.fail(f'Dynamic metaclass not supported for "{name}"', metaclass_expr) + return None, False + sym = self.lookup_qualified(metaclass_name, metaclass_expr) if sym is None: # Probably a name error - it is already handled elsewhere - return + return None, False if isinstance(sym.node, Var) and isinstance(get_proper_type(sym.node.type), AnyType): # 'Any' metaclass -- just ignore it. # # TODO: A better approach would be to record this information # and assume that the type object supports arbitrary # attributes, similar to an 'Any' base class. - return + return None, False if isinstance(sym.node, PlaceholderNode): - self.defer(defn) - return + return None, True # defer later in the caller # Support type aliases, like `_Meta: TypeAlias = type` if ( @@ -2083,16 +2095,20 @@ def analyze_metaclass(self, defn: ClassDef) -> None: metaclass_info = sym.node if not isinstance(metaclass_info, TypeInfo) or metaclass_info.tuple_type is not None: - self.fail(f'Invalid metaclass "{metaclass_name}"', defn.metaclass) - return + self.fail(f'Invalid metaclass "{metaclass_name}"', metaclass_expr) + return None, False if not metaclass_info.is_metaclass(): self.fail( - 'Metaclasses not inheriting from "type" are not supported', defn.metaclass + 'Metaclasses not inheriting from "type" are not supported', metaclass_expr ) - return + return None, False inst = fill_typevars(metaclass_info) assert isinstance(inst, Instance) - defn.info.declared_metaclass = inst + declared_metaclass = inst + return declared_metaclass, False + + def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | None) -> None: + defn.info.declared_metaclass = declared_metaclass defn.info.metaclass_type = defn.info.calculate_metaclass_type() if any(info.is_protocol for info in defn.info.mro): if ( @@ -2104,13 +2120,15 @@ def analyze_metaclass(self, defn: ClassDef) -> None: abc_meta = self.named_type_or_none("abc.ABCMeta", []) if abc_meta is not None: # May be None in tests with incomplete lib-stub. defn.info.metaclass_type = abc_meta - if defn.info.metaclass_type is None: + if declared_metaclass is not None and defn.info.metaclass_type is None: # Inconsistency may happen due to multiple baseclasses even in classes that # do not declare explicit metaclass, but it's harder to catch at this stage if defn.metaclass is not None: self.fail(f'Inconsistent metaclass structure for "{defn.name}"', defn) else: - if defn.info.metaclass_type.type.has_base("enum.EnumMeta"): + if defn.info.metaclass_type and 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) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 56253db7f053..9bf2bbd839ed 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6676,6 +6676,48 @@ class MyMetaClass(type): class MyClass(metaclass=MyMetaClass): pass + +[case testMetaclassPlaceholderNode] +from sympy.assumptions import ManagedProperties +from sympy.ops import AssocOp +reveal_type(AssocOp.x) # N: Revealed type is "sympy.basic.Basic" +reveal_type(AssocOp.y) # N: Revealed type is "builtins.int" + +[file sympy/__init__.py] + +[file sympy/assumptions.py] +from .basic import Basic +class ManagedProperties(type): + x: Basic + y: int +# The problem is with the next line, +# it creates the following order (classname, metaclass): +# 1. Basic NameExpr(ManagedProperties) +# 2. AssocOp None +# 3. ManagedProperties None +# 4. Basic NameExpr(ManagedProperties [sympy.assumptions.ManagedProperties]) +# So, `AssocOp` will still have `metaclass_type` as `None` +# and all its `mro` types will have `declared_metaclass` as `None`. +from sympy.ops import AssocOp + +[file sympy/basic.py] +from .assumptions import ManagedProperties +class Basic(metaclass=ManagedProperties): ... + +[file sympy/ops.py] +from sympy.basic import Basic +class AssocOp(Basic): ... + +[case testMetaclassSubclassSelf] +# This does not make much sense, but we must not crash: +import a +[file m.py] +from a import A # E: Module "a" has no attribute "A" +class Meta(A): pass +[file a.py] +from m import Meta +class A(metaclass=Meta): pass + [case testGenericOverride] from typing import Generic, TypeVar, Any diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index d83d0470c6b0..03f3105c5be3 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2904,6 +2904,20 @@ from . import m as m [file p/m.py] [builtins fixtures/list.pyi] +[case testSpecialModulesNameImplicitAttr] +import typing +import builtins +import abc + +reveal_type(abc.__name__) # N: Revealed type is "builtins.str" +reveal_type(builtins.__name__) # N: Revealed type is "builtins.str" +reveal_type(typing.__name__) # N: Revealed type is "builtins.str" + +[case testSpecialAttrsAreAvaliableInClasses] +class Some: + name = __name__ +reveal_type(Some.name) # N: Revealed type is "builtins.str" + [case testReExportAllInStub] from m1 import C from m1 import D # E: Module "m1" has no attribute "D" From 2a7d96d5d2225d54629a557e7ec520ecfe63f043 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 13:51:46 +0300 Subject: [PATCH 09/15] Detect metaclass conflicts, refs #13563 --- ex.py | 2 ++ ex.pyi | 2 ++ mypy/checker.py | 21 ++++++++++++++++++ test-data/unit/check-classes.test | 36 ++++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 ex.py create mode 100644 ex.pyi diff --git a/ex.py b/ex.py new file mode 100644 index 000000000000..2579e566b46a --- /dev/null +++ b/ex.py @@ -0,0 +1,2 @@ +class A: + x = 1 diff --git a/ex.pyi b/ex.pyi new file mode 100644 index 000000000000..0e5eee6517f9 --- /dev/null +++ b/ex.pyi @@ -0,0 +1,2 @@ +class A: + x: int diff --git a/mypy/checker.py b/mypy/checker.py index dbd1adfb42e3..5bd6227e2929 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2044,6 +2044,7 @@ def visit_class_def(self, defn: ClassDef) -> None: if not defn.has_incompatible_baseclass: # Otherwise we've already found errors; more errors are not useful self.check_multiple_inheritance(typ) + self.check_metaclass_compatibility(typ) self.check_final_deletable(typ) if defn.decorators: @@ -2383,6 +2384,26 @@ class C(B, A[int]): ... # this is unsafe because... if not ok: self.msg.base_class_definitions_incompatible(name, base1, base2, ctx) + def check_metaclass_compatibility(self, typ: TypeInfo) -> None: + """Ensures that metaclasses of all parent types are compatible.""" + metaclasses = [ + entry.metaclass_type + for entry in typ.mro[1:-1] + if entry.metaclass_type + and not is_named_instance(entry.metaclass_type, "builtins.type") + ] + if not metaclasses: + return + if typ.metaclass_type is not None and all( + is_subtype(typ.metaclass_type, meta) for meta in metaclasses + ): + return + self.fail( + "Metaclass conflict: the metaclass of a derived class must be " + "a (non-strict) subclass of the metaclasses of all its bases", + typ, + ) + def visit_import_from(self, node: ImportFrom) -> None: self.check_import(node) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 9bf2bbd839ed..574213a7e1f3 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6676,7 +6676,6 @@ class MyMetaClass(type): class MyClass(metaclass=MyMetaClass): pass - [case testMetaclassPlaceholderNode] from sympy.assumptions import ManagedProperties from sympy.ops import AssocOp @@ -6718,6 +6717,41 @@ class Meta(A): pass from m import Meta class A(metaclass=Meta): pass +[case testMetaclassConflict] +class MyMeta1(type): ... +class MyMeta2(type): ... +class MyMeta3(type): ... +class A(metaclass=MyMeta1): ... +class B(metaclass=MyMeta2): ... +class C(metaclass=type): ... +class A1(A): ... +class E: ... + +class CorrectMeta(MyMeta1, MyMeta2): ... +class CorrectSubclass1(A1, B, E, metaclass=CorrectMeta): ... +class CorrectSubclass2(A, B, E, metaclass=CorrectMeta): ... +class CorrectSubclass3(B, A, metaclass=CorrectMeta): ... + +class ChildOfCorrectSubclass1(CorrectSubclass1): ... + +class CorrectWithType1(C, A1): ... +class CorrectWithType2(B, C): ... + +class Conflict1(A1, B, E): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict2(A, B): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict3(B, A): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + +class ChildOfConflict1(Conflict3): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class ChildOfConflict2(Conflict3, metaclass=CorrectMeta): ... + +class ConflictingMeta(MyMeta1, MyMeta3): ... +class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Inconsistent metaclass structure for "Conflict4" \ + # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + +class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Inconsistent metaclass structure for "ChildOfCorrectButWrongMeta" \ + # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + ... + [case testGenericOverride] from typing import Generic, TypeVar, Any From 26bf5d0097a2f7a8b267b535ad1caf38258cad48 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 14:07:43 +0300 Subject: [PATCH 10/15] Add known exceptions --- mypy/checker.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 5bd6227e2929..a46007c7f1e3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2386,6 +2386,14 @@ class C(B, A[int]): ... # this is unsafe because... def check_metaclass_compatibility(self, typ: TypeInfo) -> None: """Ensures that metaclasses of all parent types are compatible.""" + if ( + typ.is_metaclass() + or typ.is_protocol + or typ.is_named_tuple + or typ.typeddict_type is not None + ): + return # Reasonable exceptions from this check + metaclasses = [ entry.metaclass_type for entry in typ.mro[1:-1] From 456acc568ed1045d1df350b429c71ec6e0db91e3 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 14:51:49 +0300 Subject: [PATCH 11/15] Remove old metaclass error in semanal.py --- docs/source/metaclasses.rst | 10 +++++++--- mypy/checker.py | 1 + mypy/semanal.py | 19 +++++++------------ test-data/unit/check-classes.test | 14 ++++++-------- test-data/unit/fine-grained.test | 2 -- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/docs/source/metaclasses.rst b/docs/source/metaclasses.rst index a5d16aa722fd..43efdd8d5fd4 100644 --- a/docs/source/metaclasses.rst +++ b/docs/source/metaclasses.rst @@ -72,12 +72,16 @@ so it's better not to combine metaclasses and class hierarchies: class A1(metaclass=M1): pass class A2(metaclass=M2): pass - class B1(A1, metaclass=M2): pass # Mypy Error: Inconsistent metaclass structure for "B1" + class B1(A1, metaclass=M2): pass # Mypy Error: metaclass conflict "B1" # At runtime the above definition raises an exception # TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - # Same runtime error as in B1, but mypy does not catch it yet - class B12(A1, A2): pass + class B12(A1, A2): pass # Mypy Error: metaclass conflict "B12" + + # This can be solved via a common metaclass subtype: + class CorrectMeta(M1, M2): pass + class B2(A1, A2, metaclass=CorrectMeta): pass # OK, runtime is also OK + * Mypy does not understand dynamically-computed metaclasses, such as ``class A(metaclass=f()): ...`` diff --git a/mypy/checker.py b/mypy/checker.py index a46007c7f1e3..f34ce7b5103e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2390,6 +2390,7 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None: typ.is_metaclass() or typ.is_protocol or typ.is_named_tuple + or typ.is_enum or typ.typeddict_type is not None ): return # Reasonable exceptions from this check diff --git a/mypy/semanal.py b/mypy/semanal.py index 757632e43f38..ce59038a7901 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2119,19 +2119,14 @@ def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | N # TODO: add a metaclass conflict check if there is another metaclass. abc_meta = self.named_type_or_none("abc.ABCMeta", []) if abc_meta is not None: # May be None in tests with incomplete lib-stub. + # We check metaclass correctness in `checker.py` defn.info.metaclass_type = abc_meta - if declared_metaclass is not None and defn.info.metaclass_type is None: - # Inconsistency may happen due to multiple baseclasses even in classes that - # do not declare explicit metaclass, but it's harder to catch at this stage - if defn.metaclass is not None: - self.fail(f'Inconsistent metaclass structure for "{defn.name}"', defn) - else: - if defn.info.metaclass_type and 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) + elif defn.info.metaclass_type and 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) # # Imports diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 574213a7e1f3..e45acedaad1e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4351,7 +4351,7 @@ class C(B): class X(type): pass class Y(type): pass class A(metaclass=X): pass -class B(A, metaclass=Y): pass # E: Inconsistent metaclass structure for "B" +class B(A, metaclass=Y): pass [case testMetaclassNoTypeReveal] class M: @@ -5213,8 +5213,8 @@ class CD(six.with_metaclass(M)): pass # E: Multiple metaclass definitions class M1(type): pass class Q1(metaclass=M1): pass @six.add_metaclass(M) -class CQA(Q1): pass # E: Inconsistent metaclass structure for "CQA" -class CQW(six.with_metaclass(M, Q1)): pass # E: Inconsistent metaclass structure for "CQW" +class CQA(Q1): pass +class CQW(six.with_metaclass(M, Q1)): pass [builtins fixtures/tuple.pyi] [case testSixMetaclassAny] @@ -5319,7 +5319,7 @@ class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not sup class M1(type): pass class Q1(metaclass=M1): pass -class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Inconsistent metaclass structure for "CQW" +class CQW(future.utils.with_metaclass(M, Q1)): pass [builtins fixtures/tuple.pyi] [case testFutureMetaclassAny] @@ -6745,11 +6745,9 @@ class ChildOfConflict1(Conflict3): ... # E: Metaclass conflict: the metaclass o class ChildOfConflict2(Conflict3, metaclass=CorrectMeta): ... class ConflictingMeta(MyMeta1, MyMeta3): ... -class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Inconsistent metaclass structure for "Conflict4" \ - # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases -class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Inconsistent metaclass structure for "ChildOfCorrectButWrongMeta" \ - # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases ... [case testGenericOverride] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 8e07deb8cd87..faf2ac6f1c51 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2968,7 +2968,6 @@ class M(type): pass [out] == -a.py:3: error: Inconsistent metaclass structure for "D" [case testFineMetaclassDeclaredUpdate] import a @@ -2984,7 +2983,6 @@ class M(type): pass class M2(type): pass [out] == -a.py:3: error: Inconsistent metaclass structure for "D" [case testFineMetaclassRemoveFromClass] import a From 8bcc6a1072951b3c27a3631678be9f89c43c7109 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 15:50:11 +0300 Subject: [PATCH 12/15] Fix crash --- mypy/semanal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index ce59038a7901..e6796dde1fd6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2105,6 +2105,7 @@ def get_declared_metaclass( inst = fill_typevars(metaclass_info) assert isinstance(inst, Instance) declared_metaclass = inst + # We check metaclass conflicts in `checker.py` return declared_metaclass, False def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | None) -> None: @@ -2119,9 +2120,8 @@ def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | N # TODO: add a metaclass conflict check if there is another metaclass. abc_meta = self.named_type_or_none("abc.ABCMeta", []) if abc_meta is not None: # May be None in tests with incomplete lib-stub. - # We check metaclass correctness in `checker.py` defn.info.metaclass_type = abc_meta - elif defn.info.metaclass_type and defn.info.metaclass_type.type.has_base( + if defn.info.metaclass_type and defn.info.metaclass_type.type.has_base( "enum.EnumMeta" ): defn.info.is_enum = True From 6ce49512f809d8952cb0eed19cf11d77d6baa403 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Aug 2022 16:21:12 +0300 Subject: [PATCH 13/15] Fix tests --- test-data/unit/check-classes.test | 8 ++++---- test-data/unit/fine-grained.test | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e45acedaad1e..8622c191a304 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4351,7 +4351,7 @@ class C(B): class X(type): pass class Y(type): pass class A(metaclass=X): pass -class B(A, metaclass=Y): pass +class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [case testMetaclassNoTypeReveal] class M: @@ -5213,8 +5213,8 @@ class CD(six.with_metaclass(M)): pass # E: Multiple metaclass definitions class M1(type): pass class Q1(metaclass=M1): pass @six.add_metaclass(M) -class CQA(Q1): pass -class CQW(six.with_metaclass(M, Q1)): pass +class CQA(Q1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class CQW(six.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [builtins fixtures/tuple.pyi] [case testSixMetaclassAny] @@ -5319,7 +5319,7 @@ class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not sup class M1(type): pass class Q1(metaclass=M1): pass -class CQW(future.utils.with_metaclass(M, Q1)): pass +class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [builtins fixtures/tuple.pyi] [case testFutureMetaclassAny] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index faf2ac6f1c51..9d8857301425 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2968,6 +2968,7 @@ class M(type): pass [out] == +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [case testFineMetaclassDeclaredUpdate] import a @@ -2983,6 +2984,7 @@ class M(type): pass class M2(type): pass [out] == +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [case testFineMetaclassRemoveFromClass] import a From 7a1a4a7ade9e1e19c3dcfb88e2733c3baf292f95 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 3 Sep 2022 20:42:50 +0300 Subject: [PATCH 14/15] Merge --- test-data/unit/check-classes.test | 3 --- 1 file changed, 3 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 18c2b7c11cfa..8622c191a304 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6676,7 +6676,6 @@ class MyMetaClass(type): class MyClass(metaclass=MyMetaClass): pass -<<<<<<< HEAD [case testMetaclassPlaceholderNode] from sympy.assumptions import ManagedProperties from sympy.ops import AssocOp @@ -6718,8 +6717,6 @@ class Meta(A): pass from m import Meta class A(metaclass=Meta): pass -======= ->>>>>>> f56d15148ffcb15ee3d4b69fe170097951ee753d [case testMetaclassConflict] class MyMeta1(type): ... class MyMeta2(type): ... From 7b8f349a6ef7cbc59608d6e2e70a3abd6beb8d41 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 3 Sep 2022 20:50:17 +0300 Subject: [PATCH 15/15] Merge --- ex.py | 2 -- ex.pyi | 2 -- 2 files changed, 4 deletions(-) delete mode 100644 ex.py delete mode 100644 ex.pyi diff --git a/ex.py b/ex.py deleted file mode 100644 index 2579e566b46a..000000000000 --- a/ex.py +++ /dev/null @@ -1,2 +0,0 @@ -class A: - x = 1 diff --git a/ex.pyi b/ex.pyi deleted file mode 100644 index 0e5eee6517f9..000000000000 --- a/ex.pyi +++ /dev/null @@ -1,2 +0,0 @@ -class A: - x: int