-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
PEP 696: Type defaults for TypeVars #2717
Conversation
pep-9999.rst
Outdated
where the parameter should ``start`` default to ``int``, ``stop`` | ||
default to ``start`` and step default to ``int | None`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where the parameter should ``start`` default to ``int``, ``stop`` | |
default to ``start`` and step default to ``int | None`` | |
where the parameter ``start`` should default to ``int``, ``stop`` shouldd | |
default to the type of ``start`` and ``step`` should default to ``int | None`` |
Also, do you mean stop
should also default to int
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No I thought for convince sake they'd be the same type most of the time so it'd save people the extra type 99% of the time
pep-9999.rst
Outdated
.. code:: py | ||
|
||
StartT = TypeVar("StartT", default=int) | ||
StopT = TypeVar("StopT", default=StartT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we should allow a TypeVar as the default for another TypeVar; this may make the TypeVar difficult to solve for in some cases. @erictraut do you have any opinion here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something that typescript supports so I naively thought it can't be an impossible feature to support
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this makes sense. A TypeVar used as a type argument must be associated with a scope (a generic class, function, or type alias). What scope is this type variable associated with? Or are you saying that this would be possible only in cases where the TypeVar is already associated with an outer scope?
class Outer[T]:
class Inner[X = T]:
...
I think the use cases for this are very rare, and it adds significant complexity — in the type checker implementation and in the specification (more edge cases to reason about), so I'd recommend against including this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it would need to have the TypeVar it's defaulting to bound to an outer scope so you could also do
class slice[StartT = int, StopT = StartT, StepT = int | None]: ...
slice[str]() # type is slice[str, str, int | None]
(in TS)
class Slice<StartT = number, StopT = StartT, StepT = number | null> {};
new Slice<string>() // type is Slice<string, string, number | null>
I don't think there are that many edge cases to reason with here, if a TypeVar with a default (T2) that is another TypeVar (T1) then T1 needs to be in scope before T2 can be used otherwise it's an error.
i.e.
class slice[StartT = StopT, StopT = int, StepT = int | None]: ...
doesn't work, I think this fits in quite neatly with PEP 695's handling of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this won't work for PEP 695. Because of all the runtime constraints that are imposed on the implementation of PEP 695, I need to allocate all of the type parameters before binding them to their names. That means the TypeVars StartT
, StopT
, and StepT
in your example above need to be constructed prior to referring to any of them by name. So the default expressions won't be able to refer to other type parameters.
Unless we have some really strong use cases in mind for this, I'd prefer to remove it from the PEP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could support this in the PEP 695 implementation if we construct the type variables first, then evaluate the default expressions and "inject" the result into the already-constructed type variable. The equivalent of this...
__local_typevars__ = (TypeVar("StartT"), TypeVar("StopT"), TypeVar("StepT"))
__local_typevars__[0].__default__ = int
__local_typevars__[1].__default__ = __local_typevars__[0]
__local_typevars__[2].__default = int | None
Co-authored-by: Jelle Zijlstra <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few little formatting nits.
Co-authored-by: Hugo van Kemenade <[email protected]>
To clarify do you mean defaults shouldn't be useable in functions at all or just to delete the compatibilty detection part? |
I'm recommending that the entire section "Function Defaults" should be deleted. This section currently says "The TypeVar's default should also be compatible with the parameter's runtime default if present...". I don't see any justification for this limitation. Unless I'm missing something, the default argument for a function parameter is completely unrelated to the default type of a TypeVar that happens to appear somewhere in the parameter's type annotation. Therefore, there should be no limitation that relates the two. A TypeVar default type is applicable in only two cases: Case 1. During explicit specialization where a type argument is omitted class Foo[S, T = int]: ...
reveal_type(Foo[str]()) # Revealed type is "Foo[str, int]"
# If we allow explicit specialization of generic functions in the future...
def foo[S, T = int](a: S, b: T) -> S | T: ...
reveal_type(foo[str]) # Revealed type is "(a: str, b: int) -> str | int" Case 2. When solving TypeVars in a call expression and the TypeVar remains unsolved class Foo[T = str]:
def __init__(self, val: Iterable[T] | None = None) -> None: ...
reveal_type(Foo()) # Foo[str]
def foo[T = str](a: T | None = None) -> T: ...
reveal_type(foo(1)) # Revealed type is int
reveal_type(foo()) # Revealed type is str Neither case 1 or 2 apply in the examples you've provided in the "Function Defaults" section, so the TypeVar default type doesn't apply in these cases. It should therefore be ignored, not flagged as an error. |
I see that there's already a lot of discussion about this PEP in the PR. Could we get a first version merged so that we can all discuss the PEP together in a proper forum? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise let's get this draft merged for discussion!
Co-authored-by: Hugo van Kemenade <[email protected]>
As the sponsor, I'd like to make sure the PEP is something I can stand behind before merging it. There are still a few small points I can't support. |
Proposes Type defaults for TypeVars, a way to specify the default type for an omitted type parameter:
I've had a thread on this on typing-sig for quite some time I just didn't have enough time to finalise all of the semantics, https://mail.python.org/archives/list/[email protected]/thread/7VWBZWXTCX6RAJO6GG67BAXUPFZ24NTC.
CC @JelleZijlstra