-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Add 'extends' clause to 'infer' type #48112
Conversation
Do we have a sense of what kind of libraries would benefit from this? |
Probably anyone doing tuple type or template literal type manipulation, plus #48112. I regularly find myself needing to define something like I found a few places that could leverage this:
|
You can add the Redux ecosystem (Redux Toolkit, Reselect) to that list - we have a few cases where we have a I'm pretty sure @tannerlinsley will have quite a few uses in React Table for this, but that's speculation from my side. |
I would greatly benefit from this in React Table and React Location where I am already writing a lot branching conditional types to both enforce and extract generics. It’s brittle. This would make those contracts easier to define and much more reliable. |
695e440
to
bafe193
Compare
@typescript-bot pack this |
Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at bafe193. You can monitor the build here. |
Hey @DanielRosenwasser, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
In the design meeting we discussed the syntax ambiguity with
I investigated using something like JS's |
I have been a bit wary of classifying all cases of |
Of course, but we only really use it in cases where we have the |
I'd also toss in "require parenthesization" as a possible parsing strategy type X<T> = T extends (infer U extends number) ? 1 : 0; |
Unfortunately, requiring parens alone doesn't resolve the ambiguity, since Right now I'm leaning towards (2) for its simplicity, but changing the // ok, parsed as conditional
type X1<T> = T extends ((infer U) extends number ? 1 : 0) ? 1 : 0;
// ok, parsed as `infer..extends` (speculative parse succeeds due to no trailing `?`)
type X2<T> = T extends (infer U extends number) ? 1 : 0;
// ok, parsed as `infer..extends` (conditional types not allowed in 'extends type' of conditional)
type X3<T> = T extends infer U extends number ? 1 : 0;
// ok, parsed as `infer..extends` (precedence wouldn't have parsed the `?` as part of a type operator)
type X4<T> = T extends keyof infer U extends number ? 1 : 0;
// ok, parsed as conditional (speculative parse rewinds when it sees the first `?`)
type X5<T> = T extends { [P in infer U extends keyof T ? 1 : 0]: 1; } ? 1 : 0;
// ok, parsed as `infer..extends` (no trailing `?`)
type X6<T> = T extends { [P in infer U extends keyof T]: 1; } ? 1 : 0;
// ok, parsed as conditional (speculative parse rewinds when it sees the first `?`)
type X7<T> = T extends { [P in keyof T as infer U extends P ? 1 : 0]: 1; } ? 1 : 0;
// ok, parsed as `infer..extends` (speculative parse succeeds due to no trailing `?`)
type X8<T> = T extends { [P in keyof T as infer U extends P]: 1; } ? 1 : 0; It doesn't work quite the same way as |
I'm gonna go ahead and ask the dumb question: Why can't we automatically derive the constraint of an inferred type binding from the constraint of the type being matched? For example, given type Quote<AString extends string> = `"${AString}"`;
type QuotedFirstElement<AStringTuple extends readonly string[]> =
AStringTuple extends readonly [infer FirstElement, ...infer Rest]
? Quote<FirstElement> // Currently errors here, because `FirstElement` is only constrained to `unknown`
: never; why can't we say "well, since Intuitively, I suspect there's a very good reason for this and that I just haven't thought it through enough, but someone's gotta spring the trap! 😁 |
We do have automatic inference, and there are ways we can improve it. However, that still wouldn't solve the issue at hand. There are times where you need a more-specific type than what the inferred constraint might allow: type X<T extends any[]> =
T extends [infer U extends string, ...infer R] ? ... :
T extends [infer U extends number, ...infer R] ? ... :
...; Above, I might want to make branching decisions based on a more specific constraint. This also ties into #48094, which uses more specific inferences for |
This last change modifies the parse based on #48112 (comment). I wanted to ensure that parenthesization was accurate, so I took a pass through the parenthesizer rule logic for types to be more accurate. One side-effect of that is that we'll emit fewer parens in declarations and diagnostics. Where we might have previously written @DanielRosenwasser, can you provide your thoughts on the change in diagnostics evidenced here: 38eb412?show-viewed-files=true&file-filters%5B%5D=#diff-daa0fded8957f3b2eecdd68e6f89377663f965eb6e9c2b83f36955200b6e858fL5-R6 |
@typescript-bot perf test |
38eb412
to
2fb093c
Compare
Ping @ahejlsberg, @weswigham, @sandersn, @RyanCavanaugh |
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.
The parser changes look good but I can't adequately comment on the checker changes.
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.
typeToTypeNodeHelper
in the checker needs to include the constraint in the context.inferTypeParameters
case of type.flags & TypeFlags.TypeParameter
. If it's not there, we'll elide the constraints in error messages and inferred type declaration emit (eg, if one of these is inlined into a function return type).
Other than that, looks good - pretty simple.
I think the latest change should cover this. I reuse |
2e4fa4f
to
01b9a2d
Compare
This adds an optional
extends
clause toinfer T
to specify an explicit constraint for theT
type parameter. When specified,the explicit constraint overrides the constraint we would have attempted to infer for
T
.For example, if you wished to infer a type from the first element of a tuple but also constrain that type, you might currently need to write something like the following:
For Option 1, you are forced to create two conditional types, resulting in two alternatives. If you are testing multiple other conditions, this could result in an unmanageable branching structure which results in the need to define additional type aliases for repeated branches:
For Option 2, you are required to define a second type to enforce the constraint. While this is trivial, it is unreliable when inferring to the same type variable in more than one position, as it is possible to define disjoint constraints:
In contrast, specifying an explicit constraint via
extends
allows us to achieve the simpler branching structure of Option 2, while providing additional safety by checking that the constraints are consistent:Related #48094