116
116
import time
117
117
import os .path
118
118
from typing import (
119
- Dict , List , Set , Tuple , Iterable , Union , Optional , Mapping , NamedTuple , Callable
119
+ Dict , List , Set , Tuple , Iterable , Union , Optional , Mapping , NamedTuple , Callable ,
120
+ Sequence ,
120
121
)
121
122
122
123
from mypy .build import (
123
124
BuildManager , State , BuildSource , BuildResult , Graph , load_graph , module_not_found ,
125
+ process_fresh_scc ,
124
126
PRI_INDIRECT , DEBUG_FINE_GRAINED ,
125
127
)
126
128
from mypy .checker import DeferredNode
127
129
from mypy .errors import Errors , CompileError
128
130
from mypy .nodes import (
129
131
MypyFile , FuncDef , TypeInfo , Expression , SymbolNode , Var , FuncBase , ClassDef , Decorator ,
130
- Import , ImportFrom , OverloadedFuncDef , SymbolTable , LambdaExpr
132
+ Import , ImportFrom , OverloadedFuncDef , SymbolTable , LambdaExpr , UnloadedMypyFile
131
133
)
132
134
from mypy .options import Options
133
135
from mypy .types import Type
@@ -324,6 +326,10 @@ def update_module(self,
324
326
previous_modules = self .previous_modules
325
327
graph = self .graph
326
328
329
+ # If this is an already existing module, make sure that we have
330
+ # its tree loaded so that we can snapshot it for comparison.
331
+ ensure_trees_loaded (manager , graph , [module ])
332
+
327
333
# Record symbol table snaphot of old version the changed module.
328
334
old_snapshots = {} # type: Dict[str, Dict[str, SnapshotItem]]
329
335
if module in manager .modules :
@@ -361,6 +367,45 @@ def update_module(self,
361
367
return remaining , (module , path ), None
362
368
363
369
370
+ def find_unloaded_deps (manager : BuildManager , graph : Dict [str , State ],
371
+ initial : Sequence [str ]) -> List [str ]:
372
+ """Find all the deps of the nodes in initial that haven't had their tree loaded.
373
+
374
+ The key invariant here is that if a module is loaded, so are all
375
+ of their dependencies. This means that when we encounter a loaded
376
+ module, we don't need to explore its dependencies. (This
377
+ invariant is slightly violated when dependencies are added, which
378
+ can be handled by calling find_unloaded_deps directly on the new
379
+ dependencies)
380
+ """
381
+ worklist = list (initial )
382
+ seen = set () # type: Set[str]
383
+ unloaded = []
384
+ while worklist :
385
+ node = worklist .pop ()
386
+ if node in seen or node not in graph :
387
+ continue
388
+ seen .add (node )
389
+ if node not in manager .modules :
390
+ continue
391
+ if isinstance (manager .modules [node ], UnloadedMypyFile ):
392
+ ancestors = graph [node ].ancestors or []
393
+ worklist .extend (graph [node ].dependencies + ancestors )
394
+ unloaded .append (node )
395
+
396
+ return unloaded
397
+
398
+
399
+ def ensure_trees_loaded (manager : BuildManager , graph : Dict [str , State ],
400
+ initial : Sequence [str ]) -> None :
401
+ """Ensure that the modules in initial and their deps have loaded trees"""
402
+ to_process = find_unloaded_deps (manager , graph , initial )
403
+ if to_process :
404
+ manager .log ("Calling process_fresh_scc on an 'scc' of size {} ({})" .format (
405
+ len (to_process ), to_process ))
406
+ process_fresh_scc (graph , to_process , manager )
407
+
408
+
364
409
def get_all_dependencies (manager : BuildManager , graph : Dict [str , State ]) -> Dict [str , Set [str ]]:
365
410
"""Return the fine-grained dependency map for an entire build."""
366
411
# Deps for each module were computed during build() or loaded from the cache.
@@ -445,8 +490,15 @@ def update_module_isolated(module: str,
445
490
remaining_modules = []
446
491
return BlockedUpdate (err .module_with_blocker , path , remaining_modules , err .messages )
447
492
493
+ # Reparsing the file may have brought in dependencies that we
494
+ # didn't have before. Make sure that they are loaded to restore
495
+ # the invariant that a module having a loaded tree implies that
496
+ # its dependencies do as well.
497
+ ensure_trees_loaded (manager , graph , graph [module ].dependencies )
498
+
448
499
# Find any other modules brought in by imports.
449
500
changed_modules = get_all_changed_modules (module , path , previous_modules , graph )
501
+
450
502
# If there are multiple modules to process, only process one of them and return
451
503
# the remaining ones to the caller.
452
504
if len (changed_modules ) > 1 :
@@ -673,7 +725,7 @@ def propagate_changes_using_dependencies(
673
725
a target that needs to be reprocessed but that has not been parsed yet."""
674
726
675
727
num_iter = 0
676
- remaining_modules = []
728
+ remaining_modules = [] # type: List[Tuple[str, str]]
677
729
678
730
# Propagate changes until nothing visible has changed during the last
679
731
# iteration.
@@ -682,7 +734,9 @@ def propagate_changes_using_dependencies(
682
734
if num_iter > MAX_ITER :
683
735
raise RuntimeError ('Max number of iterations (%d) reached (endless loop?)' % MAX_ITER )
684
736
685
- todo = find_targets_recursive (manager , triggered , deps , up_to_date_modules )
737
+ todo , unloaded = find_targets_recursive (manager , triggered , deps , up_to_date_modules )
738
+ # TODO: we sort to make it deterministic, but this is *incredibly* ad hoc
739
+ remaining_modules .extend ((id , graph [id ].xpath ) for id in sorted (unloaded ))
686
740
# Also process targets that used to have errors, as otherwise some
687
741
# errors might be lost.
688
742
for target in targets_with_errors :
@@ -696,13 +750,7 @@ def propagate_changes_using_dependencies(
696
750
# TODO: Preserve order (set is not optimal)
697
751
for id , nodes in sorted (todo .items (), key = lambda x : x [0 ]):
698
752
assert id not in up_to_date_modules
699
- if manager .modules [id ].is_cache_skeleton :
700
- # We have only loaded the cache for this file, not the actual file,
701
- # so we can't access the nodes to reprocess.
702
- # Add it to the queue of files that need to be processed fully.
703
- remaining_modules .append ((id , manager .modules [id ].path ))
704
- else :
705
- triggered |= reprocess_nodes (manager , graph , id , nodes , deps )
753
+ triggered |= reprocess_nodes (manager , graph , id , nodes , deps )
706
754
# Changes elsewhere may require us to reprocess modules that were
707
755
# previously considered up to date. For example, there may be a
708
756
# dependency loop that loops back to an originally processed module.
@@ -718,14 +766,18 @@ def find_targets_recursive(
718
766
manager : BuildManager ,
719
767
triggers : Set [str ],
720
768
deps : Dict [str , Set [str ]],
721
- up_to_date_modules : Set [str ]) -> Dict [str , Set [DeferredNode ]]:
769
+ up_to_date_modules : Set [str ]) -> Tuple [Dict [str , Set [DeferredNode ]],
770
+ Set [str ]]:
722
771
"""Find names of all targets that need to reprocessed, given some triggers.
723
772
724
- Returns: Dictionary from module id to a set of stale targets.
773
+ Returns: a tuple containing a:
774
+ * Dictionary from module id to a set of stale targets.
775
+ * A set of module ids for unparsed modules with stale targets
725
776
"""
726
777
result = {} # type: Dict[str, Set[DeferredNode]]
727
778
worklist = triggers
728
779
processed = set () # type: Set[str]
780
+ unloaded_files = set () # type: Set[str]
729
781
730
782
# Find AST nodes corresponding to each target.
731
783
#
@@ -745,13 +797,21 @@ def find_targets_recursive(
745
797
if module_id in up_to_date_modules :
746
798
# Already processed.
747
799
continue
800
+ if (module_id not in manager .modules
801
+ or manager .modules [module_id ].is_cache_skeleton ):
802
+ # We haven't actually parsed and checked the module, so we don't have
803
+ # access to the actual nodes.
804
+ # Add it to the queue of files that need to be processed fully.
805
+ unloaded_files .add (module_id )
806
+ continue
807
+
748
808
if module_id not in result :
749
809
result [module_id ] = set ()
750
810
manager .log_fine_grained ('process: %s' % target )
751
811
deferred = lookup_target (manager , target )
752
812
result [module_id ].update (deferred )
753
813
754
- return result
814
+ return ( result , unloaded_files )
755
815
756
816
757
817
def reprocess_nodes (manager : BuildManager ,
0 commit comments