diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index d4dfad6c28ad6..9f074ee6e5f5e 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -297,6 +297,21 @@ Here are some more useful flags: - ``--ignore-missing-imports`` suppresses error messages about imports that cannot be resolved (see :ref:`follow-imports` for some examples). + This doesn't suppress errors about missing names in successfully resolved + modules. For example, if one has the following files:: + + package/__init__.py + package/mod.py + + Then mypy will generate the following errors with ``--ignore-missing-imports``: + + .. code-block:: python + + import package.unknown # No error, ignored + x = package.unknown.func() # OK + + from package import unknown # No error, ignored + from package.mod import NonExisting # Error: Module has no attribute 'NonExisting' - ``--no-strict-optional`` disables strict checking of ``Optional[...]`` types and ``None`` values. With this option, mypy doesn't diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 82dd7961a4cc4..dcd152328693b 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -201,7 +201,7 @@ overridden by the pattern sections matching the module name. strict Optional checks. If False, mypy treats ``None`` as compatible with every type. - **Note::** This was False by default + **Note:** This was False by default in mypy versions earlier than 0.600. - ``disallow_any_unimported`` (Boolean, default false) disallows usage of types that come diff --git a/mypy/semanal.py b/mypy/semanal.py index 9249547d35939..058fc36221d7a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1329,15 +1329,34 @@ def add_submodules_to_parent_modules(self, id: str, module_public: bool) -> None while '.' in id: parent, child = id.rsplit('.', 1) parent_mod = self.modules.get(parent) - if parent_mod and child not in parent_mod.names: + if parent_mod and self.allow_patching(parent_mod, child): child_mod = self.modules.get(id) if child_mod: sym = SymbolTableNode(MODULE_REF, child_mod, module_public=module_public, no_serialize=True) - parent_mod.names[child] = sym + else: + # Construct a dummy Var with Any type. + any_type = AnyType(TypeOfAny.from_unimported_type, + missing_import_name=id) + var = Var(child, any_type) + var._fullname = child + var.is_ready = True + var.is_suppressed_import = True + sym = SymbolTableNode(GDEF, var, + module_public=module_public, + no_serialize=True) + parent_mod.names[child] = sym id = parent + def allow_patching(self, parent_mod: MypyFile, child: str) -> bool: + if child not in parent_mod.names: + return True + node = parent_mod.names[child].node + if isinstance(node, Var) and node.is_suppressed_import: + return True + return False + def add_module_symbol(self, id: str, as_id: str, module_public: bool, context: Context, module_hidden: bool = False) -> None: if id in self.modules: diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 3e28bd5e9c6ed..a9c6fa0e75236 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2312,3 +2312,33 @@ def run() -> None: [out] tmp/p/b.py:4: error: Revealed type is 'builtins.int' tmp/p/__init__.py:3: error: Revealed type is 'builtins.int' + +[case testMissingSubmoduleImportedWithIgnoreMissingImports] +# flags: --ignore-missing-imports +import whatever.works +import a.b +x = whatever.works.f() +y = a.b.f() +[file a/__init__.py] +# empty +[out] + +[case testMissingSubmoduleImportedWithIgnoreMissingImportsStub] +# flags: --ignore-missing-imports --follow-imports=skip +import whatever.works +import a.b +x = whatever.works.f() +y = a.b.f() +[file a/__init__.pyi] +# empty +[out] + +[case testMissingSubmoduleImportedWithIgnoreMissingImportsNested] +# flags: --ignore-missing-imports +import a.b.c.d +y = a.b.c.d.f() +[file a/__init__.py] +# empty +[file a/b/__init__.py] +# empty +[out] diff --git a/test-data/unit/fine-grained-modules.test b/test-data/unit/fine-grained-modules.test index 3506c5636d586..1442a7807d4da 100644 --- a/test-data/unit/fine-grained-modules.test +++ b/test-data/unit/fine-grained-modules.test @@ -221,7 +221,6 @@ main:1: error: Cannot find module named 'p.a' == main:1: error: Cannot find module named 'p.a' main:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) -main:2: error: Module has no attribute "a" == main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str" [builtins fixtures/module.pyi] @@ -826,7 +825,6 @@ a.py:2: error: Too many arguments for "g" == a.py:1: error: Cannot find module named 'm.x' a.py:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) -a.py:2: error: Module has no attribute "x" [case testDeletePackage1] import p.a @@ -873,7 +871,6 @@ main:2: error: Argument 1 to "f" has incompatible type "int"; expected "str" == main:1: error: Cannot find module named 'p.a' main:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) -main:2: error: Module has no attribute "a" == main:1: error: Cannot find module named 'p' main:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help)