-
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
Instantiation expressions #47607
Instantiation expressions #47607
Conversation
@ahejlsberg This is super close to a solution of #37181. Will it will allow us to do: function makeBox<T>(value: T) {
return { value };
};
// Can we do it or something similar? Currently doesn't compile :(
type MakeBox<T> = ReturnType<typeof makeBox<T>>
// As it now allows us to do (no generics though)
const stringMakeBox = makeBox<string>;
type MakeBox = ReturnType<typeof stringMakeBox>
// And even more relevant to support generics if we can now do:
type MakeBox = ReturnType<typeof makeBox<string>> Up to now |
Building the TypeScript playground for this PR would be better. |
Yes, you can indeed use that pattern to capture a generic return type of a generic function. This is something that previously wasn't possible. I'll update the PR description to include an example. |
Let's say we have a function f have a call signature What will this PR behave? They have different argument counts and even different different type constraints |
This works just fine: declare const f: {
<T extends number, Q>(): [T, Q];
new <T extends string>(): [T];
};
type T0 = typeof f<string>; // new () = [string]
type T1 = typeof f<number, string>; // () => [number, string] But an error occurs here: declare const g: {
<T extends number>(): [T];
new <T extends string>(): [T];
};
type T2 = typeof g<string>; // Error, type 'string' does not satisfy the constraint 'number' |
@typescript-bot test this |
Heya @ahejlsberg, I'm starting to run the inline community code test suite on this PR at 8e466ba. Hold tight - I'll update this comment with the log link once the build has been queued. |
It doesn't works on build-ins method 🤔 const cache = new WeakMap<
A,
ReturnType<typeof Object["getOwnPropertyDescriptors"]<A>> // error
>();
const a = { b: 1 }
type A = typeof a
const descMap = Object.getOwnPropertyDescriptors<A>(a)
|
You cant use brackets, just qualified names: |
How to use this feature with overloaded functions ? declare function foo<T extends keyof HTMLElementTagNameMap>(arg: T): HTMLElementTagNameMap[T];
declare function foo<T extends HTMLElement>(arg: T): T;
type FirstFoo = typeof foo<"div">; // error
const f: ReturnType<FirstFoo> = foo("div"); I couldn't get the code to work. The error message is The code works if I change the second |
Higher-order instantiation expressions don't resolve properly, as they weren't accounted for in the test baselines (see also issue #52035). declare function transform<
const A extends readonly any[],
F extends <
V extends A[number],
K extends keyof A,
>(value: V, index: K, array: A) => any
>(array: [...A], func: F): { [K in keyof A]: ReturnType<typeof func<A[K], K>> };
const $2 = transform(["object", "string", "number"], k => ({ type: k }));
// ^?
// Should be [{ type: "object" }, { type: "string" }, { type: "number" }], but is [any, any, any] |
Is there any issue tracking a design for a way we could do that? I've been trying to do something that I'd be able to accomplish if I could have a type like // type ApplyFn<Fn, Arg> = (some magic...)
type Applied = ApplyFn<<T>(value: T) => { value: T }, 1 | 2 | 3> // { value: 1 | 2 | 3 } Someone correct me if I'm wrong but it seems there's no way to do this for an arbitrary function. |
It also solves #20719. |
I don't believe there is. Instantiation expressions only work for values; you can't use a similar method in types. type Fn = <Arg>() => unknown
type ApplyFn<T, F extends Fn> = T extends unknown ? ReturnType<F<T>> : never
const f = <T>(): [T] => ({} as [T])
type t1 = ApplyFn<1 | 2, typeof f> The following (direct instead of the type parameter) works: type ApplyFn<T> = T extends unknown ? ReturnType<typeof f<T>> : never
type t1 = ApplyFn<1 | 2>
// ^? [1] | [2] |
With this PR we implement Instantiation Expressions which provide the ability to specify type arguments for generic functions or generic constructors without actually calling them. For example:
Above, the instantiation expression
makeBox<string>
changes the type ofmakeBox
from<T>(value: T) => { value: T }
to the more specific type(value: string) => { value: string }
. When emitted to JavaScript, instantiation expressions simply have the type arguments erased.Instantiation expressions are particularly useful for creating specific instantiations of generic class constructors such as the
ErrorMap
above. Previously, this could only be accomplished with a type annotation or a redundant subclass:The argument to the
typeof
type operator can now be an instantiation expression. For example:A particularly useful pattern is to create generic type aliases for applications of
typeof
that reference type parameters in type instantiation expressions:Notice how
Box<T>
captures the inferred return type of a generic function without loosing the generic type. This was previously not possible.The type of an instantiation expression
f<T>
is determined as follows:f
is an object type,f<T>
produces an object type in wherein generic signatures for which<T>
is an applicable type argument list are instantiated with the given type arguments. Non-applicable signatures, including non-generic signatures, are elided, but other object type members (such as properties) are unaffected. An error occurs iff
has signatures, but none for which<T>
is an applicable type argument list.f
is a union or intersection type,f<T>
produces a union or intersection type where the type argument list<T>
has been applied to each constituent.f
is a generic type,f<T>
applies the type argument list<T>
to the constraint of the type off
.f
is of any other type,f<T>
has the same type asf
.Note that an instantiation expression can be applied to any type, but that an error occurs if no call or construct signatures are present in the type. For example, an instantiation expression can be applied to a union that includes
undefined
:Fixes #37181.
Fixes #40542.