Skip to content

Fix some daemon crashes involving classes becoming generic #8157

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 6 commits into from
Dec 18, 2019
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
24 changes: 14 additions & 10 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,29 +347,33 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
template.type.has_base(instance.type.fullname)):
mapped = map_instance_to_supertype(template, instance.type)
tvars = mapped.type.defn.type_vars
for i in range(len(instance.args)):
# N.B: We use zip instead of indexing because the lengths might have
# mismatches during daemon reprocessing.
for tvar, mapped_arg, instance_arg in zip(tvars, mapped.args, instance.args):
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's kind of unfortunate that we have to do this. Another option might be to make args a property and have it adjust the number of args dynamically if the TypeInfo has changed. This could have performance impliciations, however.

# The constraints for generic type parameters depend on variance.
# Include constraints from both directions if invariant.
if tvars[i].variance != CONTRAVARIANT:
if tvar.variance != CONTRAVARIANT:
res.extend(infer_constraints(
mapped.args[i], instance.args[i], self.direction))
if tvars[i].variance != COVARIANT:
mapped_arg, instance_arg, self.direction))
if tvar.variance != COVARIANT:
res.extend(infer_constraints(
mapped.args[i], instance.args[i], neg_op(self.direction)))
mapped_arg, instance_arg, neg_op(self.direction)))
return res
elif (self.direction == SUPERTYPE_OF and
instance.type.has_base(template.type.fullname)):
mapped = map_instance_to_supertype(instance, template.type)
tvars = template.type.defn.type_vars
for j in range(len(template.args)):
# N.B: We use zip instead of indexing because the lengths might have
# mismatches during daemon reprocessing.
for tvar, mapped_arg, template_arg in zip(tvars, mapped.args, template.args):
# The constraints for generic type parameters depend on variance.
# Include constraints from both directions if invariant.
if tvars[j].variance != CONTRAVARIANT:
if tvar.variance != CONTRAVARIANT:
res.extend(infer_constraints(
template.args[j], mapped.args[j], self.direction))
if tvars[j].variance != COVARIANT:
template_arg, mapped_arg, self.direction))
if tvar.variance != COVARIANT:
res.extend(infer_constraints(
template.args[j], mapped.args[j], neg_op(self.direction)))
template_arg, mapped_arg, neg_op(self.direction)))
return res
if (template.type.is_protocol and self.direction == SUPERTYPE_OF and
# We avoid infinite recursion for structural subtypes by checking
Expand Down
6 changes: 4 additions & 2 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,8 +345,10 @@ def join_instances(t: Instance, s: Instance) -> ProperType:
if is_subtype(t, s) or is_subtype(s, t):
# Compatible; combine type arguments.
args = [] # type: List[Type]
for i in range(len(t.args)):
args.append(join_types(t.args[i], s.args[i]))
# N.B: We use zip instead of indexing because the lengths might have
# mismatches during daemon reprocessing.
for ta, sa in zip(t.args, s.args):
args.append(join_types(ta, sa))
return Instance(t.type, args)
else:
# Incompatible; return trivial result object.
Expand Down
6 changes: 4 additions & 2 deletions mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,10 @@ def visit_instance(self, t: Instance) -> ProperType:
# Combine type arguments. We could have used join below
# equivalently.
args = [] # type: List[Type]
for i in range(len(t.args)):
args.append(self.meet(t.args[i], si.args[i]))
# N.B: We use zip instead of indexing because the lengths might have
# mismatches during daemon reprocessing.
for ta, sia in zip(t.args, si.args):
args.append(self.meet(ta, sia))
return Instance(t.type, args)
else:
if state.strict_optional:
Expand Down
89 changes: 89 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -9288,3 +9288,92 @@ class B:
self.x = 0
[out]
==

[case testGenericChange1]
import a
[file a.py]
import b
def f() -> b.C: pass
[file b.py]
import a
class C: pass
[file b.py.2]
from typing import TypeVar, Generic, List
import a

T = TypeVar('T')
class C(Generic[T]): pass

reveal_type(a.f)
c: C[int]
l = a.f() if True else c
d = a.f()
d = c
c = d

x: List[C] = [a.f(), a.f()]

[out]
==
b.py:7: note: Revealed type is 'def () -> b.C[Any]'
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 a similar test case where a class goes from generic to non-generic?

[builtins fixtures/list.pyi]

[case testGenericChange2]
import a
[file a.py]
import b
def f() -> b.C[int]: pass
[file b.py]
from typing import TypeVar, Generic
import a
T = TypeVar('T')
class C(Generic[T]): pass
[file b.py.2]
from typing import List
import a

class C(): pass

c: C
l = a.f() if True else c
d = a.f()
d = c
c = d

x: List[C] = [a.f(), a.f()]

[builtins fixtures/list.pyi]
[out]
==
a.py:2: error: "C" expects no type arguments, but 1 given

[case testGenericChange3]
import a
[file a.py]
import b
def f() -> b.C[int]: pass
[file b.py]
from typing import TypeVar, Generic
import a
T = TypeVar('T')
class C(Generic[T]): pass
[file b.py.2]
from typing import TypeVar, Generic, List
import a

T = TypeVar('T')
S = TypeVar('S')
class C(Generic[S, T]): pass

c: C[int, str]
l = a.f() if True else c
d = a.f()
d = c
c = d

x: List[C] = [a.f(), a.f()]

[out]
==
a.py:2: error: "C" expects 2 type arguments, but 1 given
[builtins fixtures/list.pyi]