Skip to content

Fix logical fine-grained dependencies for __init__ #5606

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 1 commit into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 28 additions & 17 deletions mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ def visit_func_def(self, o: FuncDef) -> None:
self.add_dependency(trigger, target=make_trigger(target))
if o.info:
for base in non_trivial_bases(o.info):
self.add_dependency(make_trigger(base.fullname() + '.' + o.name()))
# Base class __init__/__new__ doesn't generate a logical
# dependency since the override can be incompatible.
if not self.use_logical_deps() or o.name() not in ('__init__', '__new__'):
self.add_dependency(make_trigger(base.fullname() + '.' + o.name()))
self.add_type_alias_deps(self.scope.current_target())
super().visit_func_def(o)
variants = set(o.expanded) - {o}
Expand Down Expand Up @@ -295,24 +298,32 @@ def process_type_info(self, info: TypeInfo) -> None:
# doesn't affect precision of Liskov checking.
if name not in info.names:
continue
# __init__ and __new__ can be overridden with different signatures, so no
# logical depedency.
if name in ('__init__', '__new__'):
continue
self.add_dependency(make_trigger(base_info.fullname() + '.' + name),
target=make_trigger(info.fullname() + '.' + name))
self.add_dependency(make_trigger(base_info.fullname() + '.__init__'),
target=make_trigger(info.fullname() + '.__init__'))
self.add_dependency(make_trigger(base_info.fullname() + '.__new__'),
target=make_trigger(info.fullname() + '.__new__'))
# If the set of abstract attributes change, this may invalidate class
# instantiation, or change the generated error message, since Python checks
# class abstract status when creating an instance.
#
# TODO: We should probably add this dependency only from the __init__ of the
# current class, and independent of bases (to trigger changes in message
# wording, as errors may enumerate all abstract attributes).
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'),
target=make_trigger(info.fullname() + '.__init__'))
# If the base class abstract attributes change, subclass abstract
# attributes need to be recalculated.
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'))
if not self.use_logical_deps():
# These dependencies are only useful for propagating changes --
# they aren't logical dependencies since __init__ and __new__ can be
# overridden with a different signature.
self.add_dependency(make_trigger(base_info.fullname() + '.__init__'),
target=make_trigger(info.fullname() + '.__init__'))
self.add_dependency(make_trigger(base_info.fullname() + '.__new__'),
target=make_trigger(info.fullname() + '.__new__'))
# If the set of abstract attributes change, this may invalidate class
# instantiation, or change the generated error message, since Python checks
# class abstract status when creating an instance.
#
# TODO: We should probably add this dependency only from the __init__ of the
# current class, and independent of bases (to trigger changes in message
# wording, as errors may enumerate all abstract attributes).
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'),
target=make_trigger(info.fullname() + '.__init__'))
# If the base class abstract attributes change, subclass abstract
# attributes need to be recalculated.
self.add_dependency(make_trigger(base_info.fullname() + '.(abstract)'))

def visit_import(self, o: Import) -> None:
for id, as_id in o.ids:
Expand Down
29 changes: 26 additions & 3 deletions test-data/unit/deps.test
Original file line number Diff line number Diff line change
Expand Up @@ -1244,9 +1244,6 @@ class C:
[out]
<m.D.x> -> m
<m.D> -> m.D
<mod.C.(abstract)> -> <m.D.__init__>, m
<mod.C.__init__> -> <m.D.__init__>
<mod.C.__new__> -> <m.D.__new__>
<mod.C.x> -> <m.D.x>
<mod.C> -> m, m.D

Expand Down Expand Up @@ -1292,3 +1289,29 @@ def gg(x: C) -> None:
<foo.bar.C.y> -> m.gg
<foo.bar.C> -> <m.gg>, m, m.g, m.gg
<foo.bar.f> -> m, m.g

[case testLogical__init__]
# flags: --logical-deps
class A:
def __init__(self) -> None: pass
class B(A): pass
class C(B): pass
class D(A):
def __init__(self, x: int) -> None: pass
def f() -> None:
A()
def g() -> None:
C()
def h() -> None:
D(1)
[out]
<m.A.__init__> -> m.f
<m.A.__new__> -> m.f
<m.A> -> m, m.A, m.B, m.D, m.f
<m.B> -> m, m.B, m.C
<m.C.__init__> -> m.g
<m.C.__new__> -> m.g
<m.C> -> m.C, m.g
<m.D.__init__> -> m.h
<m.D.__new__> -> m.h
<m.D> -> m.D, m.h