@@ -105,13 +105,15 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a
105105from mypy .util import correct_relative_import
106106from mypy .scope import Scope
107107from mypy .typestate import TypeState
108+ from mypy .options import Options
108109
109110
110111def get_dependencies (target : MypyFile ,
111112 type_map : Dict [Expression , Type ],
112- python_version : Tuple [int , int ]) -> Dict [str , Set [str ]]:
113+ python_version : Tuple [int , int ],
114+ options : Options ) -> Dict [str , Set [str ]]:
113115 """Get all dependencies of a node, recursively."""
114- visitor = DependencyVisitor (type_map , python_version , target .alias_deps )
116+ visitor = DependencyVisitor (type_map , python_version , target .alias_deps , options )
115117 target .accept (visitor )
116118 return visitor .map
117119
@@ -148,7 +150,8 @@ class DependencyVisitor(TraverserVisitor):
148150 def __init__ (self ,
149151 type_map : Dict [Expression , Type ],
150152 python_version : Tuple [int , int ],
151- alias_deps : 'DefaultDict[str, Set[str]]' ) -> None :
153+ alias_deps : 'DefaultDict[str, Set[str]]' ,
154+ options : Optional [Options ] = None ) -> None :
152155 self .scope = Scope ()
153156 self .type_map = type_map
154157 self .python2 = python_version [0 ] == 2
@@ -165,6 +168,7 @@ def __init__(self,
165168 self .map = {} # type: Dict[str, Set[str]]
166169 self .is_class = False
167170 self .is_package_init_file = False
171+ self .options = options
168172
169173 def visit_mypy_file (self , o : MypyFile ) -> None :
170174 self .scope .enter_file (o .fullname ())
@@ -201,6 +205,22 @@ def visit_decorator(self, o: Decorator) -> None:
201205 # generate dependency.
202206 if not o .func .is_overload and self .scope .current_function_name () is None :
203207 self .add_dependency (make_trigger (o .func .fullname ()))
208+ if self .options is not None and self .options .logical_deps :
209+ # Add logical dependencies from decorators to the function. For example,
210+ # if we have
211+ # @dec
212+ # def func(): ...
213+ # then if `dec` is unannotated, then it will "spoil" `func` and consequently
214+ # all call sites, making them all `Any`.
215+ for d in o .decorators :
216+ tname = None # type: Optional[str]
217+ if isinstance (d , RefExpr ) and d .fullname is not None :
218+ tname = d .fullname
219+ if (isinstance (d , CallExpr ) and isinstance (d .callee , RefExpr ) and
220+ d .callee .fullname is not None ):
221+ tname = d .callee .fullname
222+ if tname is not None :
223+ self .add_dependency (make_trigger (tname ), make_trigger (o .func .fullname ()))
204224 super ().visit_decorator (o )
205225
206226 def visit_class_def (self , o : ClassDef ) -> None :
@@ -263,6 +283,18 @@ def process_type_info(self, info: TypeInfo) -> None:
263283 target = make_trigger (info .fullname () + '.' + name ))
264284 for base_info in non_trivial_bases (info ):
265285 for name , node in base_info .names .items ():
286+ if self .options and self .options .logical_deps :
287+ # Skip logical dependency if an attribute is not overridden. For example,
288+ # in case of:
289+ # class Base:
290+ # x = 1
291+ # y = 2
292+ # class Sub(Base):
293+ # x = 3
294+ # we skip <Base.y> -> <Child.y>, because even if `y` is unannotated it
295+ # doesn't affect precision of Liskov checking.
296+ if name not in info .names :
297+ continue
266298 self .add_dependency (make_trigger (base_info .fullname () + '.' + name ),
267299 target = make_trigger (info .fullname () + '.' + name ))
268300 self .add_dependency (make_trigger (base_info .fullname () + '.__init__' ),
@@ -368,6 +400,28 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
368400 if o .type :
369401 for trigger in get_type_triggers (o .type ):
370402 self .add_dependency (trigger )
403+ if self .options and self .options .logical_deps and o .unanalyzed_type is None :
404+ # Special case: for definitions without an explicit type like this:
405+ # x = func(...)
406+ # we add a logical dependency <func> -> <x>, because if `func` is not annotated,
407+ # then it will make all points of use of `x` unchecked.
408+ if (isinstance (rvalue , CallExpr ) and isinstance (rvalue .callee , RefExpr )
409+ and rvalue .callee .fullname is not None ):
410+ fname = None # type: Optional[str]
411+ if isinstance (rvalue .callee .node , TypeInfo ):
412+ # use actual __init__ as a dependency source
413+ init = rvalue .callee .node .get ('__init__' )
414+ if init and isinstance (init .node , FuncBase ):
415+ fname = init .node .fullname ()
416+ else :
417+ fname = rvalue .callee .fullname
418+ if fname is None :
419+ return
420+ for lv in o .lvalues :
421+ if isinstance (lv , RefExpr ) and lv .fullname and lv .is_new_def :
422+ if lv .kind == LDEF :
423+ return # local definitions don't generate logical deps
424+ self .add_dependency (make_trigger (fname ), make_trigger (lv .fullname ))
371425
372426 def process_lvalue (self , lvalue : Expression ) -> None :
373427 """Generate additional dependencies for an lvalue."""
@@ -815,7 +869,8 @@ def has_user_bases(info: TypeInfo) -> bool:
815869
816870def dump_all_dependencies (modules : Dict [str , MypyFile ],
817871 type_map : Dict [Expression , Type ],
818- python_version : Tuple [int , int ]) -> None :
872+ python_version : Tuple [int , int ],
873+ options : Options ) -> None :
819874 """Generate dependencies for all interesting modules and print them to stdout."""
820875 all_deps = {} # type: Dict[str, Set[str]]
821876 for id , node in modules .items ():
@@ -824,7 +879,7 @@ def dump_all_dependencies(modules: Dict[str, MypyFile],
824879 if id in ('builtins' , 'typing' ) or '/typeshed/' in node .path :
825880 continue
826881 assert id == node .fullname ()
827- deps = get_dependencies (node , type_map , python_version )
882+ deps = get_dependencies (node , type_map , python_version , options )
828883 for trigger , targets in deps .items ():
829884 all_deps .setdefault (trigger , set ()).update (targets )
830885 TypeState .add_all_protocol_deps (all_deps )
0 commit comments