From b518646c489d40fc1a271312c9f0729669ff7a2a Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 3 Dec 2021 11:23:41 +0900 Subject: [PATCH 1/3] Make sure TypeVarIds are unique among the mro The fix for this is *really* hacky, but whatever. --- mypy/semanal.py | 16 +++++++++++++++ test-data/unit/check-generics.test | 31 ++++++++++++++++++++++++----- test-data/unit/semanal-types.test | 6 +++--- test-data/unit/typexport-basic.test | 2 +- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index fe3151ce6cd2..5816c476be17 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1149,6 +1149,14 @@ def analyze_class(self, defn: ClassDef) -> None: self.mark_incomplete(defn.name, defn) return + # update the typevar ids such that they will not conflict with any base classes + # (yuck, there has to be a better way to do this.) + if any(isinstance(base[0], Instance) for base in base_types): + offset = max(self.find_maximum_class_id(base[0].type) for base in base_types if isinstance(base[0], Instance)) + # mutating the type vars to be what we want (and hoping nothing previously saved them) + for tvar in tvar_defs: + tvar.id.raw_id += offset + is_typeddict, info = self.typed_dict_analyzer.analyze_typeddict_classdef(defn) if is_typeddict: for decorator in defn.decorators: @@ -1183,6 +1191,14 @@ def analyze_class(self, defn: ClassDef) -> None: self.analyze_class_decorator(defn, decorator) self.analyze_class_body_common(defn) + # should this be memoized in TypeInfo?? + def find_maximum_class_id(self, info: TypeInfo) -> int: + # top class? + if len(info.mro) in (0, 1): + return len(info.type_vars) + else: + return len(info.type_vars) + max(self.find_maximum_class_id(cls) for cls in info.mro[1:]) + def is_core_builtin_class(self, defn: ClassDef) -> bool: return self.cur_mod_id == 'builtins' and defn.name in CORE_BUILTIN_CLASSES diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 2fa5a0da7e90..c872329f0f79 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1933,9 +1933,9 @@ class C(Generic[T]): class D(C[Tuple[T, S]]): ... class E(D[S, str]): ... -reveal_type(D.make_one) # N: Revealed type is "def [T, S] (x: Tuple[T`1, S`2]) -> __main__.C[Tuple[T`1, S`2]]" +reveal_type(D.make_one) # N: Revealed type is "def [T, S] (x: Tuple[T`2, S`3]) -> __main__.C[Tuple[T`2, S`3]]" reveal_type(D[int, str].make_one) # N: Revealed type is "def (x: Tuple[builtins.int*, builtins.str*]) -> __main__.C[Tuple[builtins.int*, builtins.str*]]" -reveal_type(E.make_one) # N: Revealed type is "def [S] (x: Tuple[S`1, builtins.str*]) -> __main__.C[Tuple[S`1, builtins.str*]]" +reveal_type(E.make_one) # N: Revealed type is "def [S] (x: Tuple[S`4, builtins.str*]) -> __main__.C[Tuple[S`4, builtins.str*]]" reveal_type(E[int].make_one) # N: Revealed type is "def (x: Tuple[builtins.int*, builtins.str*]) -> __main__.C[Tuple[builtins.int*, builtins.str*]]" [builtins fixtures/classmethod.pyi] @@ -2111,11 +2111,11 @@ class A(Generic[T]): class B(A[T], Generic[T, S]): def meth(self) -> None: - reveal_type(A[T].foo) # N: Revealed type is "def () -> Tuple[T`1, __main__.A[T`1]]" + reveal_type(A[T].foo) # N: Revealed type is "def () -> Tuple[T`2, __main__.A[T`2]]" @classmethod def other(cls) -> None: - reveal_type(cls.foo) # N: Revealed type is "def () -> Tuple[T`1, __main__.B[T`1, S`2]]" -reveal_type(B.foo) # N: Revealed type is "def [T, S] () -> Tuple[T`1, __main__.B[T`1, S`2]]" + reveal_type(cls.foo) # N: Revealed type is "def () -> Tuple[T`2, __main__.B[T`2, S`3]]" +reveal_type(B.foo) # N: Revealed type is "def [T, S] () -> Tuple[T`2, __main__.B[T`2, S`3]]" [builtins fixtures/classmethod.pyi] [case testGenericClassAlternativeConstructorPrecise] @@ -2504,3 +2504,24 @@ b: I[I[Any]] reveal_type([a, b]) # N: Revealed type is "builtins.list[__main__.I*[__main__.I[Any]]]" reveal_type([b, a]) # N: Revealed type is "builtins.list[__main__.I*[__main__.I[Any]]]" [builtins fixtures/list.pyi] + +[case testOverlappingTypeVarIds] +from typing import TypeVar, Generic + +class A: ... +class B: ... + +T = TypeVar("T", bound=A) +V = TypeVar("V", bound=B) +S = TypeVar("S") + +class Whatever(Generic[T]): + def something(self: S) -> S: + return self + +# the "V" here had the same id as "T" and so mypy used to think it could expand one into another. +# this test is here to make sure that doesn't happen! +class WhateverPartTwo(Whatever[A], Generic[V]): + def something(self: S) -> S: + return self + diff --git a/test-data/unit/semanal-types.test b/test-data/unit/semanal-types.test index 772de61b5900..f0e1e31e9499 100644 --- a/test-data/unit/semanal-types.test +++ b/test-data/unit/semanal-types.test @@ -662,9 +662,9 @@ MypyFile:1( ClassDef:4( c TypeVars( - t`1) + t`2) BaseType( - __main__.d[t`1]) + __main__.d[t`2]) PassStmt:4())) [case testTupleType] @@ -907,7 +907,7 @@ MypyFile:1( ClassDef:5( A TypeVars( - t`1) + t`2) BaseType( __main__.B[Any]) PassStmt:5())) diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index 4f40117d18d2..260515663880 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -435,7 +435,7 @@ class B(A[C, T], Generic[T]): CallExpr(9) : None MemberExpr(9) : def (a: C) NameExpr(9) : C -NameExpr(9) : B[T`1] +NameExpr(9) : B[T`3] [case testExternalReferenceWithGenericInheritance] from typing import TypeVar, Generic From 1ad4f763284de9bb778445904a61a3307ca7df1a Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 3 Dec 2021 11:27:11 +0900 Subject: [PATCH 2/3] Styling --- mypy/semanal.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5816c476be17..8bdcd66048b0 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1152,7 +1152,10 @@ def analyze_class(self, defn: ClassDef) -> None: # update the typevar ids such that they will not conflict with any base classes # (yuck, there has to be a better way to do this.) if any(isinstance(base[0], Instance) for base in base_types): - offset = max(self.find_maximum_class_id(base[0].type) for base in base_types if isinstance(base[0], Instance)) + offset = max( + self.find_maximum_class_id(base[0].type) + for base in base_types if isinstance(base[0], Instance) + ) # mutating the type vars to be what we want (and hoping nothing previously saved them) for tvar in tvar_defs: tvar.id.raw_id += offset @@ -1191,13 +1194,16 @@ def analyze_class(self, defn: ClassDef) -> None: self.analyze_class_decorator(defn, decorator) self.analyze_class_body_common(defn) - # should this be memoized in TypeInfo?? + # should this be memoized in TypeInfo? def find_maximum_class_id(self, info: TypeInfo) -> int: # top class? if len(info.mro) in (0, 1): return len(info.type_vars) else: - return len(info.type_vars) + max(self.find_maximum_class_id(cls) for cls in info.mro[1:]) + return len(info.type_vars) + max( + self.find_maximum_class_id(cls) + for cls in info.mro[1:] + ) def is_core_builtin_class(self, defn: ClassDef) -> bool: return self.cur_mod_id == 'builtins' and defn.name in CORE_BUILTIN_CLASSES From c85fe853dbe97af5bba6c408e9db8b483012c10c Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 10 Dec 2021 09:02:44 +0900 Subject: [PATCH 3/3] Don't iterate over the MRO when only bases are needed --- mypy/semanal.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8bdcd66048b0..696854e1dbb6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1196,14 +1196,13 @@ def analyze_class(self, defn: ClassDef) -> None: # should this be memoized in TypeInfo? def find_maximum_class_id(self, info: TypeInfo) -> int: - # top class? - if len(info.mro) in (0, 1): - return len(info.type_vars) - else: + if info.bases: return len(info.type_vars) + max( - self.find_maximum_class_id(cls) - for cls in info.mro[1:] + self.find_maximum_class_id(cls.type) + for cls in info.bases ) + else: + return len(info.type_vars) def is_core_builtin_class(self, defn: ClassDef) -> bool: return self.cur_mod_id == 'builtins' and defn.name in CORE_BUILTIN_CLASSES