Skip to content

Change flag names for silent modes and add a new mode ("follow silently") #2513

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 21 commits into from
Dec 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
71 changes: 40 additions & 31 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def default_lib_path(data_dir: str,
])
# NOTE: dependencies + suppressed == all reachable imports;
# suppressed contains those reachable imports that were prevented by
# --silent-imports or simply not found.
# silent mode or simply not found.


# Priorities used for imports. (Here, top-level includes inside a class.)
Expand Down Expand Up @@ -481,7 +481,7 @@ def module_not_found(self, path: str, line: int, id: str) -> None:
else:
self.errors.report(line, 0, "Cannot find module named '{}'".format(id))
self.errors.report(line, 0, '(Perhaps setting MYPYPATH '
'or using the "--silent-imports" flag would help)',
'or using the "--ignore-missing-imports" flag would help)',
severity='note', only_once=True)

def report_file(self, file: MypyFile, type_map: Dict[Expression, Type]) -> None:
Expand Down Expand Up @@ -1101,6 +1101,9 @@ class State:
# Options, specialized for this file
options = None # type: Options

# Whether to ignore all errors
ignore_all = False

def __init__(self,
id: Optional[str],
path: Optional[str],
Expand Down Expand Up @@ -1137,16 +1140,25 @@ def __init__(self,
file_id = '__builtin__'
path = find_module(file_id, manager.lib_path)
if path:
# In silent mode, don't import .py files, except from stubs.
if (self.options.silent_imports and
path.endswith('.py') and (caller_state or ancestor_for)):
# (Never silence builtins, even if it's a .py file;
# this can happen in tests!)
if (id != 'builtins' and
not ((caller_state and
caller_state.tree and
caller_state.tree.is_stub))):
if self.options.almost_silent:
# For non-stubs, look at options.follow_imports:
# - normal (default) -> fully analyze
# - silent -> analyze but silence errors
# - skip -> don't analyze, make the type Any
follow_imports = self.options.follow_imports
if (follow_imports != 'normal'
and path.endswith('.py') # Stubs are always normal
and id != 'builtins' # Builtins is always normal
and not (caller_state and
caller_state.tree and
caller_state.tree.is_stub)):
if follow_imports == 'silent':
# Still import it, but silence non-blocker errors.
manager.log("Silencing %s (%s)" % (path, id))
self.ignore_all = True
else:
# In 'error' mode, produce special error messages.
manager.log("Skipping %s (%s)" % (path, id))
if follow_imports == 'error':
if ancestor_for:
self.skipping_ancestor(id, path, ancestor_for)
else:
Expand All @@ -1159,9 +1171,7 @@ def __init__(self,
# misspelled module name, missing stub, module not in
# search path or the module has not been installed.
if caller_state:
suppress_message = (self.options.silent_imports
and not self.options.almost_silent)
if not suppress_message:
if not self.options.ignore_missing_imports:
save_import_context = manager.errors.import_context()
manager.errors.set_import_context(caller_state.import_context)
manager.module_not_found(caller_state.xpath, caller_line, id)
Expand Down Expand Up @@ -1207,11 +1217,10 @@ def skipping_ancestor(self, id: str, path: str, ancestor_for: 'State') -> None:
manager = self.manager
manager.errors.set_import_context([])
manager.errors.set_file(ancestor_for.xpath)
manager.errors.report(-1, -1, "Ancestor package '%s' silently ignored" % (id,),
manager.errors.report(-1, -1, "Ancestor package '%s' ignored" % (id,),
severity='note', only_once=True)
manager.errors.report(-1, -1, "(Using --silent-imports, submodule passed on command line)",
severity='note', only_once=True)
manager.errors.report(-1, -1, "(This note brought to you by --almost-silent)",
manager.errors.report(-1, -1,
"(Using --follow-imports=error, submodule passed on command line)",
severity='note', only_once=True)

def skipping_module(self, id: str, path: str) -> None:
Expand All @@ -1222,12 +1231,10 @@ def skipping_module(self, id: str, path: str) -> None:
manager.errors.set_file(self.caller_state.xpath)
line = self.caller_line
manager.errors.report(line, 0,
"Import of '%s' silently ignored" % (id,),
"Import of '%s' ignored" % (id,),
severity='note')
manager.errors.report(line, 0,
"(Using --silent-imports, module not passed on command line)",
severity='note', only_once=True)
manager.errors.report(line, 0, "(This note courtesy of --almost-silent)",
"(Using --follow-imports=error, module not passed on command line)",
severity='note', only_once=True)
manager.errors.set_import_context(save_import_context)

Expand All @@ -1244,7 +1251,7 @@ def is_fresh(self) -> bool:
"""Return whether the cache data for this file is fresh."""
# NOTE: self.dependencies may differ from
# self.meta.dependencies when a dependency is dropped due to
# suppression by --silent-imports. However when a suppressed
# suppression by silent mode. However when a suppressed
# dependency is added back we find out later in the process.
return (self.meta is not None
and self.is_interface_fresh()
Expand Down Expand Up @@ -1303,24 +1310,25 @@ def calculate_mros(self) -> None:
fixup_module_pass_two(self.tree, self.manager.modules)

def fix_suppressed_dependencies(self, graph: Graph) -> None:
"""Corrects whether dependencies are considered stale or not when using silent_imports.
"""Corrects whether dependencies are considered stale in silent mode.

This method is a hack to correct imports in silent_imports + incremental mode.
This method is a hack to correct imports in silent mode + incremental mode.
In particular, the problem is that when running mypy with a cold cache, the
`parse_file(...)` function is called *at the start* of the `load_graph(...)` function.
Note that load_graph will mark some dependencies as suppressed if they weren't specified
on the command line in silent_imports mode.
on the command line in silent mode.

However, if the interface for a module is changed, parse_file will be called within
`process_stale_scc` -- *after* load_graph is finished, wiping out the changes load_graph
previously made.

This method is meant to be run after parse_file finishes in process_stale_scc and will
recompute what modules should be considered suppressed in silent_import mode.
recompute what modules should be considered suppressed in silent mode.
"""
# TODO: See if it's possible to move this check directly into parse_file in some way.
# TODO: Find a way to write a test case for this fix.
silent_mode = self.options.silent_imports or self.options.almost_silent
silent_mode = (self.options.ignore_missing_imports or
self.options.follow_imports == 'skip')
if not silent_mode:
return

Expand Down Expand Up @@ -1360,7 +1368,8 @@ def parse_file(self) -> None:
except (UnicodeDecodeError, DecodeError) as decodeerr:
raise CompileError([
"mypy: can't decode file '{}': {}".format(self.path, str(decodeerr))])
self.tree = manager.parse_file(self.id, self.xpath, source, self.options.ignore_errors)
self.tree = manager.parse_file(self.id, self.xpath, source,
self.ignore_all or self.options.ignore_errors)

modules[self.id] = self.tree

Expand Down Expand Up @@ -1413,7 +1422,7 @@ def parse_file(self) -> None:
# NOTE: What to do about race conditions (like editing the
# file while mypy runs)? A previous version of this code
# explicitly checked for this, but ran afoul of other reasons
# for differences (e.g. --silent-imports).
# for differences (e.g. silent mode).
self.dependencies = dependencies
self.suppressed = suppressed
self.priorities = priorities
Expand Down
2 changes: 1 addition & 1 deletion mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def report(self, line: int, column: int, message: str, blocker: bool = False,

def add_error_info(self, info: ErrorInfo) -> None:
(file, line) = info.origin
if not info.blocker:
if not info.blocker: # Blockers cannot be ignored
if file in self.ignored_lines and line in self.ignored_lines[file]:
# Annotation requests us to ignore all errors on this line.
self.used_ignored_lines[file].add(line)
Expand Down
75 changes: 53 additions & 22 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ def process_options(args: List[str],
"(defaults to sys.platform).")
parser.add_argument('-2', '--py2', dest='python_version', action='store_const',
const=defaults.PYTHON2_VERSION, help="use Python 2 mode")
parser.add_argument('-s', '--silent-imports', action='store_true',
help="don't follow imports to .py files")
parser.add_argument('--almost-silent', action='store_true',
help="like --silent-imports but reports the imports as errors")
parser.add_argument('--ignore-missing-imports', action='store_true',
help="silently ignore imports of missing modules")
parser.add_argument('--follow-imports', choices=['normal', 'silent', 'skip', 'error'],
default='normal', help="how to treat imports (default normal)")
parser.add_argument('--disallow-untyped-calls', action='store_true',
help="disallow calling functions without type annotations"
" from functions with type annotations")
Expand All @@ -175,7 +175,7 @@ def process_options(args: List[str],
dest='hide_error_context',
help="Hide context notes before errors")
parser.add_argument('--fast-parser', action='store_true',
help="enable experimental fast parser")
help="enable fast parser (recommended except on Windows)")
parser.add_argument('-i', '--incremental', action='store_true',
help="enable experimental module cache")
parser.add_argument('--cache-dir', action='store', metavar='DIR',
Expand Down Expand Up @@ -223,14 +223,18 @@ def process_options(args: List[str],
# is easier to debug).
parser.add_argument('--debug-cache', action='store_true', help=argparse.SUPPRESS)
# deprecated options
parser.add_argument('--silent', action='store_true', dest='special-opts:silent',
help=argparse.SUPPRESS)
parser.add_argument('-f', '--dirty-stubs', action='store_true',
dest='special-opts:dirty_stubs',
help=argparse.SUPPRESS)
parser.add_argument('--use-python-path', action='store_true',
dest='special-opts:use_python_path',
help=argparse.SUPPRESS)
parser.add_argument('-s', '--silent-imports', action='store_true',
dest='special-opts:silent_imports',
help=argparse.SUPPRESS)
parser.add_argument('--almost-silent', action='store_true',
dest='special-opts:almost_silent',
help=argparse.SUPPRESS)

report_group = parser.add_argument_group(
title='report generation',
Expand Down Expand Up @@ -259,7 +263,11 @@ def process_options(args: List[str],
# filename for the config file.
dummy = argparse.Namespace()
parser.parse_args(args, dummy)
config_file = dummy.config_file or defaults.CONFIG_FILE
config_file = defaults.CONFIG_FILE
if dummy.config_file:
config_file = dummy.config_file
if not os.path.exists(config_file):
parser.error("Cannot file config file '%s'" % config_file)

# Parse config file first, so command line can override.
options = Options()
Expand All @@ -278,11 +286,18 @@ def process_options(args: List[str],
"See https://github.com/python/mypy/issues/1411 for more discussion."
)

# warn about deprecated options
if special_opts.silent:
print("Warning: --silent is deprecated; use --silent-imports",
file=sys.stderr)
options.silent_imports = True
# Process deprecated options
if special_opts.almost_silent:
print("Warning: --almost-silent has been replaced by "
"--follow=imports=errors", file=sys.stderr)
if options.follow_imports == 'normal':
options.follow_imports = 'errors'
elif special_opts.silent_imports:
print("Warning: --silent-imports has been replaced by "
"--ignore-missing-imports --follow=imports=skip", file=sys.stderr)
options.ignore_missing_imports = True
if options.follow_imports == 'normal':
options.follow_imports = 'skip'
if special_opts.dirty_stubs:
print("Warning: -f/--dirty-stubs is deprecated and no longer necessary. Mypy no longer "
"checks the git status of stubs.",
Expand Down Expand Up @@ -456,6 +471,9 @@ def get_init_file(dir: str) -> Optional[str]:
'custom_typeshed_dir': str,
'mypy_path': lambda s: [p.strip() for p in re.split('[,:]', s)],
'junit_xml': str,
# These two are for backwards compatibility
'silent_imports': bool,
'almost_silent': bool,
}


Expand All @@ -472,14 +490,13 @@ def parse_config_file(options: Options, filename: str) -> None:
return
if 'mypy' not in parser:
print("%s: No [mypy] section in config file" % filename, file=sys.stderr)
return

section = parser['mypy']
prefix = '%s: [%s]' % (filename, 'mypy')
updates, report_dirs = parse_section(prefix, options, section)
for k, v in updates.items():
setattr(options, k, v)
options.report_dirs.update(report_dirs)
else:
section = parser['mypy']
prefix = '%s: [%s]' % (filename, 'mypy')
updates, report_dirs = parse_section(prefix, options, section)
for k, v in updates.items():
setattr(options, k, v)
options.report_dirs.update(report_dirs)

for name, section in parser.items():
if name.startswith('mypy-'):
Expand Down Expand Up @@ -509,7 +526,7 @@ def parse_section(prefix: str, template: Options,

Returns a dict of option values encountered, and a dict of report directories.
"""
results = {}
results = {} # type: Dict[str, object]
report_dirs = {} # type: Dict[str, str]
for key in section:
key = key.replace('-', '_')
Expand Down Expand Up @@ -542,6 +559,20 @@ def parse_section(prefix: str, template: Options,
except ValueError as err:
print("%s: %s: %s" % (prefix, key, err), file=sys.stderr)
continue
if key == 'silent_imports':
print("%s: silent_imports has been replaced by "
"ignore_missing_imports=True; follow_imports=skip" % prefix, file=sys.stderr)
if v:
if 'ignore_missing_imports' not in results:
results['ignore_missing_imports'] = True
if 'follow_imports' not in results:
results['follow_imports'] = 'skip'
if key == 'almost_silent':
print("%s: almost_silent has been replaced by "
"follow_imports=error" % prefix, file=sys.stderr)
if v:
if 'follow_imports' not in results:
results['follow_imports'] = 'error'
results[key] = v
return results, report_dirs

Expand Down
8 changes: 4 additions & 4 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class Options:
"""Options collected from flags."""

PER_MODULE_OPTIONS = {
"silent_imports",
"almost_silent",
"ignore_missing_imports",
"follow_imports",
"disallow_untyped_calls",
"disallow_untyped_defs",
"check_untyped_defs",
Expand All @@ -40,8 +40,8 @@ def __init__(self) -> None:
self.custom_typeshed_dir = None # type: Optional[str]
self.mypy_path = [] # type: List[str]
self.report_dirs = {} # type: Dict[str, str]
self.silent_imports = False
self.almost_silent = False
self.ignore_missing_imports = False
self.follow_imports = 'normal' # normal|silent|skip|error

# Disallow calling untyped functions from typed ones
self.disallow_untyped_calls = False
Expand Down
Loading