-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
Bug Report
🔎 Search Terms
promise generic return type resolution wrapper await resolve
🕗 Version & Regression Information
- This is a type resolution issue (i guess)
- I tested it on all Playground versions (3.3.3 - 4.5.0-beta and Nightly)
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about inference, generic, promise, return type
⏯ Playground Link
Playground link with relevant code
💻 Code
// library land (relevant and simplified part from external library)
type SelectAndInclude = {
select: any;
include: any;
};
type HasSelect = {
select: any;
};
type HasInclude = {
include: any;
};
type CheckSelect<T, S, U> = T extends SelectAndInclude
? "Please either choose `select` or `include`"
: T extends HasSelect
? U
: T extends HasInclude
? U
: S;
declare function findMany<T extends {select?: string, include?: string}>(args: T): CheckSelect<T, Promise<1>, Promise<2>>;
// user land
function wrapperWorking<T extends {select?: string, include?: string}>(args: T){
return findMany(args);
}
async function wrapperNotWorking<T extends {select?: string, include?: string}>(args: T){
const result = await findMany(args)
return result;
}
async function main() {
const isErrorText = await wrapperWorking({select:"foo",include: "bar"})
const is1 = await wrapperWorking({})
const is2 = await wrapperWorking({select: "foo"})
const is2Too = await wrapperWorking({include: "bar"})
const shouldBeErrorTextButIs1 = await wrapperNotWorking({select:"foo",include: "bar"})
const is1Too = await wrapperNotWorking({})
const shouldBe2ButIs1 = await wrapperNotWorking({select: "foo"})
const shouldBe2TButIs1Too = await wrapperNotWorking({include: "bar"})
}🙁 Actual behavior
I use a library which exports generic functions in shapes like findMany (see code sample). Generally these functions accept one argument and map to some return type depending on the arguments type. This works fine and is no issue at all.
My aim was to add a wrapper arround such a generic function to do some extra work before or after calling it e.g.:
async function wrapFindMany<T extends {select?: string, include?: string}>(args: T){
await doSomethingBefore(args); // (e.g. async validation, sanitizing)
const result = await findMany(args);
await doSomethingAfterwards(result); // (e.g. writing to a DB)
return result;
}When you look at the code sample you can see two wrappers: wrapperWorking and wrapperNotWorking. The only difference between them is that wrapperNotWorking is an async function that awaits the result of findMany first before passing it to the return.
Although these functions are essentially the same the typings behave differently. To be precise the unexpected behaviour is in this line:
const result = await findMany(args)result resolves immediatly to the value 1, but args is generic and it should not resolve to a definite value here. For this reason wrapperNotWorking is always returning Promise<1>, while wrapperWorking returns the correct type. (see main for examples and differences)
🙂 Expected behavior
The type of result should be:
"Please either choose `select` or `include`" | 1 | 2and wrapperNotWorking should return Promise's accordingly.
Otherwise it's not conistent and confusing.