From d2c25f8c3cfd6b0fe1c989db5ced837516d580cd Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Tue, 29 Sep 2020 16:53:05 -0700 Subject: [PATCH 1/3] semanal: [minor] move function --- mypy/semanal.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 6b3ab71daef04..6c2cfbc89361f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4419,6 +4419,13 @@ def add_redefinition(self, return i += 1 + def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], context: Context) -> None: + """Add local variable or function.""" + assert self.is_func_scope() + name = node.name + node._fullname = name + self.add_symbol(name, node, context) + def add_module_symbol(self, id: str, as_id: str, @@ -4436,13 +4443,6 @@ def add_module_symbol(self, as_id, context, target_name=id, module_public=module_public ) - def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], context: Context) -> None: - """Add local variable or function.""" - assert self.is_func_scope() - name = node.name - node._fullname = name - self.add_symbol(name, node, context) - def add_imported_symbol(self, name: str, node: SymbolTableNode, From 12419a7a067465b1d65f926c9d3c56398b3f7de0 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Tue, 29 Sep 2020 17:44:32 -0700 Subject: [PATCH 2/3] PEP 484: only reexport for X as X forms See recent discussion on typing-sig leading to a PEP 484 amendment. --- mypy/semanal.py | 55 +++++++++++++++---------------- mypy/test/teststubtest.py | 2 +- test-data/unit/check-modules.test | 2 ++ 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 6c2cfbc89361f..30ef70bbc08e0 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1708,18 +1708,19 @@ def analyze_metaclass(self, defn: ClassDef) -> None: def visit_import(self, i: Import) -> None: self.statement = i for id, as_id in i.ids: + # Modules imported in a stub file without using 'import X as X' won't get exported + # When implicit re-exporting is disabled, we have the same behavior as stubs. + use_implicit_reexport = not self.is_stub_file and self.options.implicit_reexport if as_id is not None: - self.add_module_symbol(id, as_id, module_public=True, context=i) + base_id = id + imported_id = as_id + module_public = use_implicit_reexport or id.split(".")[-1] == as_id else: - # Modules imported in a stub file without using 'as x' won't get exported - # When implicit re-exporting is disabled, we have the same behavior as stubs. - module_public = ( - not self.is_stub_file - and self.options.implicit_reexport - ) - base = id.split('.')[0] - self.add_module_symbol(base, base, module_public=module_public, - context=i, module_hidden=not module_public) + base_id = id.split('.')[0] + imported_id = base_id + module_public = use_implicit_reexport + self.add_module_symbol(base_id, imported_id, context=i, module_public=module_public, + module_hidden=not module_public) def visit_import_from(self, imp: ImportFrom) -> None: self.statement = imp @@ -1762,17 +1763,20 @@ def visit_import_from(self, imp: ImportFrom) -> None: if gvar: self.add_symbol(imported_id, gvar, imp) continue + + # Modules imported in a stub file without using 'from Y import X as X' won't get exported + # When implicit re-exporting is disabled, we have the same behavior as stubs. + use_implicit_reexport = not self.is_stub_file and self.options.implicit_reexport + module_public = use_implicit_reexport or (as_id is not None and id == as_id) + if node and not node.module_hidden: - self.process_imported_symbol(node, module_id, id, as_id, fullname, imp) + self.process_imported_symbol( + node, module_id, id, imported_id, fullname, module_public, context=imp + ) elif module and not missing_submodule: # Target module exists but the imported name is missing or hidden. self.report_missing_module_attribute(module_id, id, imported_id, imp) else: - module_public = ( - not self.is_stub_file - and self.options.implicit_reexport - or as_id is not None - ) # Import of a missing (sub)module. self.add_unknown_imported_symbol( imported_id, imp, target_name=fullname, module_public=module_public @@ -1782,17 +1786,10 @@ def process_imported_symbol(self, node: SymbolTableNode, module_id: str, id: str, - as_id: Optional[str], + imported_id: str, fullname: str, + module_public: bool, context: ImportBase) -> None: - imported_id = as_id or id - # 'from m import x as x' exports x in a stub file or when implicit - # re-exports are disabled. - module_public = ( - not self.is_stub_file - and self.options.implicit_reexport - or as_id is not None - ) module_hidden = not module_public and fullname not in self.modules if isinstance(node.node, PlaceholderNode): @@ -4429,9 +4426,9 @@ def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], context: Conte def add_module_symbol(self, id: str, as_id: str, - module_public: bool, context: Context, - module_hidden: bool = False) -> None: + module_public: bool, + module_hidden: bool) -> None: """Add symbol that is a reference to a module object.""" if id in self.modules: node = self.modules[id] @@ -4447,8 +4444,8 @@ def add_imported_symbol(self, name: str, node: SymbolTableNode, context: Context, - module_public: bool = True, - module_hidden: bool = False) -> None: + module_public: bool, + module_hidden: bool) -> None: """Add an alias to an existing symbol through import.""" assert not module_hidden or not module_public symbol = SymbolTableNode(node.kind, node.node, diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index ab6d6b87f6a83..050f4d688d975 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -578,7 +578,7 @@ def h(x: str): ... yield Case(stub="", runtime="__all__ += ['y']\ny = 5", error="y") yield Case(stub="", runtime="__all__ += ['g']\ndef g(): pass", error="g") # Here we should only check that runtime has B, since the stub explicitly re-exports it - yield Case(stub="from mystery import A, B as B # type: ignore", runtime="", error="B") + yield Case(stub="from mystery import A, B as B, C as D # type: ignore", runtime="", error="B") @collect_cases def test_name_mangling(self) -> Iterator[Case]: diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index b7f7c9c470361..c2d505c5cb89f 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1818,6 +1818,7 @@ m = n # E: Cannot assign multiple modules to name 'm' without explicit 'types.M [case testNoReExportFromStubs] from stub import Iterable # E: Module 'stub' has no attribute 'Iterable' +from stub import D # E: Module 'stub' has no attribute 'D' from stub import C c = C() @@ -1828,6 +1829,7 @@ reveal_type(it) # N: Revealed type is 'Any' [file stub.pyi] from typing import Iterable from substub import C as C +from substub import C as D def fun(x: Iterable[str]) -> Iterable[int]: pass From 2bd04ee9a85f48eed69c1833d416c02c93ba00f4 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Wed, 30 Sep 2020 23:00:55 -0700 Subject: [PATCH 3/3] fix flake8 --- mypy/semanal.py | 3 ++- mypy/test/teststubtest.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 30ef70bbc08e0..ffd7bda925c39 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1764,7 +1764,8 @@ def visit_import_from(self, imp: ImportFrom) -> None: self.add_symbol(imported_id, gvar, imp) continue - # Modules imported in a stub file without using 'from Y import X as X' won't get exported + # Modules imported in a stub file without using 'from Y import X as X' will + # not get exported. # When implicit re-exporting is disabled, we have the same behavior as stubs. use_implicit_reexport = not self.is_stub_file and self.options.implicit_reexport module_public = use_implicit_reexport or (as_id is not None and id == as_id) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 050f4d688d975..0577abc307e42 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -578,7 +578,9 @@ def h(x: str): ... yield Case(stub="", runtime="__all__ += ['y']\ny = 5", error="y") yield Case(stub="", runtime="__all__ += ['g']\ndef g(): pass", error="g") # Here we should only check that runtime has B, since the stub explicitly re-exports it - yield Case(stub="from mystery import A, B as B, C as D # type: ignore", runtime="", error="B") + yield Case( + stub="from mystery import A, B as B, C as D # type: ignore", runtime="", error="B" + ) @collect_cases def test_name_mangling(self) -> Iterator[Case]: