Skip to content

Make --ignore-missing-imports less strict #5228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 21 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
30 changes: 30 additions & 0 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
3 changes: 0 additions & 3 deletions test-data/unit/fine-grained-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down