Skip to content

Commit 388be20

Browse files
committed
Add --almost-silent, which modifies --silent-imports to warn about silenced imports (#1371)
Also deprecated --silent (at least in --help output). This suppresses the line number for ancestor messages, which has one downside: in Emacs' grep/compile mode, you can't click on the line to go to the file. If people really hate that it's easy to change.
1 parent 27ab644 commit 388be20

File tree

3 files changed

+77
-9
lines changed

3 files changed

+77
-9
lines changed

mypy/build.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
DUMP_TYPE_STATS = 'dump-type-stats'
5858
DUMP_INFER_STATS = 'dump-infer-stats'
5959
SILENT_IMPORTS = 'silent-imports' # Silence imports of .py files
60+
ALMOST_SILENT = 'almost-silent' # If SILENT_IMPORTS: report silenced imports as errors
6061
INCREMENTAL = 'incremental' # Incremental mode: use the cache
6162
FAST_PARSER = 'fast-parser' # Use experimental fast parser
6263
# Disallow calling untyped functions from typed ones
@@ -941,7 +942,7 @@ def __init__(self,
941942
manager: BuildManager,
942943
caller_state: 'State' = None,
943944
caller_line: int = 0,
944-
is_ancestor: bool = False,
945+
ancestor_for: 'State' = None,
945946
) -> None:
946947
assert id or path or source is not None, "Neither id, path nor source given"
947948
self.manager = manager
@@ -971,13 +972,18 @@ def __init__(self,
971972
if path:
972973
# In silent mode, don't import .py files, except from stubs.
973974
if (SILENT_IMPORTS in manager.flags and
974-
path.endswith('.py') and (caller_state or is_ancestor)):
975+
path.endswith('.py') and (caller_state or ancestor_for)):
975976
# (Never silence builtins, even if it's a .py file;
976977
# this can happen in tests!)
977978
if (id != 'builtins' and
978979
not ((caller_state and
979980
caller_state.tree and
980981
caller_state.tree.is_stub))):
982+
if ALMOST_SILENT in manager.flags:
983+
if ancestor_for:
984+
self.skipping_ancestor(id, path, ancestor_for)
985+
else:
986+
self.skipping_module(id, path)
981987
path = None
982988
manager.missing_modules.add(id)
983989
raise ModuleNotFound
@@ -986,10 +992,12 @@ def __init__(self,
986992
# misspelled module name, missing stub, module not in
987993
# search path or the module has not been installed.
988994
if caller_state:
989-
if not (SILENT_IMPORTS in manager.flags or
990-
(caller_state.tree is not None and
991-
(caller_line in caller_state.tree.ignored_lines or
992-
'import' in caller_state.tree.weak_opts))):
995+
suppress_message = ((SILENT_IMPORTS in manager.flags and
996+
ALMOST_SILENT not in manager.flags) or
997+
(caller_state.tree is not None and
998+
(caller_line in caller_state.tree.ignored_lines or
999+
'import' in caller_state.tree.weak_opts)))
1000+
if not suppress_message:
9931001
save_import_context = manager.errors.import_context()
9941002
manager.errors.set_import_context(caller_state.import_context)
9951003
manager.module_not_found(caller_state.xpath, caller_line, id)
@@ -1015,6 +1023,36 @@ def __init__(self,
10151023
# Parse the file (and then some) to get the dependencies.
10161024
self.parse_file()
10171025

1026+
def skipping_ancestor(self, id: str, path: str, ancestor_for: 'State') -> None:
1027+
# TODO: Read the path (the __init__.py file) and return
1028+
# immediately if it's empty or only contains comments.
1029+
# But beware, some package may be the ancestor of many modules,
1030+
# so we'd need to cache the decision.
1031+
manager = self.manager
1032+
manager.errors.set_import_context([])
1033+
manager.errors.set_file(ancestor_for.xpath)
1034+
manager.errors.report(-1, "Ancestor package '%s' silently ignored" % (id,),
1035+
severity='note', only_once=True)
1036+
manager.errors.report(-1, "(Using --silent-imports, submodule passed on command line)",
1037+
severity='note', only_once=True)
1038+
manager.errors.report(-1, "(This note brought to you by --almost-silent)",
1039+
severity='note', only_once=True)
1040+
1041+
def skipping_module(self, id: str, path: str) -> None:
1042+
assert self.caller_state, (id, path)
1043+
manager = self.manager
1044+
save_import_context = manager.errors.import_context()
1045+
manager.errors.set_import_context(self.caller_state.import_context)
1046+
manager.errors.set_file(self.caller_state.xpath)
1047+
line = self.caller_line
1048+
manager.errors.report(line, "Import of '%s' silently ignored" % (id,),
1049+
severity='note')
1050+
manager.errors.report(line, "(Using --silent-imports, module not passed on command line)",
1051+
severity='note', only_once=True)
1052+
manager.errors.report(line, "(This note courtesy of --almost-silent)",
1053+
severity='note', only_once=True)
1054+
manager.errors.set_import_context(save_import_context)
1055+
10181056
def add_ancestors(self) -> None:
10191057
# All parent packages are new ancestors.
10201058
ancestors = []
@@ -1215,7 +1253,7 @@ def load_graph(sources: List[BuildSource], manager: BuildManager) -> Graph:
12151253
# TODO: Why not 'if dep not in st.dependencies' ?
12161254
# Ancestors don't have import context.
12171255
newst = State(id=dep, path=None, source=None, manager=manager,
1218-
is_ancestor=True)
1256+
ancestor_for=st)
12191257
else:
12201258
newst = State(id=dep, path=None, source=None, manager=manager,
12211259
caller_state=st, caller_line=st.dep_line_map.get(dep, 1))

mypy/main.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,12 @@ def parse_version(v):
130130
help='use Python x.y')
131131
parser.add_argument('--py2', dest='python_version', action='store_const',
132132
const=defaults.PYTHON2_VERSION, help="use Python 2 mode")
133-
parser.add_argument('-s', '--silent-imports', '--silent', action='store_true',
133+
parser.add_argument('-s', '--silent-imports', action='store_true',
134134
help="don't follow imports to .py files")
135+
parser.add_argument('--silent', action='store_true',
136+
help="deprecated name for --silent-imports")
137+
parser.add_argument('--almost-silent', action='store_true',
138+
help="like --silent-imports but reports the imports as errors")
135139
parser.add_argument('--disallow-untyped-calls', action='store_true',
136140
help="disallow calling functions without type annotations"
137141
" from functions with type annotations")
@@ -208,8 +212,11 @@ def parse_version(v):
208212
if args.inferstats:
209213
options.build_flags.append(build.DUMP_INFER_STATS)
210214

211-
if args.silent_imports:
215+
if args.silent_imports or args.silent:
212216
options.build_flags.append(build.SILENT_IMPORTS)
217+
if args.almost_silent:
218+
options.build_flags.append(build.SILENT_IMPORTS)
219+
options.build_flags.append(build.ALMOST_SILENT)
213220

214221
if args.disallow_untyped_calls:
215222
options.build_flags.append(build.DISALLOW_UNTYPED_CALLS)

mypy/test/data/check-modules.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,29 @@ class C(B):
699699
[out]
700700
tmp/bar.py:1: error: Module has no attribute 'B'
701701

702+
[case testImportSuppressedWhileAlmostSilent]
703+
# cmd: mypy -m main
704+
[file main.py]
705+
# flags: silent-imports almost-silent
706+
import mod
707+
[file mod.py]
708+
[builtins fixtures/module.py]
709+
[out]
710+
tmp/main.py:2: note: Import of 'mod' silently ignored
711+
tmp/main.py:2: note: (Using --silent-imports, module not passed on command line)
712+
tmp/main.py:2: note: (This note courtesy of --almost-silent)
713+
714+
[case testAncestorSuppressedWhileAlmostSilent]
715+
# cmd: mypy -m foo.bar
716+
[file foo/bar.py]
717+
# flags: silent-imports almost-silent
718+
[file foo/__init__.py]
719+
[builtins fixtures/module.py]
720+
[out]
721+
tmp/foo/bar.py: note: Ancestor package 'foo' silently ignored
722+
tmp/foo/bar.py: note: (Using --silent-imports, submodule passed on command line)
723+
tmp/foo/bar.py: note: (This note brought to you by --almost-silent)
724+
702725
[case testStubImportNonStubWhileSilent]
703726
# cmd: mypy -m main
704727
[file main.py]

0 commit comments

Comments
 (0)