Skip to content

Error inferring typevar on generic classes #18706

Closed

Description

@alfred82santa

Bug Report

The inferred type of methods of a generic class are not bounded to typevar value on descendant classes.

To Reproduce

from typing import Iterator, Generic, TypeVar

T = TypeVar("T", str, int)

class Parent(Generic[T]):

    @classmethod
    def method_1(cls, value: T) -> T:
        return 2*value
        
    def method_2(self, value: T) -> T:
        return 2*value
        
class ChildInt(Parent[int]):
    pass

instance = ChildInt()

reveal_type(instance.method_1)  # Revealed type is "def (value: builtins.int) -> builtins.int"
reveal_type(ChildInt.method_1)  # Revealed type is "def (value: builtins.int) -> builtins.int"

reveal_type(instance.method_2)  # Revealed type is "def (value: builtins.int) -> builtins.int"
reveal_type(ChildInt.method_2)  # Revealed type is "def (self: __main__.Parent[T`1], value: T`1) -> T`1"

https://mypy-play.net/?mypy=latest&python=3.12&gist=7ce2739ccc2b43f37187d54dcdd5f923

Expected Behavior

reveal_type(ChildInt.method_2)  # Revealed type is "def (self: __main__.ChildInt, value: builtins.int) -> builtins.int"

This soulution could be acceptable, as well:

reveal_type(ChildInt.method_2)  # Revealed type is "def (self: __main__.Parent[builtins.int], value: builtins.int) -> builtins.int"

Actual Behavior

reveal_type(ChildInt.method_2)  # Revealed type is "def (self: __main__.Parent[T`1], value: T`1) -> T`1"

Your Environment

  • Mypy version used: 1.15
  • Mypy command-line flags: no flags
  • Mypy configuration options from mypy.ini (and other config files): no config
  • Python version used: 3.13

Releated issues

#15658

Activity

  1. A5rocks commented on Feb 19, 2025

    @A5rocks
    Collaborator

    Does this cause any false positives or false negatives? Mypy should eventually bind self and then at that point it will find what T is. (I don't know how to interpret the related issue)

  2. alfred82santa commented on Feb 19, 2025

    @alfred82santa
    Author

    The related problem was that T was not bound to the type in an instance object. The current problem is that T is not bound to the type in the class object.

    It could produce false negative if the class method is referenced. I know it is not common and a little bit strange.

    from typing import Iterator, Generic, TypeVar, Callable, Any
    
    T = TypeVar("T", str, int)
    
    class Parent(Generic[T]):
    
        @classmethod
        def method_1(cls, value: T) -> T:
            return 2*value
            
        def method_2(self, value: T) -> T:
            return 2*value
            
    class ChildInt(Parent[int]):
        pass
    
    instance = ChildInt()
    
    reveal_type(ChildInt.method_1)  # Revealed type is "def (value: builtins.int) -> builtins.int"
    reveal_type(instance.method_1)  # Revealed type is "def (value: builtins.int) -> builtins.int"
    
    reveal_type(ChildInt.method_2)  # Revealed type is "def (self: __main__.Parent[T`1], value: T`1) -> T`1"
    reveal_type(instance.method_2)  # Revealed type is "def (value: builtins.int) -> builtins.int"
    
    def decorator[S](f: Callable[[S, int], int]) -> Callable[[S, int], int]:
        return f
        
        
    class GrandChildInt(ChildInt):
        
        method_3 = decorator(ChildInt.method_2)  # error: Argument 1 to "decorator" has incompatible type "Callable[[Parent[T], T], T]"; expected "Callable[[Parent[T], int], int]"  [arg-type]

    https://mypy-play.net/?mypy=latest&python=3.12&gist=7ce2739ccc2b43f37187d54dcdd5f923

  3. sterliakov commented on Feb 22, 2025

    @sterliakov
    Collaborator

    This is another branch missing in checkmember.py: analyze_class_attribute_access (and precisely add_class_tvars?) does not map instance methods properly. A map_self helper similar to bind_self should help, do we have it somewhere? In all other cases using checker.bind_and_map_method and its helpers suffices, but here we do need to preserve the type of the first argument. (it isn't as trivial as "replace with current self type" due to self-type constraints on overloads and mapping generics)

  4. A5rocks commented on Feb 22, 2025

    @A5rocks
    Collaborator

    infer_type_arguments with isuper but polymorphic? Like something like this: https://github.com/python/mypy/pull/18401/files#diff-1dce7e2a19559e2ff4e08fd46877b87c9cfec3cb5e6d465157788542948e11e4R369-R370 (not sure if that would work if isuper is still generic like list[T] and not list[str]...)

  5. sterliakov commented on Feb 22, 2025

    @sterliakov
    Collaborator

    That's what bind_self does, yep, I hoped there's something higher-level somewhere:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions