Skip to content

Bug - Type aliases in closure does not work #8273

Description

@rmorshea

This may be a known limitation of the type system, but it would be nice if it were possible to write the following without the commented error:

from typing import Any, Callable, Type, TypeVar
from threading import Lock

F = TypeVar("F", bound=Callable[..., Any])
Decorator = Callable[[F], F]  # <--- the problem type alias

def with_lock(lock: Lock) -> Decorator[F]:
    def decorator(function: F) -> F:
        def wrapper(*args, **kwargs) -> Any:
            with lock:
                return function(*args, **kwargs)
        return wrapper  # type: ignore
    return decorator

l = Lock()

reveal_type(with_lock(l))  # note: Revealed type is 'def (<nothing>) -> <nothing>'

@with_lock(l)  # error: Argument 1 has incompatible type "Callable[[Any], None]"; expected <nothing>
def use_resource(resource: Any) -> None:
    ...

In order for this to work correctly I have to replace the type alias Decorator with the explicit Callable[[F], F]. This is trivial to change in this case, but for more complex types this would inhibit readability greatly.

Details:

What version of MyPy: 0.761
Have you tried on master: no
Are there any flags engaged: no

Activity

  1. changed the title [-]Feature - Type aliases in closures don't work[/-] [+]Bug - Type aliases in closures don't work[/+] on Jan 11, 2020
  2. changed the title [-]Bug - Type aliases in closures don't work[/-] [+]Bug - Type aliases in closure does not work[/+] on Jan 11, 2020
  3. JukkaL commented on Jan 13, 2020

    @JukkaL
    Collaborator

    Here is a simplified example that shows the root cause of the unexpected behavior:

    from typing import TypeVar, Callable
    
    T = TypeVar('T')
    
    F = Callable[[T], T]
    
    def f() -> F[T]: ...
    def g() -> Callable[[T], T]: ...
    
    reveal_type(f)  # def [T] () -> def (T`-1) -> T`-1
    reveal_type(g)  # def () -> def [T] (T`-1) -> T`-1

    The scope (?) of the type variable is wrong when using a type alias. The type of f should be the same as the type of g.

  4. TH3CHARLie commented on Jan 20, 2020

    @TH3CHARLie
    Collaborator

    I've investigated this for a little while. For case like

    from typing import TypeVar, Sequence
    T = TypeVar('T')
    S = Sequence[T]
    def f() -> S[T]: ...
    def g() -> Sequence[T]: ...

    T gets bound to the function after semanal/analyze_func_def: update_function_type_variables, and then when S[T] get analyzed, T will get a binding from the function scope. Therefore, in this issue, the type variable is bound to the function instead of the aliased callable. To proper fix this issue involves at least two major steps:

    1. Update semanal/update_function_type_variables to explicitly handle TypeAlias when its target is a callable. Currently, I modify typeanal/visit_unbound_type and mimic the behavior when encountering a callable. This part seems OK.
    2. Analyze function signature, bind T. However, Callable[[T], T] is specially handled while TypeAlias go through a more generic expanding approach.

    That's all I've gone so far and some insights, either high-level abstraction or low-level details would help a lot on fixing this @JukkaL

  5. rmorshea commented on Aug 29, 2020

    @rmorshea
    Author

    Definitely been feeling the pain of this one lately. Long type annotations (that could otherwise be broken up type aliases) are really inhibiting code readability. Anyone know of good workarounds for this?

  6. AlexWaygood commented on Apr 4, 2022

    @AlexWaygood
    Member

    Closing as a duplicate of #3924

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