From cbf9486e7f413b18380bd9700f7fc80c5842bdba Mon Sep 17 00:00:00 2001
From: Ivan Levkivskyi <ilevkivskyi@dropbox.com>
Date: Wed, 27 Nov 2019 12:57:57 +0000
Subject: [PATCH 1/2] Make logic around bind_self() consistent in different
 code paths

---
 mypy/checkmember.py                |  7 +--
 test-data/unit/check-generics.test | 12 ++---
 test-data/unit/check-selftype.test | 78 +++++++++++++++++++++++++++++-
 3 files changed, 87 insertions(+), 10 deletions(-)

diff --git a/mypy/checkmember.py b/mypy/checkmember.py
index 71bdd3fafc40..20013d389f09 100644
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -829,9 +829,7 @@ class B(A[str]): pass
     variables of the class callable on which the method was accessed.
     """
     # TODO: verify consistency between Q and T
-    if is_classmethod:
-        assert isuper is not None
-        t = get_proper_type(expand_type_by_instance(t, isuper))
+
     # We add class type variables if the class method is accessed on class object
     # without applied type arguments, this matches the behavior of __init__().
     # For example (continuing the example in docstring):
@@ -847,7 +845,10 @@ class B(A[str]): pass
     if isinstance(t, CallableType):
         tvars = original_vars if original_vars is not None else []
         if is_classmethod:
+            t = freshen_function_type_vars(t)
             t = bind_self(t, original_type, is_classmethod=True)
+            assert isuper is not None
+            t = get_proper_type(expand_type_by_instance(t, isuper))
         return t.copy_modified(variables=tvars + t.variables)
     elif isinstance(t, Overloaded):
         return Overloaded([cast(CallableType, add_class_tvars(item, itype, isuper, is_classmethod,
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
index b795a36aaa4f..846c2ebf99a6 100644
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -2062,7 +2062,7 @@ class Base(Generic[T]):
             return (cls(item),)
         return cls(item)
 
-reveal_type(Base.make_some)  # N: Revealed type is 'Overload(def [T] (item: T`1) -> __main__.Base*[T`1], def [T] (item: T`1, n: builtins.int) -> builtins.tuple[__main__.Base*[T`1]])'
+reveal_type(Base.make_some)  # N: Revealed type is 'Overload(def [T] (item: T`1) -> __main__.Base[T`1], def [T] (item: T`1, n: builtins.int) -> builtins.tuple[__main__.Base[T`1]])'
 reveal_type(Base.make_some(1))  # N: Revealed type is '__main__.Base[builtins.int*]'
 reveal_type(Base.make_some(1, 1))  # N: Revealed type is 'builtins.tuple[__main__.Base[builtins.int*]]'
 
@@ -2100,11 +2100,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`1, __main__.A[T`1]]'
     @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`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]]'
 [builtins fixtures/classmethod.pyi]
 
 [case testGenericClassAlternativeConstructorPrecise]
@@ -2171,8 +2171,8 @@ class C(Generic[T]):
 
 class D(C[str]): ...
 
-reveal_type(D.get())  # N: Revealed type is 'builtins.str'
-reveal_type(D.get(42))  # N: Revealed type is 'builtins.tuple[builtins.str]'
+reveal_type(D.get())  # N: Revealed type is 'builtins.str*'
+reveal_type(D.get(42))  # N: Revealed type is 'builtins.tuple[builtins.str*]'
 [builtins fixtures/classmethod.pyi]
 
 [case testGenericClassMethodAnnotation]
diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test
index f4483edec9bb..d99ad2282735 100644
--- a/test-data/unit/check-selftype.test
+++ b/test-data/unit/check-selftype.test
@@ -959,7 +959,7 @@ class A(Generic[T]):
 
 t: Type[Union[A[int], A[str]]]
 x = t.meth()
-reveal_type(x)  # N: Revealed type is 'Union[__main__.A*[builtins.int], __main__.A*[builtins.str]]'
+reveal_type(x)  # N: Revealed type is 'Union[__main__.A[builtins.int], __main__.A[builtins.str]]'
 [builtins fixtures/classmethod.pyi]
 
 [case testSelfTypeClassMethodOnUnionList]
@@ -1055,3 +1055,79 @@ class Concrete(Blah):
     def something(self) -> None: ...
 
 Concrete()  # OK
+
+[case testSelfTypeGenericClassNoClashInstanceMethod]
+from typing import TypeVar, Generic
+
+M = TypeVar("M")
+T = TypeVar("T")
+S = TypeVar("S")
+
+class Descriptor(Generic[M]): ...
+
+class BaseWrapper(Generic[M]):
+    def create_wrapper(self: T, metric_descriptor: Descriptor[M]) -> T: ...
+class SubWrapper(BaseWrapper[M]): ...
+
+def build_wrapper(descriptor: Descriptor[M]) -> BaseWrapper[M]:
+    wrapper: BaseWrapper[M]
+    return wrapper.create_wrapper(descriptor)
+
+def build_sub_wrapper(descriptor: Descriptor[S]) -> SubWrapper[S]:
+    wrapper: SubWrapper[S]
+    x = wrapper.create_wrapper(descriptor)
+    reveal_type(x)  # N: Revealed type is '__main__.SubWrapper[S`-1]'
+    return x
+
+[case testSelfTypeGenericClassNoClashClassMethod]
+from typing import TypeVar, Generic, Type
+
+M = TypeVar("M")
+T = TypeVar("T")
+S = TypeVar("S")
+
+class Descriptor(Generic[M]): ...
+
+class BaseWrapper(Generic[M]):
+    @classmethod
+    def create_wrapper(cls: Type[T], metric_descriptor: Descriptor[M]) -> T: ...
+class SubWrapper(BaseWrapper[M]): ...
+
+def build_wrapper(descriptor: Descriptor[M]) -> BaseWrapper[M]:
+    wrapper_cls: Type[BaseWrapper[M]]
+    return wrapper_cls.create_wrapper(descriptor)
+
+def build_sub_wrapper(descriptor: Descriptor[S]) -> SubWrapper[S]:
+    wrapper_cls: Type[SubWrapper[S]]
+    x = wrapper_cls.create_wrapper(descriptor)
+    reveal_type(x)  # N: Revealed type is '__main__.SubWrapper[S`-1]'
+    return x
+[builtins fixtures/classmethod.pyi]
+
+[case testSelfTypeGenericClassNoClashClassMethodClassObject]
+from typing import TypeVar, Generic, Type
+
+M = TypeVar("M")
+T = TypeVar("T")
+
+class Descriptor(Generic[M]): ...
+
+class BaseWrapper(Generic[M]):
+    @classmethod
+    def create_wrapper(cls: Type[T], metric_descriptor: Descriptor[M]) -> T: ...
+class SubWrapper(BaseWrapper[M]): ...
+
+def build_wrapper(descriptor: Descriptor[M]) -> BaseWrapper[M]:
+    return BaseWrapper.create_wrapper(descriptor)
+
+def build_sub_wrapper(descriptor: Descriptor[M]) -> SubWrapper[M]:
+    x = SubWrapper.create_wrapper(descriptor)
+    reveal_type(x)  # N: Revealed type is '__main__.SubWrapper[M`-1]'
+    return x
+
+def build_wrapper_non_gen(descriptor: Descriptor[int]) -> BaseWrapper[str]:
+    return BaseWrapper.create_wrapper(descriptor)  # E: Argument 1 to "create_wrapper" of "BaseWrapper" has incompatible type "Descriptor[int]"; expected "Descriptor[str]"
+
+def build_sub_wrapper_non_gen(descriptor: Descriptor[int]) -> SubWrapper[str]:
+    return SubWrapper.create_wrapper(descriptor)  # E: Argument 1 to "create_wrapper" of "BaseWrapper" has incompatible type "Descriptor[int]"; expected "Descriptor[str]"
+[builtins fixtures/classmethod.pyi]

From c8044c7d47fd0919dba0006efda7ecbf9e896976 Mon Sep 17 00:00:00 2001
From: Ivan Levkivskyi <ilevkivskyi@dropbox.com>
Date: Wed, 27 Nov 2019 14:02:27 +0000
Subject: [PATCH 2/2] Fix self-check

---
 mypy/checkmember.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mypy/checkmember.py b/mypy/checkmember.py
index 20013d389f09..7d82fd89e737 100644
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -848,7 +848,7 @@ class B(A[str]): pass
             t = freshen_function_type_vars(t)
             t = bind_self(t, original_type, is_classmethod=True)
             assert isuper is not None
-            t = get_proper_type(expand_type_by_instance(t, isuper))
+            t = cast(CallableType, expand_type_by_instance(t, isuper))
         return t.copy_modified(variables=tvars + t.variables)
     elif isinstance(t, Overloaded):
         return Overloaded([cast(CallableType, add_class_tvars(item, itype, isuper, is_classmethod,