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

A function returning TypeVar should receive at least one argument containing the same Typevar #13765

Closed
Xophmeister opened this issue Sep 29, 2022 · 8 comments

Comments

@Xophmeister
Copy link

Xophmeister commented Sep 29, 2022

Documentation

mypy recently (first noticed today: 2022-09-29) started failing my code with the following:

A function returning TypeVar should receive at least one argument containing the same Typevar

I did a cursory search and found no reference to this rule. Personally, I find it unnecessarily restrictive. For example, an object method could return that TypeVar without having any arguments; i.e., just relying on the object's state. Nonetheless, I assume it was added for good reason. Can that reason please be elaborated on somewhere in the documentation and/or mypy's output?

@hauntsaninja
Copy link
Collaborator

mypy won't complain about the situation you suggested (method of generic class being different from a function):

from typing import *
T = TypeVar("T")
class X(Generic[T]):
    def f(self) -> T: ...

The reason for the lint is that a single TypeVar (in all the cases that mypy warns about) will not do anything.
Generics and TypeVars are parts of the typing system users find complicated, so warning when a type hint isn't doing anything helps such users detect misuse.

@aspacca
Copy link

aspacca commented Oct 1, 2022

from typing import Generic, TypeVar

T = TypeVar("T")


class StaticFactoryClassWithError:
    @staticmethod
    def create() -> T:
        ...


class StaticFactoryClassWithoutError(Generic[T]):
    @staticmethod
    def create() -> T:
        ...

I have the same failure reason with the above code, for StaticFactoryClassWithError.create() but not for StaticFactoryClassWithoutError.create().

to me being create() a static method makes sense to not have any argument containing the same TypeVar

class StaticFactoryClassWithoutError:
    @staticmethod
    def create(t: T) -> T:
        ...

this does not fail as well

you can see the real code at https://github.com/elastic/elastic-serverless-forwarder/blob/main/shippers/factory.py

@hauntsaninja
Copy link
Collaborator

class StaticFactoryClassWithError:
    @staticmethod
    def create() -> T:

doesn't do anything.

In your real code, you want to just return ProtocolShipper, not ProtocolShipperType. I added a suggestion for that which didn't quite make it into release 0.980: #13730

If someone has a concrete suggestion on how to improve the docs or error messages, open a PR.

@aspacca
Copy link

aspacca commented Oct 1, 2022

In your real code, you want to just return ProtocolShipper, not ProtocolShipperType

I've hinted that just after sending my comment. but thanks for the quick reply :)

(I have indeed the correct return type in the factory for ProtocolStorage)

@aspacca
Copy link

aspacca commented Oct 1, 2022

Indeed I remember now why I used the ProtocolShipperType

I have somewhere else:

shipper: ElasticsearchShipper = ShipperFactory.create_from_output()

that using ProtocolShipper instead produces the following error:

handlers/aws/utils.py:165: error: Incompatible types in assignment (expression has type "ProtocolShipper", variable has type "ElasticsearchShipper")

@pierresouchay
Copy link

Any idea how to make code like this (https://stackoverflow.com/a/6798042) compatible?

from typing import Dict, Any
class Singleton(type):

    _instances: Dict['Singleton', 'Singleton'] = {}

    def __call__(cls, *args: Any, **kwargs: Any) -> 'Singleton':
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class NoValueType(metaclass=Singleton):
...

@alelom
Copy link

alelom commented Sep 21, 2023

Seems to me that fully generic functions are not yet supported. We can have generic functions if and only if the function has a generic input, but we miss the case for when a function has no input and wants to return a generic output.

Can someone confirm or correct me? @hauntsaninja
Is the following achievable?

def some_generic_func(someBool : bool) -> T:
    if(someBool):
       return SomeType #someType extends T
    else:
       return SomeOtherType #someOtherType also extends T

The advantage is code that returns a concrete instance without requiring to cast. For example, this:

output : T = some_generic_func(true)
output : SomeType = cast(SomeType, train_dataset)

could be replaced with:

output : SomeType = some_generic_func(true)

@JelleZijlstra
Copy link
Member

@alelom you can achieve what you want with overloads. If the function simply returns T, there is no way for the type checker to know that someBool being True leads to a particular return type.

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

No branches or pull requests

6 participants