Skip to content

Support type aliases in fine-grained incremental mode #4525

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 46 commits into from
Feb 21, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
3eed2a5
Basic implementation
Jan 30, 2018
3e895d6
Fix processing of aliases in base classes and of chained aliases; add…
Jan 31, 2018
8d42ca7
Fix processing of aliases in base classes and of chained aliases; add…
Jan 31, 2018
c0a6ed7
Revert "Fix processing of aliases in base classes and of chained alia…
Jan 31, 2018
54289f0
Skip two tests; minor fixes
Jan 31, 2018
0e365e8
Refsactor dependency update in a method
Jan 31, 2018
8d88890
3.5.1
Jan 31, 2018
e43821f
Review comments 1/3
Feb 1, 2018
2b25e91
Review comments 2/3
Feb 1, 2018
d5306f2
Minor fixes, not done yet with review comments.
Feb 1, 2018
fa6d426
Update some tests as requested. More refactoring needed to record Typ…
Feb 2, 2018
d9b397e
Add more tests
Feb 8, 2018
d7adb15
Add type variables to type aliases dependencies
Feb 8, 2018
3a4b12b
Fix typos
Feb 8, 2018
afa4967
Add few more dependencies
Feb 8, 2018
d19455a
Fix aststrip
Feb 8, 2018
3984464
Fire module trigger if module symbol table changed
Feb 8, 2018
224d02d
Fix lint
Feb 8, 2018
108a40d
Add extra fired triger to the test
Feb 8, 2018
b3a0488
Change the order of messages
Feb 8, 2018
ee1fdc9
Revert "Change the order of messages"
Feb 8, 2018
811be9c
Merge remote-tracking branch 'upstream/master' into support-fine-aliases
Feb 9, 2018
f31e6af
Temporary skip a test
Feb 9, 2018
edabb05
Add one more TODO item
Feb 9, 2018
b3db8e0
Smaller review comments, still need to move the Scope
Feb 9, 2018
7b2a13c
Move the Scope; some clean-up needed
Feb 9, 2018
8b666bb
Merge remote-tracking branch 'upstream/master' into support-fine-aliases
Feb 9, 2018
d1e0d07
Cleanup some logic to match normal (non-aliases) types
Feb 9, 2018
2803764
More clean-up: docstrings, comments. Also added test for class in fun…
Feb 9, 2018
9ad014c
Fix lint
Feb 9, 2018
6e1cc7a
Add missing annotation
Feb 9, 2018
3b9b19e
Avoid long-range (indirect) dependencies for aliases
Feb 19, 2018
e5ab3eb
Add some more deps tests with three modules
Feb 20, 2018
4832e37
Add AST diff tests for aliases
Feb 20, 2018
b08aef8
Remove intermediate type alias deps
Feb 20, 2018
35f4380
Replace full_target with target in alias deps
Feb 20, 2018
a027399
Merge remote-tracking branch 'upstream/master' into support-fine-aliases
Feb 20, 2018
8f3cb32
Don't add deps like <Alias> -> <fun>, only <Alias> -> fun
Feb 20, 2018
e8e01c9
Add two more tests
Feb 20, 2018
b259da4
Fix test name and output; prepare for builtins problem investigation
Feb 20, 2018
1fb320c
Merge remote-tracking branch 'upstream/master' into support-fine-aliases
Feb 20, 2018
6817ba5
Strip IndexExpr
Feb 20, 2018
b8893f0
Add extra dep from __init__ to alias definition scope and correspondi…
Feb 21, 2018
ef7f3b9
More deps tests; allow showing all deps
Feb 21, 2018
86b1bfd
Add remaining required tests
Feb 21, 2018
0a4a23e
Improve two tests
Feb 21, 2018
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
16 changes: 14 additions & 2 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import os
from abc import abstractmethod
from collections import OrderedDict
from collections import OrderedDict, defaultdict
from typing import (
Any, TypeVar, List, Tuple, cast, Set, Dict, Union, Optional, Callable, Sequence,
Any, TypeVar, List, Tuple, cast, Set, Dict, Union, Optional, Callable, Sequence
)

MYPY = False
if MYPY:
from typing import DefaultDict

import mypy.strconv
from mypy.util import short_type
from mypy.visitor import NodeVisitor, StatementVisitor, ExpressionVisitor
Expand Down Expand Up @@ -194,6 +198,8 @@ class MypyFile(SymbolNode):
path = ''
# Top-level definitions and statements
defs = None # type: List[Statement]
# Type alias dependencies as mapping from node to set of alias full names
alias_deps = None # type: DefaultDict[Union[MypyFile, FuncItem, ClassDef], Set[str]]
# Is there a UTF-8 BOM at the start?
is_bom = False
names = None # type: SymbolTable
Expand All @@ -213,6 +219,7 @@ def __init__(self,
self.line = 1 # Dummy line number
self.imports = imports
self.is_bom = is_bom
self.alias_deps = defaultdict(set)
if ignored_lines:
self.ignored_lines = ignored_lines
else:
Expand Down Expand Up @@ -2331,6 +2338,10 @@ class SymbolTableNode:
normalized = False # type: bool
# Was this defined by assignment to self attribute?
implicit = False # type: bool
# Is this node refers to other node via node aliasing?
# (This is currently used for simple aliases like `A = int` instead of .type_override)
is_aliasing = False # type: bool
alias_depends_on = None # type: Set[str]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docstring for alias_depends_on.

There are starting to be quite a few alias-related attributes in symbol table nodes. Maybe it's time to add a new AST node for type aliases soon after this PR has been merged so that we can remove several of these attributes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's time to add a new AST node for type aliases soon after this PR has been merged so that we can remove several of these attributes?

Yes, I will take care of it as part of #4082

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this actually is necessary. Even without this, the target that contains the alias definition should depend on these aliases, right? And change in the alias should be detected by mypy.server.astdiff. If you remove this, what will break? Maybe some symbol table differences won't then be propagated correctly -- but that could just be a bug elsewhere, which this papers over.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this shouldn't be necessary. But IIRC correctly some tests failed without this. I added a TODO about this in typeanal.py (that you also commented). I will try to remove this and re-run the tests, and if some will still fail, I will try to figure out where exactly is the problem in diff calculation and expand the TODO.


def __init__(self,
kind: int,
Expand All @@ -2349,6 +2360,7 @@ def __init__(self,
self.alias_tvars = alias_tvars
self.implicit = implicit
self.module_hidden = module_hidden
self.alias_depends_on = set()

@property
def fullname(self) -> Optional[str]:
Expand Down
60 changes: 49 additions & 11 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,9 +597,13 @@ def analyze_function(self, defn: FuncItem) -> None:
assert isinstance(defn.type, CallableType)
# Signature must be analyzed in the surrounding scope so that
# class-level imported names and type variables are in scope.
defn.type = self.type_analyzer().visit_callable_type(defn.type, nested=False)
analyzer = self.type_analyzer()
defn.type = analyzer.visit_callable_type(defn.type, nested=False)
if analyzer.aliases_used:
self.cur_mod_node.alias_deps[defn].update(analyzer.aliases_used)
self.check_function_signature(defn)
if isinstance(defn, FuncDef):
assert isinstance(defn.type, CallableType)
defn.type = set_callable_name(defn.type, defn)
for arg in defn.arguments:
if arg.initializer:
Expand Down Expand Up @@ -1066,7 +1070,7 @@ def analyze_base_classes(self, defn: ClassDef) -> None:

for base_expr in defn.base_type_exprs:
try:
base = self.expr_to_analyzed_type(base_expr)
base = self.expr_to_analyzed_type(base_expr, from_bases=defn)
except TypeTranslationError:
self.fail('Invalid base class', base_expr)
info.fallback_to_any = True
Expand Down Expand Up @@ -1178,7 +1182,8 @@ def update_metaclass(self, defn: ClassDef) -> None:
return
defn.metaclass = metas.pop()

def expr_to_analyzed_type(self, expr: Expression) -> Type:
def expr_to_analyzed_type(self, expr: Expression, *,
from_bases: Optional[ClassDef] = None) -> Type:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of argument from_bases is unclear. Maybe rename it and also document in a docstring.

if isinstance(expr, CallExpr):
expr.accept(self)
info = self.check_namedtuple(expr)
Expand All @@ -1190,7 +1195,7 @@ def expr_to_analyzed_type(self, expr: Expression) -> Type:
fallback = Instance(info, [])
return TupleType(info.tuple_type.items, fallback=fallback)
typ = expr_to_unanalyzed_type(expr)
return self.anal_type(typ)
return self.anal_type(typ, from_bases=from_bases)

def verify_base_classes(self, defn: ClassDef) -> bool:
info = defn.info
Expand Down Expand Up @@ -1674,12 +1679,32 @@ def anal_type(self, t: Type, *,
tvar_scope: Optional[TypeVarScope] = None,
allow_tuple_literal: bool = False,
aliasing: bool = False,
third_pass: bool = False) -> Type:
third_pass: bool = False,
from_bases: Optional[ClassDef] = None) -> Type:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another instance of the from_bases that could be renamed.

a = self.type_analyzer(tvar_scope=tvar_scope,
aliasing=aliasing,
allow_tuple_literal=allow_tuple_literal,
third_pass=third_pass)
return t.accept(a)
tp = t.accept(a)
if a.aliases_used:
if from_bases is not None:
self.cur_mod_node.alias_deps[from_bases].update(a.aliases_used)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code similar to this line of code (self.cur_mod_node.alias_deps[?].update(?)) is repeated a few times in this file. It would make sense to have a helper method for this. Perhaps just update add_alias_deps to accept an optional argument that allows overriding the target AST node.

return tp
self.add_alias_deps(a.aliases_used)
return tp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is a bit convoluted. What about rewriting this like this:

        if a.aliases_used:
            if from_bases is not None:
                self.cur_mod_node.alias_deps[from_bases].update(a.aliases_used)
            else:
                self.add_alias_deps(a.aliases_used)
        return t.accept(a)


def add_alias_deps(self, aliases_used: Set[str]) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming to add_type_alias_deps for a slightly more descriptive name (since 'alias' is such a generic term).

"""Add full names of type aliases on which the current node depends.

This is used by fine-grained incremental mode to re-chek the corresponding nodes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: re-chek

"""
if self.is_class_scope():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we use self.cur_node here instead of self.type etc.?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no cur_node in pass two (only in pass three). Instead, in pass two there are two separate scope stacks: function stack and class stack. Maybe this can be simplified, but I think it would be a large refactoring for relatively little benefits.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you test that classes nested within function work as expected? The fine-grained incremental target will be the enclosing function.

If you follow my idea above where we track the current fine-grained incremental target, this could perhaps be simplified significantly.

assert self.type is not None, "Type not set at class scope"
self.cur_mod_node.alias_deps[self.type.defn].update(aliases_used)
elif self.is_func_scope():
self.cur_mod_node.alias_deps[self.function_stack[-1]].update(aliases_used)
else:
self.cur_mod_node.alias_deps[self.cur_mod_node].update(aliases_used)

def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
for lval in s.lvalues:
Expand Down Expand Up @@ -1755,7 +1780,7 @@ def alias_fallback(self, tp: Type) -> Instance:
return Instance(fb_info, [])

def analyze_alias(self, rvalue: Expression,
warn_bound_tvar: bool = False) -> Tuple[Optional[Type], List[str]]:
warn_bound_tvar: bool = False) -> Tuple[Optional[Type], List[str], Set[str]]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update docstring to reflect the new return type.

"""Check if 'rvalue' represents a valid type allowed for aliasing
(e.g. not a type variable). If yes, return the corresponding type and a list of
qualified type variable names for generic aliases.
Expand All @@ -1775,12 +1800,15 @@ def analyze_alias(self, rvalue: Expression,
in_dynamic_func=dynamic,
global_scope=global_scope,
warn_bound_tvar=warn_bound_tvar)
tp = None # type: Optional[Type]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: we generally use typ for variables of type Type instead of tp.

if res:
tp, depends_on = res
alias_tvars = [name for (name, _) in
res.accept(TypeVariableQuery(self.lookup_qualified, self.tvar_scope))]
tp.accept(TypeVariableQuery(self.lookup_qualified, self.tvar_scope))]
else:
alias_tvars = []
return res, alias_tvars
depends_on = set()
return tp, alias_tvars, depends_on

def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None:
"""Check if assignment creates a type alias and set it up as needed.
Expand Down Expand Up @@ -1809,11 +1837,18 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None:
# annotations (see the second rule).
return
rvalue = s.rvalue
res, alias_tvars = self.analyze_alias(rvalue, warn_bound_tvar=True)
res, alias_tvars, depends_on = self.analyze_alias(rvalue, warn_bound_tvar=True)
if not res:
return
node = self.lookup(lvalue.name, lvalue)
assert node is not None
node.alias_depends_on = depends_on.copy()
if lvalue.fullname is not None:
# To avoid extra attributes on SymbolTableNode we add the fullname
# of alias to what it depends on.
node.alias_depends_on.add(lvalue.fullname)
if depends_on:
self.add_alias_deps(depends_on)
if not lvalue.is_inferred_def:
# Type aliases can't be re-defined.
if node and (node.kind == TYPE_ALIAS or isinstance(node.node, TypeInfo)):
Expand All @@ -1830,6 +1865,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None:
# For simple (on-generic) aliases we use aliasing TypeInfo's
# to allow using them in runtime context where it makes sense.
node.node = res.type
node.is_aliasing = True
if isinstance(rvalue, RefExpr):
sym = self.lookup_type_node(rvalue)
if sym:
Expand Down Expand Up @@ -3439,12 +3475,14 @@ def visit_index_expr(self, expr: IndexExpr) -> None:
elif isinstance(expr.base, RefExpr) and expr.base.kind == TYPE_ALIAS:
# Special form -- subscripting a generic type alias.
# Perform the type substitution and create a new alias.
res, alias_tvars = self.analyze_alias(expr)
res, alias_tvars, depends_on = self.analyze_alias(expr)
assert res is not None, "Failed analyzing already defined alias"
expr.analyzed = TypeAliasExpr(res, alias_tvars, fallback=self.alias_fallback(res),
in_runtime=True)
expr.analyzed.line = expr.line
expr.analyzed.column = expr.column
if depends_on: # for situations like `L = LongGeneric; x = L[int]()`
self.add_alias_deps(depends_on)
elif refers_to_class_or_function(expr.base):
# Special form -- type application.
# Translate index to an unanalyzed type.
Expand Down
12 changes: 12 additions & 0 deletions mypy/semanal_pass3.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options,
self.patches = patches
self.is_typeshed_file = self.errors.is_typeshed_file(fnam)
self.sem.cur_mod_id = file_node.fullname()
self.cur_mod_node = file_node
self.cur_node = file_node # type: Union[MypyFile, FuncItem, ClassDef]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment about the purpose of this.

self.sem.globals = file_node.names
with experiments.strict_optional_set(options.strict_optional):
self.accept(file_node)
Expand Down Expand Up @@ -92,8 +94,11 @@ def visit_func_def(self, fdef: FuncDef) -> None:
if not self.recurse_into_functions:
return
self.errors.push_function(fdef.name())
old_node = self.cur_node
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about having a context manager that temporarily sets self.cur_node and using it here (and elsewhere where it makes sense)?

self.cur_node = fdef
self.analyze(fdef.type, fdef)
super().visit_func_def(fdef)
self.cur_node = old_node
self.errors.pop_function()

def visit_overloaded_func_def(self, fdef: OverloadedFuncDef) -> None:
Expand Down Expand Up @@ -134,7 +139,10 @@ def visit_class_def(self, tdef: ClassDef) -> None:
elif isinstance(tdef.analyzed, NamedTupleExpr):
self.analyze(tdef.analyzed.info.tuple_type, tdef.analyzed, warn=True)
self.analyze_info(tdef.analyzed.info)
old_node = self.cur_node
self.cur_node = tdef
super().visit_class_def(tdef)
self.cur_node = old_node

def visit_decorator(self, dec: Decorator) -> None:
"""Try to infer the type of the decorated function.
Expand Down Expand Up @@ -353,6 +361,8 @@ def analyze(self, type: Optional[Type], node: Union[Node, SymbolTableNode],
type.accept(analyzer)
self.check_for_omitted_generics(type)
self.generate_type_patches(node, indicator, warn)
if analyzer.aliases_used:
self.cur_mod_node.alias_deps[self.cur_node].update(analyzer.aliases_used)

def analyze_types(self, types: List[Type], node: Node) -> None:
# Similar to above but for nodes with multiple types.
Expand All @@ -361,6 +371,8 @@ def analyze_types(self, types: List[Type], node: Node) -> None:
analyzer = self.make_type_analyzer(indicator)
type.accept(analyzer)
self.check_for_omitted_generics(type)
if analyzer.aliases_used:
self.cur_mod_node.alias_deps[self.cur_node].update(analyzer.aliases_used)
self.generate_type_patches(node, indicator, warn=False)

def generate_type_patches(self,
Expand Down
21 changes: 19 additions & 2 deletions mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,18 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a

from typing import Dict, List, Set, Optional, Tuple, Union

MYPY = False
if MYPY:
from typing import DefaultDict

from mypy.checkmember import bind_self
from mypy.nodes import (
Node, Expression, MypyFile, FuncDef, ClassDef, AssignmentStmt, NameExpr, MemberExpr, Import,
ImportFrom, CallExpr, CastExpr, TypeVarExpr, TypeApplication, IndexExpr, UnaryExpr, OpExpr,
ComparisonExpr, GeneratorExpr, DictionaryComprehension, StarExpr, PrintStmt, ForStmt, WithStmt,
TupleExpr, ListExpr, OperatorAssignmentStmt, DelStmt, YieldFromExpr, Decorator, Block,
TypeInfo, FuncBase, OverloadedFuncDef, RefExpr, SuperExpr, Var, NamedTupleExpr, TypedDictExpr,
LDEF, MDEF, GDEF,
LDEF, MDEF, GDEF, FuncItem,
op_methods, reverse_op_methods, ops_with_inplace_method, unary_op_methods
)
from mypy.traverser import TraverserVisitor
Expand All @@ -106,17 +110,20 @@ def get_dependencies(target: MypyFile,
python_version: Tuple[int, int]) -> Dict[str, Set[str]]:
"""Get all dependencies of a node, recursively."""
visitor = DependencyVisitor(type_map, python_version)
visitor.alias_deps = target.alias_deps
target.accept(visitor)
return visitor.map


def get_dependencies_of_target(module_id: str,
module_tree: MypyFile,
target: Node,
type_map: Dict[Expression, Type],
python_version: Tuple[int, int]) -> Dict[str, Set[str]]:
"""Get dependencies of a target -- don't recursive into nested targets."""
# TODO: Add tests for this function.
visitor = DependencyVisitor(type_map, python_version)
visitor.alias_deps = module_tree.alias_deps
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of directly modifying an attribute of visitor, it would be better style to pass module_tree.alias_deps as an argument, likely to enter_file.

visitor.scope.enter_file(module_id)
if isinstance(target, MypyFile):
# Only get dependencies of the top-level of the module. Don't recurse into
Expand All @@ -138,6 +145,8 @@ def get_dependencies_of_target(module_id: str,


class DependencyVisitor(TraverserVisitor):
alias_deps = None # type: DefaultDict[Union[MypyFile, FuncItem, ClassDef], Set[str]]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment describing the purpose of this.


def __init__(self,
type_map: Dict[Expression, Type],
python_version: Tuple[int, int]) -> None:
Expand All @@ -153,13 +162,15 @@ def __init__(self,
# await
# protocols
# metaclasses
# type aliases
# functional enum
# type variable with value restriction

def visit_mypy_file(self, o: MypyFile) -> None:
self.scope.enter_file(o.fullname())
self.is_package_init_file = o.is_package_init_file()
if o in self.alias_deps:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three lines are repeated multiple times so it would be better to put them in a helper method.

for alias in self.alias_deps[o]:
self.add_dependency(make_trigger(alias))
super().visit_mypy_file(o)
self.scope.leave()

Expand All @@ -177,6 +188,9 @@ def visit_func_def(self, o: FuncDef) -> None:
if o.info:
for base in non_trivial_bases(o.info):
self.add_dependency(make_trigger(base.fullname() + '.' + o.name()))
if o in self.alias_deps:
for alias in self.alias_deps[o]:
self.add_dependency(make_trigger(alias))
super().visit_func_def(o)
self.scope.leave()

Expand All @@ -202,6 +216,9 @@ def visit_class_def(self, o: ClassDef) -> None:
self.add_type_dependencies(o.info.typeddict_type, target=make_trigger(target))
# TODO: Add dependencies based on remaining TypeInfo attributes.
super().visit_class_def(o)
if o in self.alias_deps:
for alias in self.alias_deps[o]:
self.add_dependency(make_trigger(alias))
self.is_class = old_is_class
info = o.info
for name, node in info.names.items():
Expand Down
5 changes: 4 additions & 1 deletion mypy/server/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,10 @@ def update_deps(module_id: str,
for deferred in nodes:
node = deferred.node
type_map = graph[module_id].type_map()
new_deps = get_dependencies_of_target(module_id, node, type_map, options.python_version)
tree = graph[module_id].tree
assert tree is not None, "Tree must be processed at this stage"
new_deps = get_dependencies_of_target(module_id, tree, node, type_map,
options.python_version)
for trigger, targets in new_deps.items():
deps.setdefault(trigger, set()).update(targets)

Expand Down
Loading