From 4e6a56a4cb20b1d52696ec8f05d6a8328cc420b1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 4 Apr 2019 17:49:35 +0100 Subject: [PATCH 1/2] New semantic analyzer: allow replacing submodule with a variable --- mypy/newsemanal/semanal.py | 5 +++++ test-data/unit/check-fastparse.test | 4 ++-- test-data/unit/check-newsemanal.test | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index a8f4b9d205c6c..87e8b147e311c 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -1597,6 +1597,11 @@ def visit_import_from(self, imp: ImportFrom) -> None: if self.process_import_over_existing_name( imported_id, existing_symbol, node, imp): continue + if (existing_symbol and isinstance(existing_symbol.node, MypyFile) and + existing_symbol.no_serialize): # submodule added to parent module + # Special case: allow replacing submodules with variables. This pattern + # is used by some libraries. + del self.globals[imported_id] # 'from m import x as x' exports x in a stub file. module_public = not self.is_stub_file or as_id is not None module_hidden = not module_public and possible_module_id not in self.modules diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test index 4815f1f45d876..46e2fe6835cee 100644 --- a/test-data/unit/check-fastparse.test +++ b/test-data/unit/check-fastparse.test @@ -431,8 +431,8 @@ from . import command from . import backends [file blamodule/backends/__init__.py] -from .Bla import Bla # E: Name 'Bla' already defined (by an import) -Bla().method() # E: Module not callable +from .Bla import Bla +Bla().method() [file blamodule/backends/Bla.py] from .. import * diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 9ccb0c75e2ac8..09f28f08eb040 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2049,3 +2049,20 @@ from pp import x as y def __getattr__(attr): ... [out2] tmp/a.py:2: error: Revealed type is 'Any' + +[case testNewAnanlyzerTrickyImportPackage] +from lib import config +import lib + +reveal_type(lib.config.x) # E: Revealed type is 'builtins.int' +reveal_type(config.x) # E: Revealed type is 'builtins.int' + +[file lib/__init__.py] +from lib.config import config + +[file lib/config.py] +class Config: + x: int + +config = Config() +[builtins fixtures/module.pyi] From 48085dd53bcaae10e53823f48bcc073b88b2bd52 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 4 Apr 2019 19:03:55 +0100 Subject: [PATCH 2/2] More tests --- test-data/unit/check-newsemanal.test | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 09f28f08eb040..094c93a711e19 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2066,3 +2066,48 @@ class Config: config = Config() [builtins fixtures/module.pyi] + +[case testNewAnanlyzerTrickyImportPackageAlt] +import lib.config +import lib.config as tmp + +reveal_type(lib.config.x) # E: Revealed type is 'builtins.int' +# TODO: this actually doesn't match runtime behavior, variable wins. +tmp.x # E: Module has no attribute "x" + +[file lib/__init__.py] +from lib.config import config + +[file lib/config.py] +class Config: + x: int + +config = Config() +[builtins fixtures/module.pyi] + +[case testNewAnanlyzerTrickyImportPackage_incremental] +import a + +[file a.py] +from lib import config +import lib + +[file a.py.2] +from lib import config +import lib + +reveal_type(lib.config.x) +reveal_type(config.x) + +[file lib/__init__.py] +from lib.config import config + +[file lib/config.py] +class Config: + x: int + +config = Config() +[builtins fixtures/module.pyi] +[out2] +tmp/a.py:4: error: Revealed type is 'builtins.int' +tmp/a.py:5: error: Revealed type is 'builtins.int'