Skip to content

Commit bc57349

Browse files
committed
Merge branch 'master' into import-cycle
* master: (32 commits) Fix some fine-grained cache/fswatcher problems (python#4560) Sync typeshed (python#4559) Add _cached suffix to test cases in fine-grained tests with cache (python#4558) Add back support for simplified fine-grained logging (python#4557) Type checking of class decorators (python#4544) Sync typeshed (python#4556) When loading from a fine-grained cache, use the real path, not the cached (python#4555) Switch all of the fine-grained debug logging to use manager.log (python#4550) Caching for fine-grained incremental mode (python#4483) Fix --warn-return-any for NotImplemented (python#4545) Remove myunit (python#4369) Store line numbers of imports in the cache metadata (python#4533) README.md: Fix a typo (python#4529) Enable generation and caching of fine-grained dependencies from normal runs (python#4526) Move argument parsing for the fine-grained flag into the main arg parsing code (python#4524) Don't warn about unrecognized options starting with 'x_' (python#4522) stubgen: don't append star arg when args list already has varargs appended (python#4518) Handle TypedDict in diff and deps (python#4510) Fix Options.__repr__ to not infinite recurse (python#4514) Fix some fine-grained incremental bugs with newly imported files (python#4502) ...
2 parents 2255e1c + f0cd049 commit bc57349

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1838
-900
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ sure you've found a bug please search our issue trackers for a
1414
duplicate before filing a new issue:
1515

1616
- [mypy tracker](https://github.com/python/mypy/issues)
17-
for mypy isues
17+
for mypy issues
1818
- [typeshed tracker](https://github.com/python/typeshed/issues)
1919
for issues with specific modules
2020
- [typing tracker](https://github.com/python/typing/issues)

mypy/build.py

+80-6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@
5252
from mypy.version import __version__
5353
from mypy.plugin import Plugin, DefaultPlugin, ChainedPlugin
5454
from mypy.defaults import PYTHON3_VERSION_MIN
55+
from mypy.server.deps import get_dependencies
56+
57+
58+
# Switch to True to produce debug output related to fine-grained incremental
59+
# mode only that is useful during development. This produces only a subset of
60+
# output compared to --verbose output. We use a global flag to enable this so
61+
# that it's easy to enable this when running tests.
62+
DEBUG_FINE_GRAINED = False
5563

5664

5765
PYTHON_EXTENSIONS = ['.pyi', '.py']
@@ -392,6 +400,7 @@ def default_lib_path(data_dir: str,
392400
('child_modules', List[str]), # all submodules of the given module
393401
('options', Optional[Dict[str, object]]), # build options
394402
('dep_prios', List[int]),
403+
('dep_lines', List[int]),
395404
('interface_hash', str), # hash representing the public interface
396405
('version_id', str), # mypy version for cache invalidation
397406
('ignore_all', bool), # if errors were ignored
@@ -417,6 +426,7 @@ def cache_meta_from_dict(meta: Dict[str, Any], data_json: str) -> CacheMeta:
417426
meta.get('child_modules', []),
418427
meta.get('options'),
419428
meta.get('dep_prios', []),
429+
meta.get('dep_lines', []),
420430
meta.get('interface_hash', ''),
421431
meta.get('version_id', sentinel),
422432
meta.get('ignore_all', True),
@@ -731,6 +741,17 @@ def log(self, *message: str) -> None:
731741
print(file=sys.stderr)
732742
sys.stderr.flush()
733743

744+
def log_fine_grained(self, *message: str) -> None:
745+
if self.options.verbosity >= 1:
746+
self.log('fine-grained:', *message)
747+
elif DEBUG_FINE_GRAINED:
748+
# Output log in a simplified format that is quick to browse.
749+
if message:
750+
print(*message, file=sys.stderr)
751+
else:
752+
print(file=sys.stderr)
753+
sys.stderr.flush()
754+
734755
def trace(self, *message: str) -> None:
735756
if self.options.verbosity >= 2:
736757
print('TRACE:', *message, file=sys.stderr)
@@ -1039,7 +1060,8 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> Optional[Cache
10391060
# Ignore cache if generated by an older mypy version.
10401061
if ((m.version_id != manager.version_id and not manager.options.skip_version_check)
10411062
or m.options is None
1042-
or len(m.dependencies) != len(m.dep_prios)):
1063+
or len(m.dependencies) != len(m.dep_prios)
1064+
or len(m.dependencies) != len(m.dep_lines)):
10431065
manager.log('Metadata abandoned for {}: new attributes are missing'.format(id))
10441066
return None
10451067

@@ -1127,6 +1149,17 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
11271149
if not stat.S_ISREG(st.st_mode):
11281150
manager.log('Metadata abandoned for {}: file {} does not exist'.format(id, path))
11291151
return None
1152+
1153+
# When we are using a fine-grained cache, we want our initial
1154+
# build() to load all of the cache information and then do a
1155+
# fine-grained incremental update to catch anything that has
1156+
# changed since the cache was generated. We *don't* want to do a
1157+
# coarse-grained incremental rebuild, so we accept the cache
1158+
# metadata even if it doesn't match the source file.
1159+
if manager.options.use_fine_grained_cache:
1160+
manager.log('Using potentially stale metadata for {}'.format(id))
1161+
return meta
1162+
11301163
size = st.st_size
11311164
if size != meta.size:
11321165
manager.log('Metadata abandoned for {}: file {} has different size'.format(id, path))
@@ -1156,6 +1189,7 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
11561189
'options': (manager.options.clone_for_module(id)
11571190
.select_options_affecting_cache()),
11581191
'dep_prios': meta.dep_prios,
1192+
'dep_lines': meta.dep_lines,
11591193
'interface_hash': meta.interface_hash,
11601194
'version_id': manager.version_id,
11611195
'ignore_all': meta.ignore_all,
@@ -1183,8 +1217,9 @@ def compute_hash(text: str) -> str:
11831217

11841218

11851219
def write_cache(id: str, path: str, tree: MypyFile,
1220+
serialized_fine_grained_deps: Dict[str, List[str]],
11861221
dependencies: List[str], suppressed: List[str],
1187-
child_modules: List[str], dep_prios: List[int],
1222+
child_modules: List[str], dep_prios: List[int], dep_lines: List[int],
11881223
old_interface_hash: str, source_hash: str,
11891224
ignore_all: bool, manager: BuildManager) -> Tuple[str, Optional[CacheMeta]]:
11901225
"""Write cache files for a module.
@@ -1201,6 +1236,7 @@ def write_cache(id: str, path: str, tree: MypyFile,
12011236
suppressed: module IDs which were suppressed as dependencies
12021237
child_modules: module IDs which are this package's direct submodules
12031238
dep_prios: priorities (parallel array to dependencies)
1239+
dep_lines: import line locations (parallel array to dependencies)
12041240
old_interface_hash: the hash from the previous version of the data cache file
12051241
source_hash: the hash of the source code
12061242
ignore_all: the ignore_all flag for this module
@@ -1221,7 +1257,9 @@ def write_cache(id: str, path: str, tree: MypyFile,
12211257
assert os.path.dirname(meta_json) == parent
12221258

12231259
# Serialize data and analyze interface
1224-
data = tree.serialize()
1260+
data = {'tree': tree.serialize(),
1261+
'fine_grained_deps': serialized_fine_grained_deps,
1262+
}
12251263
if manager.options.debug_cache:
12261264
data_str = json.dumps(data, indent=2, sort_keys=True)
12271265
else:
@@ -1282,6 +1320,7 @@ def write_cache(id: str, path: str, tree: MypyFile,
12821320
'child_modules': child_modules,
12831321
'options': options.select_options_affecting_cache(),
12841322
'dep_prios': dep_prios,
1323+
'dep_lines': dep_lines,
12851324
'interface_hash': interface_hash,
12861325
'version_id': manager.version_id,
12871326
'ignore_all': ignore_all,
@@ -1523,6 +1562,8 @@ class State:
15231562
# Whether the module has an error or any of its dependencies have one.
15241563
transitive_error = False
15251564

1565+
fine_grained_deps = None # type: Dict[str, Set[str]]
1566+
15261567
# Type checker used for checking this file. Use type_checker() for
15271568
# access and to construct this on demand.
15281569
_type_checker = None # type: Optional[TypeChecker]
@@ -1551,6 +1592,7 @@ def __init__(self,
15511592
self.id = id or '__main__'
15521593
self.options = manager.options.clone_for_module(self.id)
15531594
self._type_checker = None
1595+
self.fine_grained_deps = {}
15541596
if not path and source is None:
15551597
assert id is not None
15561598
file_id = id
@@ -1626,8 +1668,10 @@ def __init__(self,
16261668
assert len(self.meta.dependencies) == len(self.meta.dep_prios)
16271669
self.priorities = {id: pri
16281670
for id, pri in zip(self.meta.dependencies, self.meta.dep_prios)}
1671+
assert len(self.meta.dependencies) == len(self.meta.dep_lines)
1672+
self.dep_line_map = {id: line
1673+
for id, line in zip(self.meta.dependencies, self.meta.dep_lines)}
16291674
self.child_modules = set(self.meta.child_modules)
1630-
self.dep_line_map = {}
16311675
else:
16321676
# Parse the file (and then some) to get the dependencies.
16331677
self.parse_file()
@@ -1734,7 +1778,9 @@ def load_tree(self) -> None:
17341778
with open(self.meta.data_json) as f:
17351779
data = json.load(f)
17361780
# TODO: Assert data file wasn't changed.
1737-
self.tree = MypyFile.deserialize(data)
1781+
self.tree = MypyFile.deserialize(data['tree'])
1782+
self.fine_grained_deps = {k: set(v) for k, v in data['fine_grained_deps'].items()}
1783+
17381784
self.manager.modules[self.id] = self.tree
17391785
self.manager.add_stats(fresh_trees=1)
17401786

@@ -1977,6 +2023,19 @@ def _patch_indirect_dependencies(self,
19772023
elif dep not in self.suppressed and dep in self.manager.missing_modules:
19782024
self.suppressed.append(dep)
19792025

2026+
def compute_fine_grained_deps(self) -> None:
2027+
assert self.tree is not None
2028+
if '/typeshed/' in self.xpath or self.xpath.startswith('typeshed/'):
2029+
# We don't track changes to typeshed -- the assumption is that they are only changed
2030+
# as part of mypy updates, which will invalidate everything anyway.
2031+
#
2032+
# TODO: Not a reliable test, as we could have a package named typeshed.
2033+
# TODO: Consider relaxing this -- maybe allow some typeshed changes to be tracked.
2034+
return
2035+
self.fine_grained_deps = get_dependencies(target=self.tree,
2036+
type_map=self.type_map(),
2037+
python_version=self.options.python_version)
2038+
19802039
def valid_references(self) -> Set[str]:
19812040
assert self.ancestors is not None
19822041
valid_refs = set(self.dependencies + self.suppressed + self.ancestors)
@@ -2001,10 +2060,12 @@ def write_cache(self) -> None:
20012060
self.mark_interface_stale(on_errors=True)
20022061
return
20032062
dep_prios = self.dependency_priorities()
2063+
dep_lines = self.dependency_lines()
20042064
new_interface_hash, self.meta = write_cache(
20052065
self.id, self.path, self.tree,
2066+
{k: list(v) for k, v in self.fine_grained_deps.items()},
20062067
list(self.dependencies), list(self.suppressed), list(self.child_modules),
2007-
dep_prios, self.interface_hash, self.source_hash, self.ignore_all,
2068+
dep_prios, dep_lines, self.interface_hash, self.source_hash, self.ignore_all,
20082069
self.manager)
20092070
if new_interface_hash == self.interface_hash:
20102071
self.manager.log("Cached module {} has same interface".format(self.id))
@@ -2016,6 +2077,9 @@ def write_cache(self) -> None:
20162077
def dependency_priorities(self) -> List[int]:
20172078
return [self.priorities.get(dep, PRI_HIGH) for dep in self.dependencies]
20182079

2080+
def dependency_lines(self) -> List[int]:
2081+
return [self.dep_line_map.get(dep, 1) for dep in self.dependencies]
2082+
20192083
def generate_unused_ignore_notes(self) -> None:
20202084
if self.options.warn_unused_ignores:
20212085
self.manager.errors.generate_unused_ignore_notes(self.xpath)
@@ -2348,6 +2412,14 @@ def process_graph(graph: Graph, manager: BuildManager) -> None:
23482412
manager.log("Processing SCC of size %d (%s) as %s" % (size, scc_str, fresh_msg))
23492413
process_stale_scc(graph, scc, manager)
23502414

2415+
# If we are running in fine-grained incremental mode with caching,
2416+
# we always process fresh SCCs so that we have all of the symbol
2417+
# tables and fine-grained dependencies available.
2418+
if manager.options.use_fine_grained_cache:
2419+
for prev_scc in fresh_scc_queue:
2420+
process_fresh_scc(graph, prev_scc, manager)
2421+
fresh_scc_queue = []
2422+
23512423
sccs_left = len(fresh_scc_queue)
23522424
nodes_left = sum(len(scc) for scc in fresh_scc_queue)
23532425
manager.add_stats(sccs_left=sccs_left, nodes_left=nodes_left)
@@ -2534,6 +2606,8 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
25342606
graph[id].transitive_error = True
25352607
for id in stale:
25362608
graph[id].finish_passes()
2609+
if manager.options.cache_fine_grained or manager.options.fine_grained_incremental:
2610+
graph[id].compute_fine_grained_deps()
25372611
graph[id].generate_unused_ignore_notes()
25382612
manager.flush_errors(manager.errors.file_messages(graph[id].xpath), False)
25392613
graph[id].write_cache()

mypy/checker.py

+37-6
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from mypy.sametypes import is_same_type, is_same_types
4141
from mypy.messages import MessageBuilder, make_inferred_type_note
4242
import mypy.checkexpr
43-
from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound
43+
from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound, type_object_type
4444
from mypy import messages
4545
from mypy.subtypes import (
4646
is_subtype, is_equivalent, is_proper_subtype, is_more_precise,
@@ -59,6 +59,7 @@
5959
from mypy.meet import is_overlapping_types
6060
from mypy.options import Options
6161
from mypy.plugin import Plugin, CheckerPluginInterface
62+
from mypy.sharedparse import BINARY_MAGIC_METHODS
6263

6364
from mypy import experiments
6465

@@ -1254,6 +1255,29 @@ def visit_class_def(self, defn: ClassDef) -> None:
12541255
# Otherwise we've already found errors; more errors are not useful
12551256
self.check_multiple_inheritance(typ)
12561257

1258+
if defn.decorators:
1259+
sig = type_object_type(defn.info, self.named_type)
1260+
# Decorators are applied in reverse order.
1261+
for decorator in reversed(defn.decorators):
1262+
if (isinstance(decorator, CallExpr)
1263+
and isinstance(decorator.analyzed, PromoteExpr)):
1264+
# _promote is a special type checking related construct.
1265+
continue
1266+
1267+
dec = self.expr_checker.accept(decorator)
1268+
temp = self.temp_node(sig)
1269+
fullname = None
1270+
if isinstance(decorator, RefExpr):
1271+
fullname = decorator.fullname
1272+
1273+
# TODO: Figure out how to have clearer error messages.
1274+
# (e.g. "class decorator must be a function that accepts a type."
1275+
sig, _ = self.expr_checker.check_call(dec, [temp],
1276+
[nodes.ARG_POS], defn,
1277+
callable_name=fullname)
1278+
# TODO: Apply the sig to the actual TypeInfo so we can handle decorators
1279+
# that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]])
1280+
12571281
def check_protocol_variance(self, defn: ClassDef) -> None:
12581282
"""Check that protocol definition is compatible with declared
12591283
variances of type variables.
@@ -1940,13 +1964,13 @@ def infer_variable_type(self, name: Var, lvalue: Lvalue,
19401964
# partial type which will be made more specific later. A partial type
19411965
# gets generated in assignment like 'x = []' where item type is not known.
19421966
if not self.infer_partial_type(name, lvalue, init_type):
1943-
self.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
1967+
self.msg.need_annotation_for_var(name, context)
19441968
self.set_inference_error_fallback_type(name, lvalue, init_type, context)
19451969
elif (isinstance(lvalue, MemberExpr) and self.inferred_attribute_types is not None
19461970
and lvalue.def_var and lvalue.def_var in self.inferred_attribute_types
19471971
and not is_same_type(self.inferred_attribute_types[lvalue.def_var], init_type)):
19481972
# Multiple, inconsistent types inferred for an attribute.
1949-
self.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
1973+
self.msg.need_annotation_for_var(name, context)
19501974
name.type = AnyType(TypeOfAny.from_error)
19511975
else:
19521976
# Infer type of the target.
@@ -2179,8 +2203,11 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
21792203
if isinstance(typ, AnyType):
21802204
# (Unless you asked to be warned in that case, and the
21812205
# function is not declared to return Any)
2182-
if (self.options.warn_return_any and not self.current_node_deferred and
2183-
not is_proper_subtype(AnyType(TypeOfAny.special_form), return_type)):
2206+
if (self.options.warn_return_any
2207+
and not self.current_node_deferred
2208+
and not is_proper_subtype(AnyType(TypeOfAny.special_form), return_type)
2209+
and not (defn.name() in BINARY_MAGIC_METHODS and
2210+
is_literal_not_implemented(s.expr))):
21842211
self.msg.incorrectly_returning_any(return_type, s)
21852212
return
21862213

@@ -3101,7 +3128,7 @@ def enter_partial_types(self) -> Iterator[None]:
31013128
var.type = NoneTyp()
31023129
else:
31033130
if var not in self.partial_reported:
3104-
self.msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
3131+
self.msg.need_annotation_for_var(var, context)
31053132
self.partial_reported.add(var)
31063133
var.type = AnyType(TypeOfAny.from_error)
31073134

@@ -3232,6 +3259,10 @@ def remove_optional(typ: Type) -> Type:
32323259
return typ
32333260

32343261

3262+
def is_literal_not_implemented(n: Expression) -> bool:
3263+
return isinstance(n, NameExpr) and n.fullname == 'builtins.NotImplemented'
3264+
3265+
32353266
def builtin_item_type(tp: Type) -> Optional[Type]:
32363267
"""Get the item type of a builtin container.
32373268

mypy/checkexpr.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
145145
partial_types = self.chk.find_partial_types(node)
146146
if partial_types is not None and not self.chk.current_node_deferred:
147147
context = partial_types[node]
148-
self.msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
148+
self.msg.need_annotation_for_var(node, context)
149149
result = AnyType(TypeOfAny.special_form)
150150
elif isinstance(node, FuncDef):
151151
# Reference to a global function.

mypy/checkmember.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def freeze_type_vars(member_type: Type) -> None:
366366

367367

368368
def handle_partial_attribute_type(typ: PartialType, is_lvalue: bool, msg: MessageBuilder,
369-
context: Context) -> Type:
369+
node: SymbolNode) -> Type:
370370
if typ.type is None:
371371
# 'None' partial type. It has a well-defined type -- 'None'.
372372
# In an lvalue context we want to preserver the knowledge of
@@ -375,7 +375,7 @@ def handle_partial_attribute_type(typ: PartialType, is_lvalue: bool, msg: Messag
375375
return NoneTyp()
376376
return typ
377377
else:
378-
msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
378+
msg.need_annotation_for_var(node, node)
379379
return AnyType(TypeOfAny.from_error)
380380

381381

0 commit comments

Comments
 (0)