Skip to content

Commit 28b5423

Browse files
authored
Fix logical fine-grained dependencies for __init__ (#5606)
This fixes many spurious logical fine-grained dependencies from __init__ methods. Since __init__ can be overridden with an incompatible signature, usually we don't need a dependency from base class __init__. The fix only applies when using `--logical-deps`.
1 parent 9b9e7e1 commit 28b5423

File tree

2 files changed

+54
-20
lines changed

2 files changed

+54
-20
lines changed

mypy/server/deps.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,10 @@ def visit_func_def(self, o: FuncDef) -> None:
190190
self.add_dependency(trigger, target=make_trigger(target))
191191
if o.info:
192192
for base in non_trivial_bases(o.info):
193-
self.add_dependency(make_trigger(base.fullname() + '.' + o.name()))
193+
# Base class __init__/__new__ doesn't generate a logical
194+
# dependency since the override can be incompatible.
195+
if not self.use_logical_deps() or o.name() not in ('__init__', '__new__'):
196+
self.add_dependency(make_trigger(base.fullname() + '.' + o.name()))
194197
self.add_type_alias_deps(self.scope.current_target())
195198
super().visit_func_def(o)
196199
variants = set(o.expanded) - {o}
@@ -295,24 +298,32 @@ def process_type_info(self, info: TypeInfo) -> None:
295298
# doesn't affect precision of Liskov checking.
296299
if name not in info.names:
297300
continue
301+
# __init__ and __new__ can be overridden with different signatures, so no
302+
# logical depedency.
303+
if name in ('__init__', '__new__'):
304+
continue
298305
self.add_dependency(make_trigger(base_info.fullname() + '.' + name),
299306
target=make_trigger(info.fullname() + '.' + name))
300-
self.add_dependency(make_trigger(base_info.fullname() + '.__init__'),
301-
target=make_trigger(info.fullname() + '.__init__'))
302-
self.add_dependency(make_trigger(base_info.fullname() + '.__new__'),
303-
target=make_trigger(info.fullname() + '.__new__'))
304-
# If the set of abstract attributes change, this may invalidate class
305-
# instantiation, or change the generated error message, since Python checks
306-
# class abstract status when creating an instance.
307-
#
308-
# TODO: We should probably add this dependency only from the __init__ of the
309-
# current class, and independent of bases (to trigger changes in message
310-
# wording, as errors may enumerate all abstract attributes).
311-
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'),
312-
target=make_trigger(info.fullname() + '.__init__'))
313-
# If the base class abstract attributes change, subclass abstract
314-
# attributes need to be recalculated.
315-
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'))
307+
if not self.use_logical_deps():
308+
# These dependencies are only useful for propagating changes --
309+
# they aren't logical dependencies since __init__ and __new__ can be
310+
# overridden with a different signature.
311+
self.add_dependency(make_trigger(base_info.fullname() + '.__init__'),
312+
target=make_trigger(info.fullname() + '.__init__'))
313+
self.add_dependency(make_trigger(base_info.fullname() + '.__new__'),
314+
target=make_trigger(info.fullname() + '.__new__'))
315+
# If the set of abstract attributes change, this may invalidate class
316+
# instantiation, or change the generated error message, since Python checks
317+
# class abstract status when creating an instance.
318+
#
319+
# TODO: We should probably add this dependency only from the __init__ of the
320+
# current class, and independent of bases (to trigger changes in message
321+
# wording, as errors may enumerate all abstract attributes).
322+
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'),
323+
target=make_trigger(info.fullname() + '.__init__'))
324+
# If the base class abstract attributes change, subclass abstract
325+
# attributes need to be recalculated.
326+
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'))
316327

317328
def visit_import(self, o: Import) -> None:
318329
for id, as_id in o.ids:

test-data/unit/deps.test

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,9 +1244,6 @@ class C:
12441244
[out]
12451245
<m.D.x> -> m
12461246
<m.D> -> m.D
1247-
<mod.C.(abstract)> -> <m.D.__init__>, m
1248-
<mod.C.__init__> -> <m.D.__init__>
1249-
<mod.C.__new__> -> <m.D.__new__>
12501247
<mod.C.x> -> <m.D.x>
12511248
<mod.C> -> m, m.D
12521249

@@ -1292,3 +1289,29 @@ def gg(x: C) -> None:
12921289
<foo.bar.C.y> -> m.gg
12931290
<foo.bar.C> -> <m.gg>, m, m.g, m.gg
12941291
<foo.bar.f> -> m, m.g
1292+
1293+
[case testLogical__init__]
1294+
# flags: --logical-deps
1295+
class A:
1296+
def __init__(self) -> None: pass
1297+
class B(A): pass
1298+
class C(B): pass
1299+
class D(A):
1300+
def __init__(self, x: int) -> None: pass
1301+
def f() -> None:
1302+
A()
1303+
def g() -> None:
1304+
C()
1305+
def h() -> None:
1306+
D(1)
1307+
[out]
1308+
<m.A.__init__> -> m.f
1309+
<m.A.__new__> -> m.f
1310+
<m.A> -> m, m.A, m.B, m.D, m.f
1311+
<m.B> -> m, m.B, m.C
1312+
<m.C.__init__> -> m.g
1313+
<m.C.__new__> -> m.g
1314+
<m.C> -> m.C, m.g
1315+
<m.D.__init__> -> m.h
1316+
<m.D.__new__> -> m.h
1317+
<m.D> -> m.D, m.h

0 commit comments

Comments
 (0)