-
Notifications
You must be signed in to change notification settings - Fork 12.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
WIP - Allow infer types in expression type argument positions #22368
Conversation
17ddeec
to
f373993
Compare
So for example, this (based on actual code): export function withManyMixins<T, S extends Constructor<Observable<T>>>(
superclass: S): S & Constructor<MixedTogether<T>> {
const result1 = withFirstMixin<T, S>(superclass);
const result2 = withSecondMixin<T, typeof result1>(result1);
const result3 = withThirdMixin<T, typeof result2>(result2);
return result3;
} could be shortened to: export function withManyMixins<T, S extends Constructor<Observable<T>>>(
superclass: S): S & Constructor<MixedTogether<T>> {
return withThirdMixin<T, infer S2>(
withSecondMixin<T, infer S1>(
withFirstMixin<T, S>(superclass)
)
);
} with this PR, correct? Can the name of the inferred parameter be omitted if that variable isn't used anywhere? Otherwise, I need to make up unique names like |
Yeah it'd be nice to have the binding to a type variable optional, because most of the time you won't need this and as @appsforartists said it just means making up names that aren't actually used. |
Does this have the same joining behaviour for multiple occurrences of declare function swap<A,B>(pair: [A,B]): [B,A];
const result1 = swap<infer A, A>(['left', true]); // not ok
const result2 = swap<infer A, infer A>(['left', true]); // A = boolean | string Having syntactic sugar for Is it the case that the following two behave the same for all generic functions? declare function swap<A,B>(pair: [A,B]): [B,A];
const result1 = swap<infer A, infer B>(....); // explicit
const result2 = swap(...); // implicit |
declare function swap<A,B>(pair: [A,B]): [B,A];
const result1 = swap<infer A, infer B>(...); // explicit
const result2 = swap(...); // implicit Yes and no. In that case, mostly yes, but suppose you have something overloaded on generic arity: declare function merge<A,B,C>(root: A, last: B & C): A & B;
declare function merge<A,B>(root: A, last: B & {x: any}): A & B;
const result1 = merge<infer A, infer B>(...); // Selects 2-arity overload
const result2 = merge<infer A, infer B, infer C>(...); // Selects 3-arity overload
const result3 = merge(...); // implicitly attempts inference for all type-argument arities you can select a specific generic arity which is something not possible with an un-annotated call. |
I'm having trouble getting my head around this, but here's my use-case: export class Store {
_map = new Map();
do<Type, R, A1>(f: <Type>(a1: A1) => R, a1: A1): R
do<R>(f: (...args: any[]) => R, ...args: any[]): R {
return f.call(this, ...args);
}
}
let defaultStore: Store;
function getDefaultStore() {
if (!defaultStore) defaultStore = new Store();
return defaultStore;
}
export function get<Type>(this: any, key: any): Promise<Type> {
const store = this instanceof Store ? this : getDefaultStore();
return Promise.resolve(this._map.get(key));
} I'm hoping to be able to call: const store = new Store();
const val = store.do<string>(get, 'foo'); Where |
As per the discussion in #23045, we should close this one, and do named-type arguments + partially specified type argument list with inference |
I have a use case I think this PR will fix: consider the following (simplified): interface IBuilder<I> {
use(h: <II extends I = I>(ctx: II) => void): IBuilder<I>;
}
let app: IBuilder<{ name: string }>; If you don't specify any type information for ctx, then it is inferred automatically. If you do type it explicitly with something that runs contrary to the constaints app.use((ctx: { invalid: number }) => { }); ... then you get a property missing in type error. The problem arises when you want to introduce a generic app.use(<T>(ctx:T) => { }); at this point app.use(<T>(ctx: T & { invalid: number }) => { }); I'm hoping this PR would facilitate |
@mhegazy Is there already an issue to track doing "named type parameters + partially specified argument list"? |
No. it has been on my list to put a proposal out with the details discussed in #20398. The idea is to do both, allow partially specified type argument list (positional), and allow for named type arguments, and in both cases do inference on the unspecified parameters. I am using issue #10571 to track it meanwhile. |
I've closed this PR, since this is a little too complex for most uses, and doesn't help with the named-argument-bag case that named arguments also are desired for. Hopefully we'll have a candidate ready for named type arguments available soon(tm). |
That's very sad @weswigham. Many users, including myself, have been watching this WIP and anticipated much for its coming. IMHO, it is one of the most wanted features in Typescript. |
What's your practical use for it that can't also be solved with named type arguments and automatically inferring all missing type arguments? |
In Redux for instance, for creating a type-safe action, you really wish to have an inferred type e.g. interface FSA<A,P>{
type: A;
payload: P;
}
function actionADD<infer A, infer P>(amount: number): FSA<A, P> {
return {
type: "ADD",
payload: amount
};
} Here With typesafe-actions, we have achieved something, but it's still by far not ideal. At the moment, to achieve the resulting type above, we use const actionADD = createAction('ADD', (amount: number) => ({
type: 'ADD',
payload: amount,
})),
} Obviously, it is not the way an action function is usually written in js. typesafe-actions provides a type-safe option, but it also creates a new syntax for the sake of being type safe. Also, even with typesafe-actions, noticed that it still needs an extra parameter 'ADD' in the function. The existence of the parameter is purely due to the inability of referring the 'ADD' type from the returned object. IMO, users should not be required to change the syntax dramatically from the js world and yet they can still enjoy the benefit of typescript because types should be able to be automatically inferred. The need of much refactoring is a huge barrier stopping people to use typescript. |
If you just remove all the type annotations, interface FSA<A,P>{
type: A;
payload: P;
}
function actionADD(amount: number) {
return {
type: "ADD",
payload: amount
};
}
function fsaPayload<A, P>(fsa: FSA<A, P>) {
return fsa.payload
}
function fsaType<A, P>(fsa: FSA<A, P>) {
return fsa.type
}
fsaPayload(actionADD(10)) // number
fsaType(actionADD(10)) // string Type parameters on functions are for creating polymorphic functions, i.e. functions for which some parameter(s) may assume any type subject to constraints. We don't really need them if we're writing a function with a completely fixed type. |
This is not correct. The return type will be |
@MartinJohns Oh, right. See #20195. This would be an issue in the OP's original example as well, since In the meantime, you have to do: function actionADD(amount: number) {
return {
type: "ADD" as "ADD",
payload: amount
};
} |
@weswigham See #22368 (comment) the fleshed out example would be : app.use(<infer T>(ctx: T & {required: true}, next: (_ctx : T & {required: true, extra: string})=>void)=>void ); currently I can do... app.use((ctx, next: (_ctx : typeof ctx & { extra: string} ) =>void))=>{} ); here I think this PR and its explicit inference is the only way to solve the use-case where you want to infer a type and use it in a union/intersection: i.e. |
@masaeedu Thanks for your comment, but there's a problem I can see, which is not being able to check the validity of the structure of the return. In the example, I want to make sure |
This allows you to write
infer T
anywhere in the types inside a call or new expression argument list and have that position inferred. Additionally, the sameT
may be referenced elsewhere in the arguments and be reused as may be expected (with only theinfer
'd declaration as sole inference site), since type parameters are able to reference one another, so, too, would be the expectation of arguments:Enables #10571
If this is too complex a feature (though it's really neat, since you can infer one argument (or just part of one!) while also locking another argument into a corresponding shape), it's possible to either scale back to, or in addition to this, allow omitted type parameters to implicitly be an
infer
'd type that works similarly to still enable #10571. In any case, I'd like to discuss this direction.Incidentally, I believe this fixes #21836.