From 8fbd00ad53f310d602c14379e1af9aa3d3ffaefd Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 1 Sep 2022 13:02:51 +0300 Subject: [PATCH 01/13] Test that illustrates `sympy` metaclass problem --- test-data/unit/check-classes.test | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 55f368979158..b3dfb6fc1963 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6663,6 +6663,39 @@ 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 testGenericOverride] from typing import Generic, TypeVar, Any From 3b5186412e0dfd92e62466998a4359588302f7f3 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 1 Sep 2022 19:41:06 +0300 Subject: [PATCH 02/13] Progress! --- mypy/semanal.py | 58 +++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 65b883793907..1b3480b58dd1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1390,20 +1390,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 +1427,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) @@ -2046,30 +2051,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 # Support type aliases, like `_Meta: TypeAlias = type` if ( @@ -2083,16 +2091,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 ( From 276786fa6bc0fc4893fbb66d813a71d321a2739f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 15:06:20 +0300 Subject: [PATCH 03/13] Fix builtins cycle import --- mypy/semanal.py | 14 ++++++++------ test-data/unit/check-classes.test | 9 +++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 1b3480b58dd1..8d84f0d39399 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -581,7 +581,8 @@ def refresh_partial( def refresh_top_level(self, file_node: MypyFile) -> None: """Reanalyze a stale module top-level in fine-grained incremental mode.""" self.recurse_into_functions = False - self.add_implicit_module_attrs(file_node) + if not self.is_incomplete_namespace(file_node): + self.add_implicit_module_attrs(file_node) for d in file_node.defs: self.accept(d) if file_node.fullname == "typing": @@ -1374,7 +1375,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( @@ -1405,6 +1406,7 @@ def analyze_class(self, defn: ClassDef) -> None: declared_metaclass, should_defer = self.get_declared_metaclass(defn.name, defn.metaclass) if should_defer or self.found_incomplete_ref(tag): + print(defn.name, declared_metaclass, defn.metaclass) # Metaclass was not ready. Defer current target. self.mark_incomplete(defn.name, defn) return @@ -1973,7 +1975,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, ...) @@ -2077,7 +2079,7 @@ def get_declared_metaclass( # attributes, similar to an 'Any' base class. return None, False if isinstance(sym.node, PlaceholderNode): - return None, True # defer later + return None, True # defer later in the caller # Support type aliases, like `_Meta: TypeAlias = type` if ( @@ -2116,13 +2118,13 @@ def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | N 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 b3dfb6fc1963..ce7852a33ed9 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6695,6 +6695,15 @@ class Basic(metaclass=ManagedProperties): ... 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 From 4d24290520176e7fd65469ee59d142a3cf4d23b4 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 15:18:03 +0300 Subject: [PATCH 04/13] Fix ci --- mypy/semanal.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8d84f0d39399..d170a3ea8d38 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -581,8 +581,7 @@ def refresh_partial( def refresh_top_level(self, file_node: MypyFile) -> None: """Reanalyze a stale module top-level in fine-grained incremental mode.""" self.recurse_into_functions = False - if not self.is_incomplete_namespace(file_node): - self.add_implicit_module_attrs(file_node) + self.add_implicit_module_attrs(file_node) for d in file_node.defs: self.accept(d) if file_node.fullname == "typing": From b502458225d90c8ac4430907464193c4065c8250 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 15:25:32 +0300 Subject: [PATCH 05/13] Fix ci --- mypy/semanal.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index d170a3ea8d38..ef1eb852d3f9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2123,7 +2123,9 @@ def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | N 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"): + 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) From 914fc7048f5505be3a7b80c73bacd8c8b6e898f3 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 15:33:47 +0300 Subject: [PATCH 06/13] Fix ci --- mypy/semanal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index ef1eb852d3f9..79a80b7bc2eb 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1405,7 +1405,6 @@ def analyze_class(self, defn: ClassDef) -> None: declared_metaclass, should_defer = self.get_declared_metaclass(defn.name, defn.metaclass) if should_defer or self.found_incomplete_ref(tag): - print(defn.name, declared_metaclass, defn.metaclass) # Metaclass was not ready. Defer current target. self.mark_incomplete(defn.name, defn) return From d2b5bea0ebaef74d1385d37eb03718eef54f9cf7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 18:19:11 +0300 Subject: [PATCH 07/13] Fix CI --- mypy/semanal.py | 1 - mypy/semanal_main.py | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 79a80b7bc2eb..fe250e5ca479 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -581,7 +581,6 @@ def refresh_partial( def refresh_top_level(self, file_node: MypyFile) -> None: """Reanalyze a stale module top-level in fine-grained incremental mode.""" self.recurse_into_functions = False - self.add_implicit_module_attrs(file_node) for d in file_node.defs: self.accept(d) if file_node.fullname == "typing": diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index 406fd93139d1..cb45d095dc22 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -222,6 +222,12 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: worklist = list(reversed(all_deferred)) final_iteration = not any_progress + # We do it in the very last order, + # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. + if iteration <= MAX_ITERATIONS: + with analyzer.file_context(state.tree, state.options): + analyzer.add_implicit_module_attrs(state.tree) + def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None: # Process functions. From 9e2800e85a6101d84648391c014e08e9a4f8af5b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 18:40:06 +0300 Subject: [PATCH 08/13] Try this setup --- mypy/semanal_main.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index cb45d095dc22..60d6b0a01e0c 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -215,6 +215,10 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: any_progress = any_progress or progress if not incomplete: state.manager.incomplete_namespaces.discard(next_id) + # We do it in the very last order, + # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. + with analyzer.file_context(state.tree, state.options): + analyzer.add_implicit_module_attrs(state.tree) if final_iteration: assert not all_deferred, "Must not defer during final iteration" # Reverse to process the targets in the same order on every iteration. This avoids @@ -222,12 +226,6 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: worklist = list(reversed(all_deferred)) final_iteration = not any_progress - # We do it in the very last order, - # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. - if iteration <= MAX_ITERATIONS: - with analyzer.file_context(state.tree, state.options): - analyzer.add_implicit_module_attrs(state.tree) - def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None: # Process functions. From 9dc5b1c1d4b8c3a56161224c32da00596682c50f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 23:08:47 +0300 Subject: [PATCH 09/13] Address review --- mypy/semanal.py | 7 ++++++- mypy/semanal_main.py | 10 ++++++---- test-data/unit/check-modules.test | 9 +++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index fe250e5ca479..752f62b5b4ac 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -581,6 +581,11 @@ def refresh_partial( def refresh_top_level(self, file_node: MypyFile) -> None: """Reanalyze a stale module top-level in fine-grained incremental mode.""" self.recurse_into_functions = False + # We do it in the very last order, because of + # `builtins.dict <-> typing.Mapping <-> abc.ABCMeta` + # cyclic imports. + if file_node.fullname not in ('typing', 'abc', 'builtins'): + self.add_implicit_module_attrs(file_node) for d in file_node.defs: self.accept(d) if file_node.fullname == "typing": @@ -612,7 +617,7 @@ def add_implicit_module_attrs(self, file_node: MypyFile) -> None: if not sym: continue node = sym.node - assert isinstance(node, TypeInfo) + assert isinstance(node, TypeInfo), file_node.fullname typ = Instance(node, [self.str_type(), AnyType(TypeOfAny.special_form)]) else: assert t is not None, f"type should be specified for {name}" diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index 60d6b0a01e0c..7d6e03e14e0d 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -215,10 +215,6 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: any_progress = any_progress or progress if not incomplete: state.manager.incomplete_namespaces.discard(next_id) - # We do it in the very last order, - # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. - with analyzer.file_context(state.tree, state.options): - analyzer.add_implicit_module_attrs(state.tree) if final_iteration: assert not all_deferred, "Must not defer during final iteration" # Reverse to process the targets in the same order on every iteration. This avoids @@ -226,6 +222,12 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: worklist = list(reversed(all_deferred)) final_iteration = not any_progress + if state.tree.fullname in ('typing', 'builtins', 'abc'): + with analyzer.file_context(state.tree, state.options): + # We do it in the very last order, + # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. + analyzer.add_implicit_module_attrs(state.tree) + def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None: # Process functions. diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index d83d0470c6b0..40cb0a15705f 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2904,6 +2904,15 @@ 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 testReExportAllInStub] from m1 import C from m1 import D # E: Module "m1" has no attribute "D" From 50b5d491f0cfa96129ae8cdc7854a9888598ffb9 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 23:14:39 +0300 Subject: [PATCH 10/13] Fix ci --- mypy/semanal_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index 7d6e03e14e0d..28748f1b365c 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -222,7 +222,7 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: worklist = list(reversed(all_deferred)) final_iteration = not any_progress - if state.tree.fullname in ('typing', 'builtins', 'abc'): + if state.tree and state.tree.fullname in ('typing', 'builtins', 'abc'): with analyzer.file_context(state.tree, state.options): # We do it in the very last order, # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. From 7248fe151cd225d922b21cfecdfefc3d718ef966 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 23:46:43 +0300 Subject: [PATCH 11/13] Tests pass locally --- mypy/semanal.py | 4 ++-- mypy/semanal_main.py | 18 +++++++++++++----- test-data/unit/check-modules.test | 6 ++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 752f62b5b4ac..709b9aa6437f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -584,7 +584,7 @@ def refresh_top_level(self, file_node: MypyFile) -> None: # We do it in the very last order, because of # `builtins.dict <-> typing.Mapping <-> abc.ABCMeta` # cyclic imports. - if file_node.fullname not in ('typing', 'abc', 'builtins'): + if file_node.fullname not in ("typing", "abc", "builtins"): self.add_implicit_module_attrs(file_node) for d in file_node.defs: self.accept(d) @@ -617,7 +617,7 @@ def add_implicit_module_attrs(self, file_node: MypyFile) -> None: if not sym: continue node = sym.node - assert isinstance(node, TypeInfo), file_node.fullname + assert isinstance(node, TypeInfo) typ = Instance(node, [self.str_type(), AnyType(TypeOfAny.special_form)]) else: assert t is not None, f"type should be specified for {name}" diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index 28748f1b365c..97e53539d799 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -92,6 +92,10 @@ def semantic_analysis_for_scc(graph: Graph, scc: list[str], errors: Errors) -> N check_type_arguments(graph, scc, errors) calculate_class_properties(graph, scc, errors) check_blockers(graph, scc) + # Add some magic attrs to special modules: + for special_module in ("builtins", "typing", "abc"): + if special_module in scc: + process_special_implicit_attrs(graph[special_module]) # Clean-up builtins, so that TypeVar etc. are not accessible without importing. if "builtins" in scc: cleanup_builtin_scc(graph["builtins"]) @@ -222,11 +226,15 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: worklist = list(reversed(all_deferred)) final_iteration = not any_progress - if state.tree and state.tree.fullname in ('typing', 'builtins', 'abc'): - with analyzer.file_context(state.tree, state.options): - # We do it in the very last order, - # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. - analyzer.add_implicit_module_attrs(state.tree) + +def process_special_implicit_attrs(state: State) -> None: + # Add things like `__name__` and `__annotations__` to main modules. + analyzer = state.manager.semantic_analyzer + assert state.tree is not None + with analyzer.file_context(state.tree, state.options): + # We do it in the very last order, + # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. + analyzer.add_implicit_module_attrs(state.tree) def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None: diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 40cb0a15705f..76ba940bf748 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2913,6 +2913,12 @@ 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) + [case testReExportAllInStub] from m1 import C from m1 import D # E: Module "m1" has no attribute "D" From 57c5bfe55dc478c017f3a75e862473368a1a9f16 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 2 Sep 2022 23:47:29 +0300 Subject: [PATCH 12/13] Tests pass locally --- test-data/unit/check-modules.test | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 76ba940bf748..03f3105c5be3 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2916,8 +2916,7 @@ reveal_type(typing.__name__) # N: Revealed type is "builtins.str" [case testSpecialAttrsAreAvaliableInClasses] class Some: name = __name__ - -reveal_type(Some.name) +reveal_type(Some.name) # N: Revealed type is "builtins.str" [case testReExportAllInStub] from m1 import C From 47fa22adc611f6d9d850e2158d4e822b6b9a1669 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 3 Sep 2022 15:47:36 +0300 Subject: [PATCH 13/13] Try `defer` on incomplete implicit attrs --- mypy/semanal.py | 14 +++++++------- mypy/semanal_main.py | 14 -------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 709b9aa6437f..757632e43f38 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -581,11 +581,7 @@ def refresh_partial( def refresh_top_level(self, file_node: MypyFile) -> None: """Reanalyze a stale module top-level in fine-grained incremental mode.""" self.recurse_into_functions = False - # We do it in the very last order, because of - # `builtins.dict <-> typing.Mapping <-> abc.ABCMeta` - # cyclic imports. - if file_node.fullname not in ("typing", "abc", "builtins"): - self.add_implicit_module_attrs(file_node) + self.add_implicit_module_attrs(file_node) for d in file_node.defs: self.accept(d) if file_node.fullname == "typing": @@ -610,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}" diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index 97e53539d799..406fd93139d1 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -92,10 +92,6 @@ def semantic_analysis_for_scc(graph: Graph, scc: list[str], errors: Errors) -> N check_type_arguments(graph, scc, errors) calculate_class_properties(graph, scc, errors) check_blockers(graph, scc) - # Add some magic attrs to special modules: - for special_module in ("builtins", "typing", "abc"): - if special_module in scc: - process_special_implicit_attrs(graph[special_module]) # Clean-up builtins, so that TypeVar etc. are not accessible without importing. if "builtins" in scc: cleanup_builtin_scc(graph["builtins"]) @@ -227,16 +223,6 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None: final_iteration = not any_progress -def process_special_implicit_attrs(state: State) -> None: - # Add things like `__name__` and `__annotations__` to main modules. - analyzer = state.manager.semantic_analyzer - assert state.tree is not None - with analyzer.file_context(state.tree, state.options): - # We do it in the very last order, - # because of `dict <-> Mapping <-> ABCMeta` cyclic imports. - analyzer.add_implicit_module_attrs(state.tree) - - def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None: # Process functions. for module in scc: