We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
There was an error while loading. Please reload this page.
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
In this PR, we allow the _ sigil to appear in type argument lists in expression positions as a placeholder for locations where you would like inference to occur:
_
const instance = new Foo<_, string>(0, ""); const result = foo<_, string>(0, ""); const tagged = tag<_, string>`tags ${12} ${""}`; const jsx = <Component<_, string> x={12} y="" cb={props => void (props.x.toFixed() + props.y.toUpperCase())} />;
This allows users to override a variable in a list of defaulted ones without actually explicitly providing the rest or allow a type variable to be inferred from another provided one.
Implements #26242. Supersedes #23696.
Fixes #20122. Fixes #10571.
Technically, this prevents you from passing a type named _ as a type argument (we do not reserve _ in general and don't think we need to). Our suggested workaround is simply to rename or alias the type you wish to pass. Eg,
interface _ { (): UnderscoreStatic; } foo<_>(); / bad - triggers partial type inference, instead: type Underscore = _; foo<Underscore>(); / good
we did a quick check over at big ts query, and didn't find any public projects which passed a type named _ as a type argument in an expression/inference position, so it seems like a relatively safe care-out to make.
Prior work for the _ sigil for partial inference includes f#, so it should end up being pretty familiar.
Sorry, something went wrong.
Why write infer explicitly? Could we do like destructuring? <, string> instead of <infer, string>
infer
<, string>
<infer, string>
By default, it would infer.
#26242
Would this PR enable this scenario? I didn't see a test quite like it. Basically extracting an inferred type parameter from a specified type parameter.
type Box<T> = { value: T }; type HasBoxedNumber = Box<number>; declare function foo<T extends Box<S>, S>(arg: T): S; declare const hbn: HasBoxedNumber; foo<HasBoxedNumber, infer>(hbn).toFixed();
Based on the design meeting feedback, this has been swapped to variant 2 from the proposal - using the * sigil as a placeholder for inference. We'll need updates to our tmlanguage to get syntax highlighting right (although we already parsed * in type positions for jsdoc, so we probably should have already).
*
Treat * in type argument list as infering type argument
d33674e
Support for microsoft/TypeScript#26349
As is, no. Other type parameters (supplied or no) are not currently inference sites for a type parameter. We could enable it here (just by performing some extra inferType calls between the supplied types and their parameters' constraints), probably, but... should we? @ahejlsberg you have an opinion here?
inferType
@ahejlsberg you have an opinion here?
I don't think we want constraints to be inference sites, at least not without some explicit indication. At some point we might consider allowing infer declarations in type parameter lists just as we do in conditional types:
type Unbox<T extends Box<infer U> = U;
Though you can get pretty much the same effect with conditional types:
type Unbox<T extends Box<any> = T extends Box<infer U> ? U : never;
Alright, I'll leave this as is then and just mention that it's available as a branch if we ever change our minds in the future.
@weswigham It seems inconsistent (and kinda strange) to use the * sigil for this when we already use the infer keyword to denote explicit type inference...
type Tagged<O extends object, T> = O & { __tag: T }; / "Infer a type, and make it available under the alias 'T'" declare function getTag<O extends Tagged<any, any>(object: O): O extends Tagged<any, infer T> ? T : never; / "Infer a type, and make it available to 'getTag' under the alias at the first type position" getTag<infer>({ foo: string, __tag: 'bar' }) / => 'bar'
This seems like an obvious syntactic duality to me... What was the reason you instead decided to go with *?
The existing infer T keyword produces a new binding for T; this wouldn't be available in argument positions (e.g. you can't write getFoo<infer T, T>()). Having the infer keyword have arity 1 in conditional types and arity 0 in type argument positions seems like a decrease in overall consistency rather than an increase.
infer T
T
getFoo<infer T, T>()
It would probably be nice to be able to declare infer on the functions, ex: function foo<A, B = infer>(b: B, c: SomeComplexType<A,B>): SomeOtherComplexType<A,B>
function foo<A, B = infer>(b: B, c: SomeComplexType<A,B>): SomeOtherComplexType<A,B>
@RyanCavanaugh
Having the infer keyword have arity 1 in conditional types and arity 0 in type argument positions seems like a decrease in overall consistency rather than an increase.
Thanks for the response. :)
Fair enough, but I'd argue that this decrease in consistency is far less than that of introducing an entirely new sigil for this purpose. Is there really a benefit to users in using such a radically different syntax for something whose only difference to infer T is the arity?
Something else to consider is that TypeScript supports JSDoc, and * in JSDoc means any. I'm not sure it's a good idea to reuse a symbol that means any in one context for something that means "please infer this type for me" in another context.
any
If we're concerned about making operators/keywords context-sensitive, then again it seems like making infer context-sensitive is far less of an evil than doing the same for *.
I don't mind * as it jives with flow. Users of typescript can just avoid * in jsdoc and always use any for the purpose easily enough?
I'd also like to see this:
const instance = new Blah<T, **>(1, 'b', false, new Date()
I have a class that bundles many string literal types and I have to enumerate them all at every callsite even when I'm using the code from this branch. Everytime I add a new string literal I have to update every single callsite which is a massive drag ;)
Consider:
type LiteralMap<S1 extends string, S2 extends string, S3 extends string> = { item1: S1, item2: S2, item3: S3 }
With this feature at every definition using this type I have to use:
function user(map: LiteralMap<*, *, *>) {}
Now if I need to add a new literal to my map I have to update this to:
type LiteralMap<S1 extends string, S2 extends string, S3 extends string, S4 extends string> = { item1: S1, item2: S2, item3: S3, item4: S4, }
which is no big deal, but now I also have to update every single use of this to:
function user(map: LiteralMap<*, *, *, *>) {}
With LiteralMap<**> I can just update the definition without affecting every area it is used.
LiteralMap<**>
Or it could follow the tuple system
type LiteralMap<S1?, S2?, S3?> = { item1: S1, item2: S2, item3: S3 } function user(map: LiteralMap) {} / infer, infer, infer function user(map: LiteralMap<boolean>) {} / boolean, infer, infer function user(map: LiteralMap<_, boolean>) {} / infer, boolean, infer type LiteralMap<S1, S2, S3?> = { item1: S1, item2: S2, item3: S3 } function user(map: LiteralMap) {} / not allowed, S1 and S2 missing function user(map: LiteralMap<boolean>) {} / not allowed, S2 missing function user(map: LiteralMap<_, boolean>) {} / infer, boolean, infer
alternatively it could use the default assignation (which I guess makes more sense, since if you want it to infer the default type makes no sense?)
type LiteralMap<S1 = _, S2 = _, S3 = _> = { item1: S1, item2: S2, item3: S3 } function user(map: LiteralMap) {} / infer, infer, infer function user(map: LiteralMap<boolean>) {} / boolean, infer, infer function user(map: LiteralMap<*, boolean>) {} / infer, boolean, infer type LiteralMap<S1, S2, S3 = _> = { item1: S1, item2: S2, item3: S3 } function user(map: LiteralMap) {} / not allowed, S1 and S2 missing function user(map: LiteralMap<boolean>) {} / not allowed, S2 missing function user(map: LiteralMap<_, boolean>) {} / infer, boolean, infer
Much better than #23696. Great stuff 👍 Thanks!
@RyanCavanaugh What the problem with this PR? Is this too complicated? I wait this feature more than 2 years. I really need this one in a lot of places. Currently I need to use dirty hacks like
function x<A>() { return <B>(prop: {a: A, b: B})=>void} x<number>()({a:1, b:'b'})
I've throught quite a bit about partial type inference and I agree that, while this is a good step forward, I have a need for definition-site inference much more often (I suspect the divergence of opinions we've seen upthread is likely splitting across library users vs. library authors - the latter of whom are concerned that requiring the caller to opt into inference doesn't really help in making safe/ergonomic/hard-to-misuse APIs). So I'd also like to see at least some thought toward whether/how this syntax fits in with a hypothetical future definition-site syntax.
FWIW, I put together a gist a while back exploring this (see here, feedback appreciated), and concluded that infer T, while a better analog with the conditional type syntax, doesn't make as much sense because it really is about providing a default. Additionally, for the case mentioned several times upthread where the user should not ever specify this type directly, I proposed a private T = infer (or private T = _, if that placeholder sticks). There's a lot of synergy to considering infer and private together: while each has value on its own, the combination is greater than the sum of its parts.
private T = infer
private T = _
private
Can we split generic types by some symbol, like ";". Before the splitter, generic types that can be provided from outside. After the splitter, they are never be provided from outside.
declare function foo < / available outside O extends Record<string, any>; / private V extends string > (value: V): O & Record<'value', V> const test = foo<{ test: true }>('value') / test: { test: true } & { value: 'value' }
Can we split generic types by some symbol, like ";". Before the splitter, generic types that can be provided from outside. After the splitter, they are never be provided from outside. declare function foo < / available outside O extends Record<string, any>; / private V extends string > (value: V): O & Record<'value', V> const test = foo<{ test: true }>('value') / test: { test: true } & { value: 'value' }
Considering that EcmaScript has introduced and TypeScript has adopted an ergonomic # to specify "private" fields in classes, I think it would be intuitive enough to use the same ergonomic approach to specify private type parameters:
#
declare function foo < O extends Record<string, any>, #V extends string > (value: #V): O & Record<'value', #V> const test = foo<{ test: true }>('value') / test: { test: true } & { value: 'value' }
I'd prefer if TypeScript didn't add non-standard features that look like private class fields, as it may increase usage of private class fields, which don't support certain libraries that rely on Proxies (including Vue).
Alternatively, we could support both # and the private keyword, like TypeScript already does in class declarations.
Can we split generic types by some symbol, like ";". Before the splitter, generic types that can be provided from outside. After the splitter, they are never be provided from outside. declare function foo < / available outside O extends Record<string, any>; / private V extends string > (value: V): O & Record<'value', V> const test = foo<{ test: true }>('value') / test: { test: true } & { value: 'value' } Considering that EcmaScript has introduced and TypeScript has adopted an ergonomic # to specify "private" fields in classes, I think it would be intuitive enough to use the same ergonomic approach to specify private type parameters: declare function foo < O extends Record<string, any>, #V extends string > (value: #V): O & Record<'value', #V> const test = foo<{ test: true }>('value') / test: { test: true } & { value: 'value' }
Looks qualified to me. I suggested ";" like in "for i" loop syntax. Any way, there are no cases to mix private and public types.
I'm not sure about
declare function foo < #V extends string, O extends Record<string, #V> > (value: #V): O & Record<'value', #V> const test = foo<{ test: true }>('value') / test: { test: true } & { value: 'value' }
This discussion of private generic parameters is getting a bit off-topic to this issue (though as I asserted above, it's tangentially relevant insofar as the syntax should work well together). I am very interested in continuing the private types discussion, but I think it's more appropriate on #42388 so I've moved the discussion there.
As far as this specific issue goes, I'm definitely in favor, don't particularly care about _ vs infer, and would love to see the follow-up of definition-site partial inference happen sooner rather than later.
Simplify using animation loops for css transitions
f7b38c0
This would be even cleaner with microsoft/TypeScript#26349
derived
any updates here? would love the ability to partially supply generics while the remainder continue to infer
scratch work on deriving the params object type from the string passe…
106d747
…d as the path spec it all works except that we also take the returned value type as a type parameter, and typescript won't infer the pathspec string type to the literal passed if you pass the return type. its both or neither. when microsoft/TypeScript#26349 is merged, this should work
Coming from this issue: #19205 . It would be nice if the inference wouldn't be turned off.
.attrs()
Any news on this topic? Following the issue for years. It would be great to have insight on whether this is approved or not. Seeing the comments here this feature seems quite demanded. How it's done is another thing.
https://github.com/microsoft/TypeScript/wiki/FAQ#any-updates.
TransformT
All the recent activity on this PR has been design discussion. I think we should close this and go back to design to get something workable there.
sandersn Awaiting requested review from sandersn
andrewbranch Awaiting requested review from andrewbranch
Copilot code review Copilot Awaiting requested review from Copilot Copilot will automatically review once the pull request is marked ready for review
At least 1 approving review is required to merge this pull request.
weswigham
Successfully merging this pull request may close these issues.