Skip to content

Type parameter scoping seems to disagree with PEP 484 #11993

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

Closed
kmillikin opened this issue Jan 14, 2022 · 2 comments · Fixed by #18943
Closed

Type parameter scoping seems to disagree with PEP 484 #11993

kmillikin opened this issue Jan 14, 2022 · 2 comments · Fixed by #18943
Labels

Comments

@kmillikin
Copy link

PEP 484 has: "A type variable used in a method of a generic class that coincides with one of the variables that parameterize this class is always bound to that variable." The example there has:

T = TypeVar('T')

class MyClass(Generic[T]):
    def meth_1(self, x: T) -> T: ...

This should mean that T occurs bound in the signature of meth_1 (it is bound to the type parameter of the generic class MyClass).
The signature of MyClass[int].meth_1 should be (self:MyClass[int],x:int)->int. This is gotten from the signature of the function in the generic class with the type argument int substituted for occurrences of the type parameter T.

The signature of MyClass.meth_1 should be (self:MyClass[Any],x:Any)->Any. The generic class is implicitly instantiated to Any. Further, we must substitute something for the occurrences of the type parameter in the signature (the type parameter can't become a free occurrence just by looking at it from outside the class).

Mypy seems to do something else. The type of MyClass[int].meth_1 is revealed as:

def (self: test.MyClass[T`1], x: T`1) -> T`1

Instead of substituting int for T it has used some other type (maybe a free type variable?). If we try to apply it to an instance of MyClass[int] and an int, we get that the type of the application is T`1 whatever that is, and we get errors:

error: Argument 1 has incompatible type "MyClass[int]"; expected "MyClass[T]"
error: Argument 2 has incompatible type "int"; expected "T"

The type of MyClass.meth_1 is revealed as:

def [T] (self: test.MyClass[T`1], x: T`1) -> T`1

This looks like it might be a generic function, but MyClass.meth_1 should not be generic (it has no free type variables). There is actually no occurrence of the type parameter T in the signature, it seems to have been instantiated to T`1 whatever that is (unless T`1 is another spelling of T).

This function does behave as if it were generic, actually. We can apply it to an instance of MyClass[int] and an int and the type of the application is int. And it's apparently not an instantiation to Any because if we try to apply it to an instance of MyClass[int] and a str it is a type error "Cannot infer function argument".

What I think is happening is that mypy is treating functions in generic classes as themselves generic functions parameterized over the class's type parameters. This seems wrong or else I've completely misunderstood PEP 484.

@kmillikin kmillikin added the bug mypy got something wrong label Jan 14, 2022
@JelleZijlstra
Copy link
Member

Could you add a complete code sample that illustrates the behavior you find problematic?

@kmillikin
Copy link
Author

kmillikin commented Jan 14, 2022

from typing import Generic, TypeVar

T = TypeVar('T')

class A(Generic[T]):
  def m(self, x: T) -> list[T]:
    return [x]

f = A[int].m
f(A[int](), 0)  # Signature of f should be (self: A[int], x: int) -> list[int]

g = A.m
g(A(), 0)  # Signature of g should be (self: A[Any], x: Any) -> list[Any]

This program should type check. Mypy (version 0.931) reports:

test.py:10: error: Argument 1 has incompatible type "A[int]"; expected "A[T]"
test.py:10: error: Argument 2 has incompatible type "int"; expected "T"
test.py:13: error: Cannot infer function type argument

cdce8p pushed a commit to cdce8p/mypy that referenced this issue May 31, 2025
Fixes python#18024
Fixes python#18706
Fixes python#17734
Fixes python#15097
Fixes python#14814
Fixes python#14806
Fixes python#14259
Fixes python#13041
Fixes python#11993
Fixes python#9585
Fixes python#9266
Fixes python#9202
Fixes python#5481

This is a fourth "major" PR toward
python#7724. This is one is
watershed/crux of the whole series (but to set correct expectations,
there are almost a dozen smaller follow-up/clean-up PRs in the
pipeline).

The core of the idea is to set current type-checker as part of the
global state. There are however some details:
* There are cases where we call `is_subtype()` before type-checking. For
now, I fall back to old logic in this cases. In follow up PRs we may
switch to using type-checker instances before type checking phase (this
requires some care).
* This increases typeops import cycle by a few modules, but
unfortunately this is inevitable.
* This PR increases potential for infinite recursion in protocols. To
mitigate I add: one legitimate fix for `__call__`, and one temporary
hack for `freshen_all_functions_type_vars` (to reduce performance
impact).
* Finally I change semantics for method access on class objects to match
the one in old `find_member()`. Now we will expand type by instance, so
we have something like this:
  ```python
  class B(Generic[T]):
      def foo(self, x: T) -> T: ...
  class C(B[str]): ...
  reveal_type(C.foo)  # def (self: B[str], x: str) -> str
  ```
FWIW, I am not even 100% sure this is correct, it seems to me we _may_
keep the method generic. But in any case what we do currently is
definitely wrong (we infer a _non-generic_ `def (x: T) -> T`).

---------

Co-authored-by: hauntsaninja <[email protected]>
Co-authored-by: Shantanu <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants