Use error subcodes to differentiate import errors (#14740)
Resolves #9789
Users could use `--disable-error-code=import-untyped` to only ignore
errors about libraries not having stubs, but continue to get errors
about e.g. typos in an import name.
The error subcode mechanism is new from #14570. Note that users will now
get a different error code depending on whether or not a package is
installed, and may not know that they can use the parent error code to
ignore the issue regardless. I think this is okay, in general type
checking results can change if you run them in two different
environments. Note also that with `--warn-unused-ignore` / `--strict`
mypy will complain about not having the most specific error code
diff --git a/mypy/build.py b/mypy/build.py
index 5a0a481..eed5005 100644
--- a/mypy/build.py
+++ b/mypy/build.py
@@ -2780,7 +2780,16 @@
else:
daemon = manager.options.fine_grained_incremental
msg, notes = reason.error_message_templates(daemon)
- errors.report(line, 0, msg.format(module=target), code=codes.IMPORT)
+ if reason == ModuleNotFoundReason.NOT_FOUND:
+ code = codes.IMPORT_NOT_FOUND
+ elif (
+ reason == ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS
+ or reason == ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED
+ ):
+ code = codes.IMPORT_UNTYPED
+ else:
+ code = codes.IMPORT
+ errors.report(line, 0, msg.format(module=target), code=code)
top_level, second_level = get_top_two_prefixes(target)
if second_level in legacy_bundled_packages or second_level in non_bundled_packages:
top_level = second_level
diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py
index 717629a..e7d0c16 100644
--- a/mypy/errorcodes.py
+++ b/mypy/errorcodes.py
@@ -107,6 +107,12 @@
IMPORT: Final = ErrorCode(
"import", "Require that imported module can be found or has stubs", "General"
)
+IMPORT_NOT_FOUND: Final = ErrorCode(
+ "import-not-found", "Require that imported module can be found", "General", sub_code_of=IMPORT
+)
+IMPORT_UNTYPED: Final = ErrorCode(
+ "import-untyped", "Require that imported module has stubs", "General", sub_code_of=IMPORT
+)
NO_REDEF: Final = ErrorCode("no-redef", "Check that each name is defined once", "General")
FUNC_RETURNS_VALUE: Final = ErrorCode(
"func-returns-value", "Check that called function returns a value in value context", "General"
diff --git a/mypy/errors.py b/mypy/errors.py
index 2badac3..680b7f1 100644
--- a/mypy/errors.py
+++ b/mypy/errors.py
@@ -8,7 +8,7 @@
from typing_extensions import Literal, TypeAlias as _TypeAlias
from mypy import errorcodes as codes
-from mypy.errorcodes import IMPORT, ErrorCode
+from mypy.errorcodes import IMPORT, IMPORT_NOT_FOUND, IMPORT_UNTYPED, ErrorCode
from mypy.message_registry import ErrorMessage
from mypy.options import Options
from mypy.scope import Scope
@@ -510,7 +510,11 @@
if info.message in self.only_once_messages:
return
self.only_once_messages.add(info.message)
- if self.seen_import_error and info.code is not IMPORT and self.has_many_errors():
+ if (
+ self.seen_import_error
+ and info.code not in (IMPORT, IMPORT_UNTYPED, IMPORT_NOT_FOUND)
+ and self.has_many_errors()
+ ):
# Missing stubs can easily cause thousands of errors about
# Any types, especially when upgrading to mypy 0.900,
# which no longer bundles third-party library stubs. Avoid
diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test
index 1efbab7..796e1c1 100644
--- a/test-data/unit/check-errorcodes.test
+++ b/test-data/unit/check-errorcodes.test
@@ -183,7 +183,7 @@
[case testErrorCodeBadIgnore]
import nostub # type: ignore xyz # E: Invalid "type: ignore" comment [syntax] \
- # E: Cannot find implementation or library stub for module named "nostub" [import] \
+ # E: Cannot find implementation or library stub for module named "nostub" [import-not-found] \
# N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
import nostub # type: ignore[ # E: Invalid "type: ignore" comment [syntax]
import nostub # type: ignore[foo # E: Invalid "type: ignore" comment [syntax]
@@ -211,7 +211,7 @@
pass
[out]
main:2: error: Invalid "type: ignore" comment [syntax]
-main:2: error: Cannot find implementation or library stub for module named "nostub" [import]
+main:2: error: Cannot find implementation or library stub for module named "nostub" [import-not-found]
main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
main:3: error: Invalid "type: ignore" comment [syntax]
main:4: error: Invalid "type: ignore" comment [syntax]
@@ -522,12 +522,12 @@
[builtins fixtures/primitives.pyi]
[case testErrorCodeMissingModule]
-from defusedxml import xyz # E: Cannot find implementation or library stub for module named "defusedxml" [import]
-from nonexistent import foobar # E: Cannot find implementation or library stub for module named "nonexistent" [import]
-import nonexistent2 # E: Cannot find implementation or library stub for module named "nonexistent2" [import]
-from nonexistent3 import * # E: Cannot find implementation or library stub for module named "nonexistent3" [import]
+from defusedxml import xyz # E: Cannot find implementation or library stub for module named "defusedxml" [import-not-found]
+from nonexistent import foobar # E: Cannot find implementation or library stub for module named "nonexistent" [import-not-found]
+import nonexistent2 # E: Cannot find implementation or library stub for module named "nonexistent2" [import-not-found]
+from nonexistent3 import * # E: Cannot find implementation or library stub for module named "nonexistent3" [import-not-found]
from pkg import bad # E: Module "pkg" has no attribute "bad" [attr-defined]
-from pkg.bad2 import bad3 # E: Cannot find implementation or library stub for module named "pkg.bad2" [import] \
+from pkg.bad2 import bad3 # E: Cannot find implementation or library stub for module named "pkg.bad2" [import-not-found] \
# N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
[file pkg/__init__.py]