Skip to content

Commit c719fa6

Browse files
committed
Merge branch 'master' into warn-unused-strictness-exceptions
2 parents c623a85 + ae374af commit c719fa6

13 files changed

+196
-176
lines changed

misc/incremental_checker.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def run_mypy(target_file_path: Optional[str],
157157

158158

159159
def start_daemon(mypy_cache_path: str) -> None:
160-
cmd = DAEMON_CMD + ["restart", "--", "--cache-dir", mypy_cache_path]
160+
cmd = DAEMON_CMD + ["restart", "--log-file", "./@incr-chk-logs", "--", "--cache-dir", mypy_cache_path]
161161
execute(cmd)
162162

163163

@@ -287,6 +287,8 @@ def test_repo(target_repo_url: str, temp_repo_path: str,
287287
else:
288288
raise RuntimeError("Invalid option: {}".format(range_type))
289289
commits = get_commits_starting_at(temp_repo_path, start_commit)
290+
if params.limit:
291+
commits = commits[:params.limit]
290292
if params.sample:
291293
seed = params.seed or base64.urlsafe_b64encode(os.urandom(15)).decode('ascii')
292294
random.seed(seed)
@@ -308,7 +310,8 @@ def test_repo(target_repo_url: str, temp_repo_path: str,
308310
exit_on_error=params.exit_on_error)
309311

310312
# Stage 5: Remove temp files, stop daemon
311-
cleanup(temp_repo_path, mypy_cache_path)
313+
if not params.keep_temporary_files:
314+
cleanup(temp_repo_path, mypy_cache_path)
312315
if params.daemon:
313316
print('Stopping daemon')
314317
stop_daemon()
@@ -332,13 +335,17 @@ def main() -> None:
332335
help="the name of the file or directory to typecheck")
333336
parser.add_argument("-x", "--exit-on-error", action='store_true',
334337
help="Exits as soon as an error occurs")
338+
parser.add_argument("--keep-temporary-files", action='store_true',
339+
help="Keep temporary files on exit")
335340
parser.add_argument("--cache-path", default=CACHE_PATH, metavar="DIR",
336341
help="sets a custom location to store cache data")
337342
parser.add_argument("--branch", default=None, metavar="NAME",
338343
help="check out and test a custom branch"
339344
"uses the default if not specified")
340345
parser.add_argument("--sample", type=int, help="use a random sample of size SAMPLE")
341346
parser.add_argument("--seed", type=str, help="random seed")
347+
parser.add_argument("--limit", type=int,
348+
help="maximum number of commits to use (default until end)")
342349
parser.add_argument("--mypy-script", type=str, help="alternate mypy script to run")
343350
parser.add_argument("--daemon", action='store_true',
344351
help="use mypy daemon instead of incremental (highly experimental)")

mypy/build.py

+97-24
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
if MYPY:
3333
from typing import Deque
3434

35-
from mypy.nodes import (MypyFile, Node, ImportBase, Import, ImportFrom, ImportAll)
35+
from mypy.nodes import (MODULE_REF, MypyFile, Node, ImportBase, Import, ImportFrom, ImportAll)
3636
from mypy.semanal_pass1 import SemanticAnalyzerPass1
3737
from mypy.semanal import SemanticAnalyzerPass2
3838
from mypy.semanal_pass3 import SemanticAnalyzerPass3
@@ -179,6 +179,8 @@ def build(sources: List[BuildSource],
179179
# multiple builds, there could be a mix of files/modules, so its easier
180180
# to just define the semantics that we always add the current director
181181
# to the lib_path
182+
# TODO: Don't do this in some cases; for motivation see see
183+
# https://github.com/python/mypy/issues/4195#issuecomment-341915031
182184
lib_path.insert(0, os.getcwd())
183185

184186
# Prepend a config-defined mypy path.
@@ -1621,7 +1623,6 @@ def mark_as_rechecked(self) -> None:
16211623

16221624
def mark_interface_stale(self, *, on_errors: bool = False) -> None:
16231625
"""Marks this module as having a stale public interface, and discards the cache data."""
1624-
self.meta = None
16251626
self.externally_same = False
16261627
if not on_errors:
16271628
self.manager.stale_modules.add(self.id)
@@ -1903,6 +1904,7 @@ def write_cache(self) -> None:
19031904
is_errors = self.manager.errors.is_errors()
19041905
if is_errors:
19051906
delete_cache(self.id, self.path, self.manager)
1907+
self.meta = None
19061908
self.mark_interface_stale(on_errors=True)
19071909
return
19081910
dep_prios = [self.priorities.get(dep, PRI_HIGH) for dep in self.dependencies]
@@ -1920,6 +1922,7 @@ def write_cache(self) -> None:
19201922

19211923

19221924
def dispatch(sources: List[BuildSource], manager: BuildManager) -> Graph:
1925+
set_orig = set(manager.saved_cache)
19231926
manager.log()
19241927
manager.log("Mypy version %s" % __version__)
19251928
t0 = time.time()
@@ -1940,13 +1943,23 @@ def dispatch(sources: List[BuildSource], manager: BuildManager) -> Graph:
19401943
if manager.options.warn_unused_ignores:
19411944
# TODO: This could also be a per-module option.
19421945
manager.errors.generate_unused_ignore_notes()
1946+
updated = preserve_cache(graph)
1947+
set_updated = set(updated)
1948+
manager.saved_cache.clear()
1949+
manager.saved_cache.update(updated)
1950+
set_final = set(manager.saved_cache)
1951+
# These keys have numbers in them to force a sort order.
1952+
manager.add_stats(saved_cache_1orig=len(set_orig),
1953+
saved_cache_2updated=len(set_updated & set_orig),
1954+
saved_cache_3added=len(set_final - set_orig),
1955+
saved_cache_4removed=len(set_orig - set_final),
1956+
saved_cache_5final=len(set_final))
19431957
if manager.options.warn_unused_strictness_exceptions:
19441958
for option in manager.options.unused_strictness_whitelist:
19451959
for file in manager.options.unused_strictness_whitelist[option]:
19461960
message = "Flag {} can be enabled for module '{}'".format(option, file)
19471961
manager.errors.report(-1, -1, message, blocker=False, severity='warning',
19481962
file=file)
1949-
manager.saved_cache.update(preserve_cache(graph))
19501963
if manager.options.dump_deps:
19511964
# This speeds up startup a little when not using the daemon mode.
19521965
from mypy.server.deps import dump_all_dependencies
@@ -2045,7 +2058,19 @@ def load_graph(sources: List[BuildSource], manager: BuildManager) -> Graph:
20452058
while new:
20462059
st = new.popleft()
20472060
assert st.ancestors is not None
2048-
for dep in st.ancestors + st.dependencies + st.suppressed:
2061+
# Strip out indirect dependencies. These will be dealt with
2062+
# when they show up as direct dependencies, and there's a
2063+
# scenario where they hurt:
2064+
# - Suppose A imports B and B imports C.
2065+
# - Suppose on the next round:
2066+
# - C is deleted;
2067+
# - B is updated to remove the dependency on C;
2068+
# - A is unchanged.
2069+
# - In this case A's cached *direct* dependencies are still valid
2070+
# (since direct dependencies reflect the imports found in the source)
2071+
# but A's cached *indirect* dependency on C is wrong.
2072+
dependencies = [dep for dep in st.dependencies if st.priorities.get(dep) != PRI_INDIRECT]
2073+
for dep in st.ancestors + dependencies + st.suppressed:
20492074
# We don't want to recheck imports marked with '# type: ignore'
20502075
# so we ignore any suppressed module not explicitly re-included
20512076
# from the command line.
@@ -2127,7 +2152,7 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
21272152
for id in scc:
21282153
deps.update(graph[id].dependencies)
21292154
deps -= ascc
2130-
stale_deps = {id for id in deps if not graph[id].is_interface_fresh()}
2155+
stale_deps = {id for id in deps if id in graph and not graph[id].is_interface_fresh()}
21312156
if not manager.options.quick_and_dirty:
21322157
fresh = fresh and not stale_deps
21332158
undeps = set()
@@ -2184,8 +2209,9 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
21842209

21852210
scc_str = " ".join(scc)
21862211
if fresh:
2187-
manager.trace("Queuing %s SCC (%s)" % (fresh_msg, scc_str))
2188-
fresh_scc_queue.append(scc)
2212+
if not maybe_reuse_in_memory_tree(graph, scc, manager):
2213+
manager.trace("Queuing %s SCC (%s)" % (fresh_msg, scc_str))
2214+
fresh_scc_queue.append(scc)
21892215
else:
21902216
if len(fresh_scc_queue) > 0:
21912217
manager.log("Processing {} queued fresh SCCs".format(len(fresh_scc_queue)))
@@ -2273,23 +2299,6 @@ def process_fresh_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
22732299
22742300
If the tree is loaded from memory ('saved_cache') it's even quicker.
22752301
"""
2276-
saved_cache = manager.saved_cache
2277-
# Check that all nodes are available for loading from memory.
2278-
if all(id in saved_cache for id in scc):
2279-
deps = set(dep for id in scc for dep in graph[id].dependencies if dep in graph)
2280-
# Check that all dependencies were loaded from memory.
2281-
# If not, some dependency was reparsed but the interface hash
2282-
# wasn't changed -- in that case we can't reuse the tree.
2283-
if all(graph[dep].is_from_saved_cache for dep in deps):
2284-
trees = {id: saved_cache[id][1] for id in scc}
2285-
for id, tree in trees.items():
2286-
manager.add_stats(reused_trees=1)
2287-
manager.trace("Reusing saved tree %s" % id)
2288-
st = graph[id]
2289-
st.tree = tree # This is never overwritten.
2290-
st.is_from_saved_cache = True
2291-
manager.modules[id] = tree
2292-
return
22932302
for id in scc:
22942303
graph[id].load_tree()
22952304
for id in scc:
@@ -2300,6 +2309,70 @@ def process_fresh_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
23002309
graph[id].patch_dependency_parents()
23012310

23022311

2312+
def maybe_reuse_in_memory_tree(graph: Graph, scc: List[str], manager: BuildManager) -> bool:
2313+
"""Set the trees for the given SCC from the in-memory cache, if all valid.
2314+
2315+
If any saved tree for this SCC is invalid, set the trees for all
2316+
SCC members to None and mark as not-from-cache.
2317+
"""
2318+
if not can_reuse_in_memory_tree(graph, scc, manager):
2319+
for id in scc:
2320+
manager.add_stats(cleared_trees=1)
2321+
manager.trace("Clearing tree %s" % id)
2322+
st = graph[id]
2323+
st.tree = None
2324+
st.is_from_saved_cache = False
2325+
if id in manager.modules:
2326+
del manager.modules[id]
2327+
return False
2328+
trees = {id: manager.saved_cache[id][1] for id in scc}
2329+
for id, tree in trees.items():
2330+
manager.add_stats(reused_trees=1)
2331+
manager.trace("Reusing saved tree %s" % id)
2332+
st = graph[id]
2333+
st.tree = tree
2334+
st.is_from_saved_cache = True
2335+
manager.modules[id] = tree
2336+
# Delete any submodules from the module that aren't
2337+
# dependencies of the module; they will be re-added once
2338+
# imported. It's possible that the parent module is reused
2339+
# but a submodule isn't; we don't want to accidentally link
2340+
# into the old submodule's tree. See also
2341+
# patch_dependency_parents() above. The exception for subname
2342+
# in st.dependencies handles the case where 'import m'
2343+
# guarantees that some submodule of m is also available
2344+
# (e.g. 'os.path'); in those cases the submodule is an
2345+
# explicit dependency of the parent.
2346+
for name in list(tree.names):
2347+
sym = tree.names[name]
2348+
subname = id + '.' + name
2349+
if (sym.kind == MODULE_REF
2350+
and sym.node is not None
2351+
and sym.node.fullname() == subname
2352+
and subname not in st.dependencies):
2353+
manager.trace("Purging %s" % subname)
2354+
del tree.names[name]
2355+
return True
2356+
2357+
2358+
def can_reuse_in_memory_tree(graph: Graph, scc: List[str], manager: BuildManager) -> bool:
2359+
"""Check whether the given SCC can safely reuse the trees from saved_cache.
2360+
2361+
Assumes the SCC is already considered fresh.
2362+
"""
2363+
saved_cache = manager.saved_cache
2364+
# Check that all nodes are available for loading from memory.
2365+
if all(id in saved_cache for id in scc):
2366+
# Check that all dependencies were loaded from memory.
2367+
# If not, some dependency was reparsed but the interface hash
2368+
# wasn't changed -- in that case we can't reuse the tree.
2369+
deps = set(dep for id in scc for dep in graph[id].dependencies if dep in graph)
2370+
deps -= set(scc) # Subtract the SCC itself (else nothing will be safe)
2371+
if all(graph[dep].is_from_saved_cache for dep in deps):
2372+
return True
2373+
return False
2374+
2375+
23032376
def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> None:
23042377
"""Process the modules in one SCC from source code.
23052378

mypy/checkmember.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance,
584584
callable_type = init_type.copy_modified(
585585
ret_type=fill_typevars(info), fallback=type_type, name=None, variables=variables,
586586
special_sig=special_sig)
587-
c = callable_type.with_name('"{}"'.format(info.name()))
587+
c = callable_type.with_name(info.name())
588588
return c
589589

590590

mypy/messages.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
536536

537537
for op, method in op_methods.items():
538538
for variant in method, '__r' + method[2:]:
539+
# FIX: do not rely on textual formatting
539540
if name.startswith('"{}" of'.format(variant)):
540541
if op == 'in' or variant != method:
541542
# Reversed order of base/argument.
@@ -726,9 +727,10 @@ def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None:
726727

727728
def no_variant_matches_arguments(self, overload: Overloaded, arg_types: List[Type],
728729
context: Context) -> None:
729-
if overload.name():
730+
name = callable_name(overload)
731+
if name:
730732
self.fail('No overload variant of {} matches argument types {}'
731-
.format(overload.name(), arg_types), context)
733+
.format(name, arg_types), context)
732734
else:
733735
self.fail('No overload variant matches argument types {}'.format(arg_types), context)
734736

@@ -1349,7 +1351,10 @@ def format_item_name_list(s: Iterable[str]) -> str:
13491351

13501352

13511353
def callable_name(type: FunctionLike) -> Optional[str]:
1352-
return type.get_name()
1354+
name = type.get_name()
1355+
if name is not None and name[0] != '<':
1356+
return '"{}"'.format(name).replace(' of ', '" of "')
1357+
return name
13531358

13541359

13551360
def for_function(callee: CallableType) -> str:

mypy/report.py

+3-22
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from mypy.version import __version__
2626

2727
try:
28-
import lxml.etree as etree
28+
import lxml.etree as etree # type: ignore
2929
LXML_INSTALLED = True
3030
except ImportError:
3131
LXML_INSTALLED = False
@@ -361,25 +361,6 @@ def on_finish(self) -> None:
361361
register_reporter('linecoverage', LineCoverageReporter)
362362

363363

364-
class OldHtmlReporter(AbstractReporter):
365-
"""Old HTML reporter.
366-
367-
This just calls the old functions in `stats`, which use global
368-
variables to preserve state for the index.
369-
"""
370-
371-
def on_file(self,
372-
tree: MypyFile,
373-
type_map: Dict[Expression, Type], options: Options) -> None:
374-
stats.generate_html_report(tree, tree.path, type_map, self.output_dir)
375-
376-
def on_finish(self) -> None:
377-
stats.generate_html_index(self.output_dir)
378-
379-
380-
register_reporter('old-html', OldHtmlReporter)
381-
382-
383364
class FileInfo:
384365
def __init__(self, name: str, module: str) -> None:
385366
self.name = name
@@ -407,7 +388,7 @@ def __init__(self, reports: Reports, output_dir: str) -> None:
407388
self.css_html_path = os.path.join(reports.data_dir, 'xml', 'mypy-html.css')
408389
xsd_path = os.path.join(reports.data_dir, 'xml', 'mypy.xsd')
409390
self.schema = etree.XMLSchema(etree.parse(xsd_path))
410-
self.last_xml = None # type: Optional[etree._ElementTree]
391+
self.last_xml = None # type: Optional[Any]
411392
self.files = [] # type: List[FileInfo]
412393

413394
def on_file(self,
@@ -501,7 +482,7 @@ class CoberturaPackage(object):
501482
"""
502483
def __init__(self, name: str) -> None:
503484
self.name = name
504-
self.classes = {} # type: Dict[str, etree._Element]
485+
self.classes = {} # type: Dict[str, Any]
505486
self.packages = {} # type: Dict[str, CoberturaPackage]
506487
self.total_lines = 0
507488
self.covered_lines = 0

mypy/semanal.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -2448,14 +2448,14 @@ def add_method(funcname: str,
24482448
arg_kinds = [arg.kind for arg in args]
24492449
assert None not in types
24502450
signature = CallableType(cast(List[Type], types), arg_kinds, items, ret,
2451-
function_type,
2452-
name=name or info.name() + '.' + funcname)
2451+
function_type)
24532452
signature.variables = [tvd]
2454-
func = FuncDef(funcname, args, Block([]), typ=signature)
2453+
func = FuncDef(funcname, args, Block([]))
24552454
func.info = info
24562455
func.is_class = is_classmethod
2456+
func.type = set_callable_name(signature, func)
24572457
if is_classmethod:
2458-
v = Var(funcname, signature)
2458+
v = Var(funcname, func.type)
24592459
v.is_classmethod = True
24602460
v.info = info
24612461
dec = Decorator(func, [NameExpr('classmethod')], v)
@@ -3830,9 +3830,9 @@ def set_callable_name(sig: Type, fdef: FuncDef) -> Type:
38303830
if isinstance(sig, FunctionLike):
38313831
if fdef.info:
38323832
return sig.with_name(
3833-
'"{}" of "{}"'.format(fdef.name(), fdef.info.name()))
3833+
'{} of {}'.format(fdef.name(), fdef.info.name()))
38343834
else:
3835-
return sig.with_name('"{}"'.format(fdef.name()))
3835+
return sig.with_name(fdef.name())
38363836
else:
38373837
return sig
38383838

0 commit comments

Comments
 (0)