You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
mypy doesn't escape typevars from generics in self-type #18400
Passing a callable to another which matches the typevar with another typevar doesn't allow passing them into the return type. (if that makes no sense, the reproducer below is simple)
To Reproduce
fromtypingimportGeneric, TypeVar, CallableT=TypeVar("T")
V=TypeVar("V")
classX(Generic[T]):
deff(self: X[Callable[[V], None]]) ->Callable[[V], V]:
definner_f(v: V) ->V:
returnvreturninner_freveal_type(X[Callable[[T], None]]().f()) # N: Revealed type is "def (Never) -> Never"# ^ this should keep the generic
Expected Behavior
I don't expect a Never.
Your Environment
Checked on mypy playground.
Mypy version used: v1.14
Mypy command-line flags: --strict
Mypy configuration options from mypy.ini (and other config files): N/A
changed the title [-]mypy doesn't escaped stored typevartuples in a self-type[/-][+]mypy doesn't escape stored typevartuples in a self-type[/+]on Jan 1, 2025
changed the title [-]mypy doesn't escape stored typevartuples in a self-type[/-][+]mypy doesn't escape stored typevars in a self-type[/+]on Jan 1, 2025
Hm. Are you sure this is supposed to work at all? There is no precedent of bare typevar at top level working like this, otherwise we'd have some fancy features like dict[T, Callable[[T], None]] top-level type map to object handlers which, I believe, have never been supported (and maybe shouldn't be - what does the spec say?).
As a rule of thumb, there's no way to spell your repro using horrible PEP695 syntax, so the typevar is used in a wrong scope. Mypy should produce an error right there ("Type variable T is unbound"), and absence of that diagnostic is indeed a bug.
If I fix your example to put the typevar in scope, mypy does what you expected:
Hm. Are you sure this is supposed to work at all? There is no precedent of bare typevar at top level working like this, otherwise we'd have some fancy features like dict[T, Callable[[T], None]] top-level type map to object handlers which, I believe, have never been supported (and maybe shouldn't be - what does the spec say?).
This should be valid because callables capture their generic. I'm also just doing this for a more simple reproducer. Here's the less minimized version of my failing example:
from __future__ importannotationsfromdataclassesimportdataclassfromtypingimportTypeVarTuple, Unpack, Generic, TypeVar, Any, overloadfromcollections.abcimportCallable# TODO: hide this in an internal file so that I can have descriptive names without polluting namespaceArgs=TypeVarTuple("Args")
classBuild(Generic[Unpack[Args]]):
passclassBuilt(Generic[Unpack[Args]]):
passFrom=TypeVarTuple("From")
To=TypeVarTuple("To")
InitialCallable=TypeVar("InitialCallable", bound=Callable[[Build[Unpack[tuple[Any, ...]]]], Build[Unpack[tuple[Any, ...]]]])
@dataclassclassBuilder(Generic[InitialCallable]):
initial_callable: InitialCallabledef__call__(self: Builder[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]]) ->Callable[[Callable[[object, Unpack[To]], None]], Built[Unpack[From]]]:
defbuild_up(in_progress: Callable[[object, Unpack[To]], None]) ->Built[Unpack[From]]:
...
returnbuild_updefmake_builder() ->Callable[[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]], Builder[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]]]:
definner_make_builder(command: Callable[[Build[Unpack[From]]], Build[Unpack[To]]]) ->Builder[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]]:
returnBuilder(command)
returninner_make_builderfromtypingimportUnpack, TypeVarTuple#Args = TypeVarTuple("Args")deftest_basic_command() ->None:
@make_builder()defcommand_builder(b: Build[Unpack[Args]]) ->Build[Unpack[Args]]:
returnbreveal_type(command_builder()) # N: Revealed type is "def (def (builtins.object, *Never)) -> __main__.Built[Unpack[builtins.tuple[Never, ...]]]"# ^ I expect this to work
To prove this should work, I've turned Builder.__call__ into an explicit call to Builder_call and removed use of a self-type:
from __future__ importannotationsfromdataclassesimportdataclassfromtypingimportTypeVarTuple, Unpack, Generic, TypeVar, Any, overloadfromcollections.abcimportCallable# TODO: hide this in an internal file so that I can have descriptive names without polluting namespaceArgs=TypeVarTuple("Args")
classBuild(Generic[Unpack[Args]]):
passclassBuilt(Generic[Unpack[Args]]):
passFrom=TypeVarTuple("From")
To=TypeVarTuple("To")
InitialCallable=TypeVar("InitialCallable", bound=Callable[[Build[Unpack[tuple[Any, ...]]]], Build[Unpack[tuple[Any, ...]]]])
@dataclassclassBuilder(Generic[InitialCallable]):
initial_callable: InitialCallabledefBuilder_call(self: Builder[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]]) ->Callable[[Callable[[object, Unpack[To]], None]], Built[Unpack[From]]]:
defbuild_up(in_progress: Callable[[object, Unpack[To]], None]) ->Built[Unpack[From]]:
...
returnbuild_updefmake_builder() ->Callable[[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]], Builder[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]]]:
definner_make_builder(command: Callable[[Build[Unpack[From]]], Build[Unpack[To]]]) ->Builder[Callable[[Build[Unpack[From]]], Build[Unpack[To]]]]:
returnBuilder(command)
returninner_make_builderfromtypingimportUnpack, TypeVarTuple#Args = TypeVarTuple("Args")deftest_basic_command() ->None:
@make_builder()defcommand_builder(b: Build[Unpack[Args]]) ->Build[Unpack[Args]]:
returnbreveal_type(Builder_call(command_builder)) # N: Revealed type is "def [From] (def (builtins.object, *Unpack[From`330])) -> __main__.Built[Unpack[From`330]]"# ^ now it works!
Here's a much smaller and more obviously correct reproduction:
importdataclassesfromtypingimportProtocol, TypeVar, ParamSpec, Generic, CallableT=TypeVar("T", covariant=True)
P=ParamSpec("P")
classMeta(type):
def__call__(cls, *args: object, **kwargs: object) ->None:
raiseTypeError(
f"{cls.__module__}.{cls.__qualname__} has no public constructor",
)
def_create(cls: Callable[P, T], *args: P.args, **kwargs: P.kwargs) ->T:
returnsuper().__call__(*args, **kwargs) # type: ignore@dataclasses.dataclassclassX(Generic[T], metaclass=Meta):
x: Treveal_type(X._create) # N: Revealed type is "def (*args: Never, **kwargs: Never) -> Never"
(the main point of contention I see is that cls: Callable[P, T] should maybe match __call__, but that would have (*args: object, **kwargs: object) and just isn't the case currently.)
Activity
[-]mypy doesn't escaped stored typevartuples in a self-type[/-][+]mypy doesn't escape stored typevartuples in a self-type[/+]A5rocks commentedon Jan 1, 2025
Turns out this applies to TypeVars too -- I'll update this issue for that (since it's simpler).
[-]mypy doesn't escape stored typevartuples in a self-type[/-][+]mypy doesn't escape stored typevars in a self-type[/+][-]mypy doesn't escape stored typevars in a self-type[/-][+]mypy doesn't escape typevars from a passed in function[/+][-]mypy doesn't escape typevars from a passed in function[/-][+]mypy doesn't escape typevars that partially match[/+][-]mypy doesn't escape typevars that partially match[/-][+]mypy doesn't escape typevars from generics in self-type[/+]sterliakov commentedon Jan 10, 2025
Hm. Are you sure this is supposed to work at all? There is no precedent of bare typevar at top level working like this, otherwise we'd have some fancy features like
dict[T, Callable[[T], None]]top-level type map to object handlers which, I believe, have never been supported (and maybe shouldn't be - what does the spec say?).As a rule of thumb, there's no way to spell your repro using
horriblePEP695 syntax, so the typevar is used in a wrong scope. Mypy should produce an error right there ("Type variable T is unbound"), and absence of that diagnostic is indeed a bug.If I fix your example to put the typevar in scope,
mypydoes what you expected:plaground
A5rocks commentedon Jan 10, 2025
This should be valid because callables capture their generic. I'm also just doing this for a more simple reproducer. Here's the less minimized version of my failing example:
To prove this should work, I've turned
Builder.__call__into an explicit call toBuilder_calland removed use of a self-type:A5rocks commentedon May 3, 2025
Here's a much smaller and more obviously correct reproduction:
(the main point of contention I see is that
cls: Callable[P, T]should maybe match__call__, but that would have(*args: object, **kwargs: object)and just isn't the case currently.)