From d84fc52367b0857a7b15480c1226c8ac29f5f083 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 16 Dec 2019 18:08:59 -0800 Subject: [PATCH 1/6] Fix some daemon crashes involving classes becoming generic Fixes #3279. Also fixes another related crash. --- mypy/constraints.py | 24 ++++++++++++++---------- mypy/meet.py | 8 +++++--- test-data/unit/fine-grained.test | 29 +++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index ca254026c310..aa4ce24b65df 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -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): # 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 diff --git a/mypy/meet.py b/mypy/meet.py index 608faf8f25fe..9333837aaeee 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -486,13 +486,15 @@ def visit_type_var(self, t: TypeVarType) -> ProperType: def visit_instance(self, t: Instance) -> ProperType: if isinstance(self.s, Instance): si = self.s - if t.type == si.type: + if t.type == si.type and len(t.args) == len(si.args): if is_subtype(t, self.s) or is_subtype(self.s, t): # 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: diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 11e83f560eee..6c83584f11f2 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9288,3 +9288,32 @@ class B: self.x = 0 [out] == + +[case testGenericChange] +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, Type, 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]' +[builtins fixtures/list.pyi] From 9f0d80081b0f5011a4d39d861cee75578922c0a6 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 17 Dec 2019 13:00:34 -0800 Subject: [PATCH 2/6] do join --- mypy/join.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 8989a596b70e..843e187a05bc 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -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(self.join(ta, sa)) return Instance(t.type, args) else: # Incompatible; return trivial result object. From ab19890a24e20ffcb2b30c877f655dc9b2c90652 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 17 Dec 2019 15:32:52 -0800 Subject: [PATCH 3/6] actually fix --- mypy/join.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/join.py b/mypy/join.py index 843e187a05bc..a2513bd36201 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -348,7 +348,7 @@ def join_instances(t: Instance, s: Instance) -> ProperType: # 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(self.join(ta, sa)) + args.append(join_types(ta, sa)) return Instance(t.type, args) else: # Incompatible; return trivial result object. From 6d369b5a931af1f6aa252d8aded905f7984efefb Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 17 Dec 2019 15:33:45 -0800 Subject: [PATCH 4/6] remove a check --- mypy/meet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index 9333837aaeee..548278c154da 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -486,7 +486,7 @@ def visit_type_var(self, t: TypeVarType) -> ProperType: def visit_instance(self, t: Instance) -> ProperType: if isinstance(self.s, Instance): si = self.s - if t.type == si.type and len(t.args) == len(si.args): + if t.type == si.type: if is_subtype(t, self.s) or is_subtype(self.s, t): # Combine type arguments. We could have used join below # equivalently. From 272f53cb19c9a6a815cbe63f918aa8bd4edc1aa0 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 17 Dec 2019 17:43:50 -0800 Subject: [PATCH 5/6] kick, since travis has forgotten me???? From a39a24958c183f11f48768852ddcd5b1a0b6bc37 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 18 Dec 2019 14:43:41 -0800 Subject: [PATCH 6/6] some more testing --- test-data/unit/fine-grained.test | 64 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 6c83584f11f2..2dc598661fd9 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9289,7 +9289,7 @@ class B: [out] == -[case testGenericChange] +[case testGenericChange1] import a [file a.py] import b @@ -9298,7 +9298,7 @@ def f() -> b.C: pass import a class C: pass [file b.py.2] -from typing import TypeVar, Generic, Type, List +from typing import TypeVar, Generic, List import a T = TypeVar('T') @@ -9317,3 +9317,63 @@ x: List[C] = [a.f(), a.f()] == b.py:7: note: Revealed type is 'def () -> b.C[Any]' [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]