Skip to content

Parameterless generic functions are allowed #2885

Closed

Description

@miedzinski
from typing import TypeVar
T = TypeVar('T')
def f() -> T: ...

Mypy is absolutely OK with this code, but it makes no sense (it is not possible to return T without argument).

Activity

  1. ilevkivskyi commented on Feb 20, 2017

    @ilevkivskyi
    Member

    I don't think mypy should necessarily prohibit something that "makes no sense". Could this cause a false positive or false negative (uncaught runtime error)? If yes, then those are bugs, if no, then there is no problem.

  2. miedzinski commented on Feb 20, 2017

    @miedzinski
    ContributorAuthor

    I disagree. You could say the same about usage of ClassVar in function signatures, but I'm pretty sure you think otherwise. :)

  3. ilevkivskyi commented on Feb 20, 2017

    @ilevkivskyi
    Member

    These two cases are different. For ClassVar a user might expect some guarantees (like accepting only C.x as an argument of a function), that are in fact not provided, we want to avoid such situations. But what are the guarantees here that could be expected by a user but not actually provided? If you see them, then this might be a reason to "fix" this.

  4. miedzinski commented on Feb 20, 2017

    @miedzinski
    ContributorAuthor

    Okay, that's a point. On the other hand, I could see an user with poor understanding of generics writing something like

    T = TypeVar('T')
    U = TypeVar('U')
    def f(x: T) -> U: ...

    but as soon as he would provide some implementation for f (and it'd result in returning something other than None, assuming --strict-optional is off) mypy would throw an error.

    If no one else considers this a bug, I can close this.

  5. JukkaL commented on Feb 21, 2017

    @JukkaL
    Collaborator

    There actually is a valid reason to only use T in a return value type, at least if the return type is generic. A contrived example:

    class MyList(List[T]): ...
    
    def make_list() -> List[T]:
        return MyList()  # want to hide the implementation detail that it's actually a MyList
    
    a: List[int] = make_list()
    

    (Also, list() basically behaves like make_list in the above example, though it's a bit more complicated.)

    However, I can't think of any real use case where plain T would be a reasonable return type if T is never used in an argument type. Warning about this would probably be a reasonable thing to do, but I agree with @ilevkivskyi -- it's not necessary for correctness. This issue can be left open, but this is a pretty low-priority thing unless it seems that users are confused by the current behavior.

  6. pkch commented on Feb 24, 2017

    @pkch
    Contributor

    IMO, identifying every possible non-sensical type annotation in mypy is unrealistic. So as with any apparently meaningless code, the question is how likely it is a symptom of a serious bug. If often, it could be reported as an error by mypy. If not too often, it might be something that could be caught by a linter such as pylint (if it becomes aware of mypy). This would be quite similar to how python interpreter accepts unused variables, but pylint complains about them.

  7. ilevkivskyi commented on Mar 20, 2017

    @ilevkivskyi
    Member

    While thinking about whether there are possible use cases for returning a plain type variable I discovered that mypy behaves strangely in case of a restricted type variable:

    from typing import TypeVar
    
    class A: ...
    class B: ...
    T = TypeVar('T', A, B)
    
    def f() -> T:
        if bool():
            return A() # E: Incompatible return value type (got "A", expected "B")
        else:
            return B() # E: Incompatible return value type (got "B", expected "A")

    Note how two error messages contradict each other.

  8. sixolet commented on Apr 11, 2017

    @sixolet
    Collaborator

    Regarding @JukkaL's example of returning List[T], I think it would be kind to tell the user their line needs a type annotation rather than returning List[uninhabited] if the relevant line is a = make_list() instead of a: List[int] = make_list()

  9. ilevkivskyi commented on Mar 6, 2019

    @ilevkivskyi
    Member

    Raising priority to normal since this appeared couple times again.

  10. ilevkivskyi commented on Oct 14, 2019

    @ilevkivskyi
    Member

    Note that mypy actually complains about an invalid return in such functions, see #7702. So I think it makes sense to instead give a dedicated error message explaining why it is bad, or better just with a links to some docs about this.

  11. Kurt-von-Laven commented on Sep 15, 2021

    @Kurt-von-Laven

    Here is a use case I encountered for the more general notion of generic functions that don't accept generic parameters. Suppose a function calls a specific REST API with the given path and query and returns the result of json.loads(). Perhaps it adds some headers to deal with authentication and the hostname since these may be common to all requests. What should the return type of such a function be? It can certainly be Dict[str, Any] or whatever your preferred arbitrary JSON object type, but it also seems perfectly valid to require that the calling code provide a type hint (since the type can not be inferred) and instead return T, where T = TypeVar("T", bound=Dict[str, Any]). The calling code (or something higher up the stack) often knows the expected structure of the response to a request to a specific path and can hint a much narrower TypedDict and then call isinstance() once on the returned value.

  12. Kurt-von-Laven commented on Sep 15, 2021

    @Kurt-von-Laven

    Hmm... I take it all back. You could just return Dict[str, Any] and call isinstance() on the whole thing anyways, and there's nothing lost really since the concrete type of the JSON response is only known at runtime anyways.

  13. JukkaL commented on Nov 19, 2022

    @JukkaL
    Collaborator

    This was fixed by #13166.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions