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
Implement partial type argument inference using the _ sigil #26349
base: main
Are you sure you want to change the base?
Conversation
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 |
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. |
@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 |
It would probably be nice to be able to declare infer on the functions, ex: |
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 |
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 |
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 ;) |
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 |
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 |
I'm relatively new to TypeScript and I expected this feature to already be available. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Still no progress? |
At mswjs/msw#1175 we are trying to infer path parameters from a URL. We haven't find a way yet to both allow customers to define generics, and infer the parameters. Is there any workaround that we can use today to get this shipped? Thanks. |
Curried functions (outer one for explicit generics, inner one for inferred ones). Or allow providing generics through "fake" properties/arguments (u can check out what we are doing with |
Given the recent addition of variance annotations - would it make sense to introduce |
I was thinking the exact same thing the other day after seeing both the new generic constraint syntax and variance annotations, I feel like we should be able to come up with something better than |
function foo<T, TName extends string = string>(name: TName): [T, TName] {
[...]
}
const a = foo('bar'); // correct a: [ unknown, 'bar' ]
const b = foo<number>('baz'); // not correct b: [ number, string ] instead of [ number, 'baz' ]
const c = foo<number, 'baz'>('baz'); // correct c: [ number, 'baz' ] And looking at the example, the |
Rather than using curried functions, which seem like extra gymnastics, I've resorted to "fully qualified" typing; that is, just fixing the "expected 2 type arguments, but got 1" problem that Typescript reports by filling in the type parameter explicitly that should be inferred. Admittedly, mine is a simple case: interface FooArgs {
//...
}
function foo<T extends Builder, U extends Record<string, string|number>>( builder: T, args: FooArgs, key: keyof U ) {
//...
} Where Still, it would be really, really helpful to be able to write generic functions knowing that in most cases I only need to focus on typing where type information is required to complete the function call. Here's hoping this PR regains some momentum! |
in light of #46827, i'm following up on #26349 (comment) - afaict, a lot (albeit not all) of the feature requests in this proposal are resolved via one of these two mechanisms:
|
I don’t understand how it is resolving the issue of having parameters infered in a generic typed function. This is literally something so many people want and it breaks nothing. That’s the default behavior when no type is given, we would like to do the same when we need to pass on (without having to respecify every types or having to redefine a contract) |
It would break the case when latter arguments specify defaults. The inferred type would be chosen (new) over the default type (current). Although I think it's very very unlikely there's many cases in the world where the default value would either be different to the inferred value, or a type that leads to a successful compilation. I think 99.9999% of the time this feature would just turn a compiler error into a desirable type selection. |
It's not about changing the current behavior but adding a |
Still doesn’t fix the pain of having 10+ generic slots on a type. I wish we had both _ and a way to do partial/named generics.
…On Sep 9, 2022, 3:58 AM -0600, Debove Christopher ***@***.***>, wrote:
> > @btoo
> > > in light of #46827, i'm following up on #26349 (comment) - afaict, a lot (albeit not all) of the feature requests in this proposal are resolved via one of these two mechanisms:
> > >
> > > • satisfies
> > > • a combination of satisfies and instantiation expressions
> > >
> > I don’t understand how it is resolving the issue of having parameters infered in a generic typed function. This is literally something so many people want and it breaks nothing. That’s the default behavior when no type is given, we would like to do the same when we need to pass on (without having to respecify every types or having to redefine a contract)
> It would break the case when latter arguments specify defaults. The inferred type would be chosen (new) over the default type (current). Although I think it's very very unlikely there's many cases in the world where the default value would either be different to the inferred value, or a type that leads to a successful compilation. I think 99.9999% of the time this feature would just turn a compiler error into a desirable type selection.
It's not about changing the current behavior but adding a infer of the _ sigil to specify you want your types to be infered from parameters. This, does not break anything
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
This PR is awesome, but partial/named generics is so much better, as it's follows the same principles from underlying javascript, which in fact everyone here is familiar with. |
Any chance this gets implemented? Would be extremely useful |
Just a small thing, but allowing the type Options = {
name: string;
};
class Command<const O extends Options, R extends string> {
constructor(options: O, executor: R) { /* ... */ }
getName(): O["name"] { /* ... */ }
invoke(): R { /* ... */ }
}
class Subcommand extends Command<
_, // or whatever would be implemented, `infer`, `auto`, `*`, ect...
"error" | "success",
> {
constructor() {
super({ name: "subcommand" }, () => "success" as const);
// Different super calls by letting the types be inferred *entirely* would make type resolution extremely difficult...
/*
if (Math.random() > 0.5) {
super({ name: "sub2" }, () => "error");
} else {
super({ name: "sub1" }, () => "success");
}
*/
// What do we resolve to? Both types? Or the first?
// We now have direct access to the information we passed *once*, keeping with the DRY principle.
this.getName(); // => "subcommand"
}
} |
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.