Skip to content

Commit 8ec7046

Browse files
authored
Fix dep generation of super() and class names (#4513)
Class names need to generate dependencies to __init__ even when not directly being called, since they could be passed elsewhere and called. We generate spurious dependencies in cases such as classes being referenced in isinstance checks and expect statements, but that's not a big deal. Handling super() deps is bundled in with this because the __init__ fix broke a super() using test that previously worked somewhat by accident.
1 parent d8b884f commit 8ec7046

File tree

5 files changed

+114
-15
lines changed

5 files changed

+114
-15
lines changed

mypy/server/deps.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a
8787
ImportFrom, CallExpr, CastExpr, TypeVarExpr, TypeApplication, IndexExpr, UnaryExpr, OpExpr,
8888
ComparisonExpr, GeneratorExpr, DictionaryComprehension, StarExpr, PrintStmt, ForStmt, WithStmt,
8989
TupleExpr, ListExpr, OperatorAssignmentStmt, DelStmt, YieldFromExpr, Decorator, Block,
90-
TypeInfo, FuncBase, OverloadedFuncDef, RefExpr, Var, NamedTupleExpr, LDEF, MDEF, GDEF,
90+
TypeInfo, FuncBase, OverloadedFuncDef, RefExpr, SuperExpr, Var, NamedTupleExpr,
91+
LDEF, MDEF, GDEF,
9192
op_methods, reverse_op_methods, ops_with_inplace_method, unary_op_methods
9293
)
9394
from mypy.traverser import TraverserVisitor
@@ -154,7 +155,6 @@ def __init__(self,
154155
# protocols
155156
# metaclasses
156157
# type aliases
157-
# super()
158158
# functional enum
159159
# type variable with value restriction
160160

@@ -368,9 +368,18 @@ def visit_del_stmt(self, o: DelStmt) -> None:
368368

369369
# Expressions
370370

371-
# TODO
372-
# dependency on __init__ (e.g. ClassName())
373-
# super()
371+
def process_global_ref_expr(self, o: RefExpr) -> None:
372+
if o.fullname is not None:
373+
self.add_dependency(make_trigger(o.fullname))
374+
375+
# If this is a reference to a type, generate a dependency to its
376+
# constructor.
377+
# TODO: avoid generating spurious dependencies for isinstancce checks,
378+
# except statements, class attribute reference, etc, if perf problem.
379+
typ = self.type_map.get(o)
380+
if isinstance(typ, FunctionLike) and typ.is_type_obj():
381+
class_name = typ.type_object().fullname()
382+
self.add_dependency(make_trigger(class_name + '.__init__'))
374383

375384
def visit_name_expr(self, o: NameExpr) -> None:
376385
if o.kind == LDEF:
@@ -381,17 +390,13 @@ def visit_name_expr(self, o: NameExpr) -> None:
381390
# Direct reference to member is only possible in the scope that
382391
# defined the name, so no dependency is required.
383392
return
384-
if o.fullname is not None:
385-
trigger = make_trigger(o.fullname)
386-
self.add_dependency(trigger)
393+
self.process_global_ref_expr(o)
387394

388395
def visit_member_expr(self, e: MemberExpr) -> None:
389396
super().visit_member_expr(e)
390397
if e.kind is not None:
391398
# Reference to a module attribute
392-
if e.fullname is not None:
393-
trigger = make_trigger(e.fullname)
394-
self.add_dependency(trigger)
399+
self.process_global_ref_expr(e)
395400
else:
396401
# Reference to a non-module attribute
397402
if e.expr not in self.type_map:
@@ -401,12 +406,13 @@ def visit_member_expr(self, e: MemberExpr) -> None:
401406
typ = self.type_map[e.expr]
402407
self.add_attribute_dependency(typ, e.name)
403408

409+
def visit_super_expr(self, e: SuperExpr) -> None:
410+
super().visit_super_expr(e)
411+
if e.info is not None:
412+
self.add_dependency(make_trigger(e.info.fullname() + '.' + e.name))
413+
404414
def visit_call_expr(self, e: CallExpr) -> None:
405415
super().visit_call_expr(e)
406-
callee_type = self.type_map.get(e.callee)
407-
if isinstance(callee_type, FunctionLike) and callee_type.is_type_obj():
408-
class_name = callee_type.type_object().fullname()
409-
self.add_dependency(make_trigger(class_name + '.__init__'))
410416

411417
def visit_cast_expr(self, e: CastExpr) -> None:
412418
super().visit_cast_expr(e)

test-data/unit/deps-classes.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,35 @@ class D:
115115
<m.D.g> -> m.D.g
116116
<m.D> -> m.D
117117
<m.f> -> m.f
118+
119+
[case testClassSuper]
120+
class C:
121+
def __init__(self, x: int) -> None: pass
122+
def foo(self) -> None: pass
123+
124+
class D(C):
125+
def __init__(self, x: int) -> None:
126+
super().__init__(x)
127+
super().foo()
128+
[out]
129+
<m.C.__init__> -> <m.D.__init__>, m.D.__init__
130+
<m.C.foo> -> <m.D.foo>
131+
<m.C> -> m, m.C, m.D
132+
<m.D.__init__> -> m.D.__init__
133+
<m.D.foo> -> m.D.__init__
134+
<m.D> -> m.D
135+
136+
[case testClassMissingInit]
137+
class C:
138+
def __init__(self, x: int) -> None: pass
139+
140+
class D(C):
141+
pass
142+
143+
def foo() -> None:
144+
D(6)
145+
[out]
146+
<m.C.__init__> -> <m.D.__init__>
147+
<m.C> -> m, m.C, m.D
148+
<m.D.__init__> -> m.foo
149+
<m.D> -> m.D, m.foo

test-data/unit/deps-statements.test

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ def g() -> None:
173173
f3()
174174
[builtins fixtures/exception.pyi]
175175
[out]
176+
-- The dependency on the ctor is basically spurious but not a problem
177+
<m.A.__init__> -> m.g
176178
<m.A> -> m.A, m.g
179+
<m.B.__init__> -> m.g
177180
<m.B.f> -> m.g
178181
<m.B> -> m.B, m.g
179182
<m.f1> -> m.g
@@ -195,7 +198,10 @@ def g() -> None:
195198
f2()
196199
[builtins fixtures/exception.pyi]
197200
[out]
201+
-- The dependency on the ctor is basically spurious but not a problem
202+
<m.A.__init__> -> m.g
198203
<m.A> -> m.A, m.g
204+
<m.B.__init__> -> m.g
199205
<m.B> -> m.B, m.g
200206
<m.f1> -> m.g
201207
<m.f2> -> m.g

test-data/unit/deps.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ def f(x: object) -> None:
337337
x.g()
338338
[builtins fixtures/isinstancelist.pyi]
339339
[out]
340+
-- The dependency on the ctor is basically spurious but not a problem
341+
<m.A.__init__> -> m.f
340342
<m.A.g> -> m.f
341343
<m.A> -> m.A, m.f
342344

@@ -353,6 +355,8 @@ def f(x: A) -> None:
353355
[builtins fixtures/isinstancelist.pyi]
354356
[out]
355357
<m.A> -> <m.f>, m.A, m.f
358+
-- The dependency on the ctor is basically spurious but not a problem
359+
<m.B.__init__> -> m.f
356360
<m.B> -> m.B, m.f
357361

358362
[case testAttributeWithClassType1]

test-data/unit/fine-grained.test

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,42 @@ class A:
655655
==
656656
main:4: error: Too few arguments for "A"
657657

658+
[case testConstructorSignatureChanged2]
659+
from typing import Callable
660+
import m
661+
662+
def use(x: Callable[[], m.A]) -> None:
663+
x()
664+
def f() -> None:
665+
use(m.A)
666+
[file m.py]
667+
class A:
668+
def __init__(self) -> None: pass
669+
[file m.py.2]
670+
class A:
671+
def __init__(self, x: int) -> None: pass
672+
[out]
673+
==
674+
-- This is a bad error message
675+
main:7: error: Argument 1 to "use" has incompatible type "Type[A]"; expected "Callable[[], A]"
676+
677+
[case testConstructorSignatureChanged3]
678+
from a import C
679+
class D(C):
680+
def g(self) -> None:
681+
super().__init__()
682+
D()
683+
[file a.py]
684+
class C:
685+
def __init__(self) -> None: pass
686+
[file a.py.2]
687+
class C:
688+
def __init__(self, x: int) -> None: pass
689+
[out]
690+
==
691+
main:4: error: Too few arguments for "__init__" of "C"
692+
main:5: error: Too few arguments for "D"
693+
658694
[case testConstructorAdded]
659695
import m
660696

@@ -700,6 +736,21 @@ class B(A): pass
700736
==
701737
main:4: error: Too few arguments for "B"
702738

739+
[case testSuperField]
740+
from a import C
741+
class D(C):
742+
def g(self) -> int:
743+
return super().x
744+
[file a.py]
745+
class C:
746+
def __init__(self) -> None: self.x = 12
747+
[file a.py.2]
748+
class C:
749+
def __init__(self) -> None: self.x = 'ar'
750+
[out]
751+
==
752+
main:4: error: Incompatible return value type (got "str", expected "int")
753+
703754
[case testImportFrom]
704755
from m import f
705756

0 commit comments

Comments
 (0)