pyright icon indicating copy to clipboard operation
pyright copied to clipboard

Constructor call within generic class confuses type arguments

Open erictraut opened this issue 11 months ago • 6 comments

A regression introduced in 1.1.337 breaks the following:

from typing import Generic, TypeVar

T = TypeVar("T")
U = TypeVar("U")


class Test2(Generic[T, U]):
    def __init__(self):
        pass

    def test2(self) -> None:
        x1: Test2[U, T]

        x1 = Test2[U, T]()

This should have been caught by the constructor26.py unit test, but it was not.

This is related to https://github.com/microsoft/pyright/issues/7369.

erictraut avatar Mar 17 '24 18:03 erictraut

I am not sure if this is related, but recently this started to raise "type is partially unknown" error:


from typing import Generic, Self, TypeVar


T = TypeVar("T")

class Test(Generic[T]):
    @classmethod
    def make(cls, type_: type[T]) -> Self:
        raise NotImplementedError()
    
t = Test.make(int)

in previous versions t was recognized as Test[int], but now it shows Test[Unknown] and two errors are reported:

Type of "make" is partially unknown
  Type of "make" is "(type_: Unknown) -> Test[Unknown]"

and

Type of "t" is partially unknown
  Type of "t" is "Test[Unknown]"

peku33 avatar Mar 26 '24 17:03 peku33

I am not sure if this is related

This isn't related. The behavior you're seeing here was due to a recent change for spec compliance. The current behavior is correct. Since you haven't specified a type argument for the Test class, it defaults to Unknown when binding Test to the make class method.

erictraut avatar Mar 26 '24 19:03 erictraut

Okay, but isn't the type_ parameter with type T forcing return type to also be specialized with T?

Do you know what is the current approach to specialize a classmethod returning Self with concrete type from one of its arguments?

peku33 avatar Mar 26 '24 19:03 peku33

Do you know what is the current approach to specialize a classmethod returning Self with concrete type from one of its arguments?

If you want to use a classmethod in this manner, you would need explicitly specialize the class that you're binding to the class method.

t = Test[int].make(int)

Or if you're using the new PEP 696 TypeVar defaults, you can rely on the default value of the TypeVar.

from typing import Generic, Self
from typing_extensions import TypeVar

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

class Test(Generic[T]):
    @classmethod
    def make(cls, type_: type[T]) -> Self:
        raise NotImplementedError()

t = Test.make(int)

erictraut avatar Mar 26 '24 19:03 erictraut

Okay, can you provide more details (any source to read more details) on change for spec compliance? mypy in current version reveals type correctly (as pyright in previous versions) and does not require this double typing. I would like to understand in which scenario (and why) the same T in type_: type[T] and Generic[T] refer to different types, so return type is unknown.

peku33 avatar Mar 26 '24 22:03 peku33

We're quite off topic for this issue, but since you asked... This was previously an under-specified (and somewhat ambiguous) part of the typing spec, but the recent adoption of PEP 696 clarifies how this must work. The PEP 696 specification was recently incorporated into the official Python typing spec. The latest published version of mypy (1.9) doesn't have support for PEP 696, but the next version (1.10) will have partial (possibly complete) support.

erictraut avatar Mar 27 '24 00:03 erictraut

This is addressed in pyright 1.1.373.

erictraut avatar Jul 24 '24 00:07 erictraut