-
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
Higher Order Instantiation Expressions don't resolve types correctly #52035
Comments
Additional repro demonstrating this bug. P.S. It seems that cases for higher-order expressions weren't included in baselines for instantiation expressions. |
It seems type Mapped<Mapper, T> = Mapper<T>; // Type 'Mapper' is not generic. (2315) It's common to make abstraction like this in function mapped(mapper, value) {
return mapper(value);
}
function box(value) {
return { value };
}
console.log(mapped(box, 'foo')); That is, we might want to create higher order function called Another situation is class instantiation with constructors and constructor parameters. Generic function works well with normal constructors, but it cannot infer generic instance type with generic constructors: function create<T extends new (...args: any[]) => any>(ctor: T, ...params: ConstructorParameters<T>): InstanceType<T> {
return new ctor(...params);
}
class Foo {
constructor(public message: string) {}
sayHello() {
console.log(`hello from ${this.message}`);
}
}
// Well typed, because `Foo` is not a generic type.
const foo = create(Foo, "bar");
foo.sayHello();
// Not well typed, because `Set` is a generic type, and its constructor parameter uses its generic parameter `T`.
const numberSet = create(Set, [1, 2, 3]);
// ^^^^^^^^^ Inferred as `Set<unknown>` -- the generic parameter is instantiated with its constraint.
I know it's not the first time to introduce |
The thing you mostly need here is call types, not higher-kinded types per se. The very smallest problem is that Tracking somewhat at #52295 (this would be a mapped type rather than a union type, but the logic is pretty similar) and to a greater extent at #6606, which I think we will revisit sometime to specifically think about (generic) functions' call types. |
I don't think you understood my example; the issue is not strictly mapped or union types. Even though type Func = <T>(value: T) => any;
const satisfiedFunc = (param => {
return [
param
] as const;
}) satisfies Func;
type $0 = typeof satisfiedFunc<0>; // outputs correctly as `readonly [0]` To further illustrate the issue, the expected behavior actually happens when you explicitly type the return type of // ...
F extends <
V extends A[number],
K extends keyof A,
>(value: V, index: K, array: A) => { type: V } | {}
// ...
const items = ["object", "string", "number"] as const;
const $0 = transform(items, k => ({ type: k }));
// ^?
// Correctly becomes [{ type: "object" }, { type: "string" }, { type: "number" }], because the type is explicitly typed in the generic constraint
// It should go to resolve to the type of `F` though |
What you're seeing there is an illusion. Consider tweaking it to this variant: interface Lookup {
object: "obj";
string: "str";
number: "num";
}
declare function remap<T extends (typeof items)[number]>(k: T): Lookup[T];
const m = remap("object");
// ^? 'obj'
const items = ["object", "string", "number"] as const;
const $0 = transform(items, k => ({ type: remap(k) })); The correct type of Since |
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
declare function transform<
const A extends readonly any[],
F extends <V>(value: V) => any
>(array: [...A], func: F): { [K in keyof A]: ReturnType<typeof func<A[K]>> };
const f = <T>(k:T) => ({ type: k })
const $2 = transform(["object", "string", "number"], f);
// ^?
// Should be [{ type: "object" }, { type: "string" }, { type: "number" }] When I change to: declare function transform<
const A extends readonly any[],
F extends <V>(value: V) => {type: V}
>(array: [...A], func: F): { [K in keyof A]: ReturnType<typeof func<A[K]>> };
const f = <T>(k:T) => ({ type: k })
const $2 = transform(["object", "string", "number"], f);
// ^?
// Should be [{ type: "object" }, { type: "string" }, { type: "number" }] it works - but that assumes we actually know the return value - if we're having the entire F be generic - maybe it'll have a different return type! declare function transform<
const A extends readonly any[],
F extends <V>(value: V) => {type: V}
>(array: [...A], func: F): { [K in keyof A]: ReturnType<typeof func<A[K]>> };
const f = <T>(k:T) => ({ type: k })
const f2 = <T>(k:T) => ({ blarg: k })
const $2 = transform(["object", "string", "number"], f);
const $3 = transform(["object", "string", "number"], f2);
// ^?
// Should be [{ type: "object" }, { type: "string" }, { type: "number" }] This simply makes the second call fail...but if we make the generic accept that, then it won't give us a return type |
Bug Report
🔎 Search Terms
🕗 Version & Regression Information
TS 4.7+ (in every version I tried, it seems to be there from start this feature was added).
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
transform()
, the instantiation expression (added by Instantiation expressions #47607)typeof func<A[K], K>
reverts to it's generic constraint type ofF
(<V extends A[number], K extends keyof A>(value: V, index: K, array: A) => any
) when it is invoked. This causes the return type oftransform()
to be a tuple ofany
instead of{ type: A[K] }
.ReturnType<F>
in place of a instantiation expression from above example results in unresolved generics leaking from the function type🙂 Expected behavior
typeof func<A[K], K>
intransform()
should resolve to{ type: A[K] }
.res
should resolve to[{ type: "object" }, { type: "string" }, { type: "number" }]
.The text was updated successfully, but these errors were encountered: