Skip to content
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

Error with generic argument defaults #3130

Closed
vberlier opened this issue Feb 28, 2022 · 2 comments
Closed

Error with generic argument defaults #3130

vberlier opened this issue Feb 28, 2022 · 2 comments
Labels
as designed Not a bug, working as intended

Comments

@vberlier
Copy link

Pyright reports an error when returning an instance from generic type arguments with default values.

class Base:
    pass

class Derived(Base):
    pass

BaseType = TypeVar("BaseType", bound=Base)

def instantiate(cls: Type[BaseType]) -> BaseType:
    return cls()  # No error

def instantiate_default(cls: Type[BaseType] = Derived) -> BaseType:
    # Expression of type "BaseType@instantiate_default | Derived" cannot be assigned to return type "BaseType@instantiate_default"
    #   Type "BaseType@instantiate_default | Derived" cannot be assigned to type "BaseType@instantiate_default"
    return cls()

# External usage works fine, the inferred type is "Derived", no error
foo = instantiate(Derived)
bar = instantiate_default()

I'm using version 1.1.225 with strict mode.

@erictraut
Copy link
Collaborator

In general, specifying a default argument for a parameter that is typed with a TypeVar presents a bunch of challenges for type safety. Mypy doesn't allow default arguments to be provided in this situation. Compilers in other typed languages like C# and Swift also disallow this. For more details, refer to this thread and this thread.

Pyright handles the situation, but it makes it safe by setting the parameter to the union of the annotation (the TypeVar) and the type of the default argument. In your example, this means the type of cls is Type[BaseType | Type[Derived].

I'm still not 100% convinced that this is the best approach. Perhaps pyright should simply give up and disallow this case entirely like other type checkers and compilers.

A workaround I can suggest is the following:

@overload
def instantiate_default() -> Derived: ...

@overload
def instantiate_default(cls: Type[BaseType]) -> BaseType: ...

def instantiate_default(cls = Derived) -> Base:
    return cls()

@erictraut erictraut added the as designed Not a bug, working as intended label Feb 28, 2022
@vberlier
Copy link
Author

vberlier commented Mar 1, 2022

I see. Thanks for the explanation.

erictraut pushed a commit that referenced this issue May 21, 2023
… has a default argument value. Mypy doesn't allow this case. Pyright allows it but previously modified the parameter type to be a union of the TypeVar and the type of the default argument. This behavior closed some type validation holes, but it created other problems and was very unintuitive for users. See #3130. See #2507 for a discussion that led to this original change. This change reverses this behavior. It potentially allows for some unsafe "creative" uses that will go unreported.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
as designed Not a bug, working as intended
Projects
None yet
Development

No branches or pull requests

2 participants