Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upImplement partial type argument inference using the _ sigil #26349
Conversation
alfaproject
commented
Aug 10, 2018
|
Why write By default, it would infer. |
|
@alfaproject I wrote my rationale down in #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 |
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 |
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 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. |
treybrisbane
commented
Aug 18, 2018
•
|
@weswigham It seems inconsistent (and kinda strange) to use the 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 |
KyleDavidE
commented
Aug 22, 2018
|
It would probably be nice to be able to declare infer on the functions, ex: |
treybrisbane
commented
Aug 23, 2018
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 |
treybrisbane
commented
Aug 23, 2018
|
Something else to consider is that TypeScript supports JSDoc, and If we're concerned about making operators/keywords context-sensitive, then again it seems like making |
insidewhy
commented
Aug 31, 2018
•
|
I don't mind 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 ;) |
insidewhy
commented
Sep 1, 2018
|
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 |
xaviergonz
commented
Sep 1, 2018
•
|
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, inferalternatively 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 |
nickbclifford
commented
Jun 29, 2019
•
|
What's blocking this from being merged right now? |
trusktr
commented
Jul 14, 2019
•
|
This will be useful in making mixin class definitions more terse! examplesI wanted to do the following, but then I discovered that supplying only the first generic arg to a generic type causes the remaining args not to be inferred: function AwesomeMixin<T extends Constructor>(Base: T) {
// This would be nice, a properly typed mixin all in a single return statement.
// Pretend the second arg in the following MixinResult application is inferred:
return MixinResult<T>(class Awesome extends Constructor(Base) {
// ...
})
}
type Constructor<T = object, A extends any[] = any[]> = new (...a: A) => T
function Constructor<T = object, Static = {}>(Ctor: Constructor<any>) {
return (Ctor as unknown) as Constructor<T> & Static
}
function MixinResult<TBase extends Constructor, TClass extends Constructor = Constructor>(Class: TClass) {
return Class as Constructor<InstanceType<TClass> & InstanceType<TBase>> & TClass & TBase
}Note, you may think that you can still have a single-return mixin and don't need to do what I've done above, but if you try composing multiple mixins, then you'll run into issues in #32080. I need to compose mixins like the following: // a mixin for use on Custom Elements, and composed of multiple mixins:
function AwesomeMixin<T extends Constructor<HTMLElement>>(Base: T) {
// Pretend the second arg in the following MixinResult application is inferred:
return MixinResult<T>(class Awesome extends Mixin1(Mixin2(Constructor<HTMLElement>(Base))) {
// ... inside here, we get all types from Mixin1, Nixin2, and HTMLElement ...
})
}where But without some new feature from #32080, writing just For the time being, I have to resort to the slightly less terse version due to current lack of inference on generic args when only some are supplied: function AwesomeMixin<T extends Constructor<HTMLElement>>(Base: T) {
class Awesome extends Mixin1(Mixin2(Constructor<HTMLElement>(Base))) {
// ...
}
// less terse, more duplication of the Awesome identifier
return Awesome as MixinResult<typeof Awesome, T>
}
// ... the other parts omitted ... and MixinResult is only a type, not a function:
type MixinResult<TClass extends Constructor, TBase extends Constructor> =
Constructor<InstanceType<TClass> & InstanceType<TBase>> & TClass & TBaseAnd even if we had the inferred elided args in the future, but you still wanted the return to be a separate statement, then the benefit of this feature would still be nice: function AwesomeMixin<T extends Constructor<HTMLElement>>(Base: T) {
class Awesome extends Mixin1(Mixin2(Constructor<HTMLElement>(Base))) {
// ...
}
// better, one less "Awesome" identifier than the previous example
return MixinResult<T>(Awesome)
}
// ... other stuff omitted ... but MixinResult is a function again:
function MixinResult<TBase extends Constructor, TClass extends Constructor = Constructor>(Class: TClass) {
return Class as Constructor<InstanceType<TClass> & InstanceType<TBase>> & TClass & TBase
} |
fwh1990
commented
Oct 14, 2019
I really need this feature right now. |
szszoke
commented
Nov 22, 2019
•
|
Could this support a scenario where the inference of a generic parameter would be the default behavior? I'm thinking of something like this:
This would be useful for Redux action creators. It would allow us to have an optional generic parameter but still have |
dhoulb
commented
Nov 23, 2019
•
|
Just adding some design thoughts :) Use the
|
jesseoh
commented
Dec 7, 2019
•
|
@Nandiin If you modify your first workaround example a bit, the end usage isn't really all that bad. You still have multiple parentheses, but at least the generic parameters aren't awkwardly sandwiched between them: type Modified<O, K extends string, T> = O & Record<K, (v: T) => void> \n
declare function modify<T>(): <O, K extends string>(target: O, k: K) => Modified<O, K, T>
const John = {}
const modified = modify<number>()(John, 'age')
modified.age // (v: number) => void
modified.age(18)` |
Ranguna
commented
Dec 15, 2019
|
@weswigham You are incredible, great work! I'm patiently waiting for this merge. Is there any ETA on this merge ? |
Losses
commented
Jan 15, 2020
|
Yeah... currying looks so dirty... hope this PR could be merged soon... |
alvis
commented
Apr 16, 2020
|
Like @dhoulb's idea on using the type TypeConstraint = string | boolean;
function getName<A, infer B extends TypeConstraint>(a: A, b: B): B {}
// ok
const abc: boolean = getName<string>("abc", true);
const abc: boolean = getName<string, boolean>("abc", true);
// error
const abc: boolean = getName<string>("abc", 10); |
satanTime
commented
May 10, 2020
|
Should someone overtake this? I see last changes in 2018, but this feature would be really cool |
|
This is not stalled for lack of ownership, rather, for lack of drive to accept the feature in the first place. |
satanTime
commented
May 10, 2020
•
|
I see, sad sad, I would vote for |
zachkirsch
commented
May 10, 2020
@DanielRosenwasser @ahejlsberg @RyanCavanaugh @andy-ms as the reviewers on this PR, can you provide an update as to why this PR isn't being accepted? There seems to a lot of interest from the community in this feature. |
insidewhy
commented
May 11, 2020
|
The sad thing about this is it's a problem I hit with almost every typescript project I work on. And the only way around it is to add an extra function. So in turn my APIs stop making sense (especially for javascript consumers) because now people have to use |
kotarella1110
commented
May 11, 2020
|
I thought this would be accepted in the near future as it was added to the roadmap. |
awerlogus
commented
May 13, 2020
|
@weswigham Maybe the better way is to implement this proposal?
Then we will be available to move all type parameters that should be inferred automatically to the end of the list and assign them all default values. This approach has some pros:
|
trusktr
commented
May 13, 2020
•
|
@awerlogus but there will still be a case of someone wanting to specify a specific type arg in the middle of the inferrable parameters. That proposal I don't think covers that case like this one does. I also like Plus I think it makes things like the following more sensible and inline with familiar patterns: Instead of const instance = new Blah<T, **>(1, 'b', false, new Date())we can write const instance = new Blah<T, ...infer>(1, 'b', false, new Date())But arguably with trailing parameters that should just be const instance = new Blah<T>(1, 'b', false, new Date())but the only bad thing is that it may be a breaking change. :/ In that case, For non-trailing args, to avoid writing infer many times, what about something like const instance = new Blah<infer * 2, T, infer>(1, 'b', false, new Date())where the |
AustP
commented
Jun 21, 2020
|
I was looking for this functionality in TypeScript as well and I spent a few hours trying to get this working. During my exploration I naturally discovered the infer and leading comma syntaxes:
Although I'd be tickled pink if this PR got merged with the current syntax, I would prefer one of these alternative syntaxes. They have both been proposed already in this thread and I think it is worth noting that I discovered them both naturally before even seeing the proposals. On the other hand, I did not naturally discover the underscore syntax which is currently the syntax of the PR. To me that says that the underscore syntax should be replaced with one of the other two, but that's just my two cents. If I could choose, I would choose the infer syntax over the others. |
MrLoh
commented
Aug 2, 2020
|
It would be great to see this merged, no matter what syntax is used. |
jamiepine
commented
Aug 6, 2020
|
Also bumping this, would love the keyword "infer". Seems clean and verbose. |
YunHsiao
commented
Sep 1, 2020
•
|
This is... interesting. If it's for compatibility concerns can't we just set a new compiler flag on this and get on with it? |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.


weswigham commentedAug 10, 2018
•
edited
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: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,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 flow and f#, so it should end up being pretty familiar.