Skip to content
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

Improve contextual typing when the returnMapper is involved #56953

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30850,9 +30850,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// the 'boolean' type from the contextual type such that contextually typed boolean
// literals actually end up widening to 'boolean' (see #48363).
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
// nothing is gained from contextual 'never' type so the uninstantiated type is kept
if (type.flags & TypeFlags.Never) {
return contextualType;
}
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
type;
// intersecting helps to avoid widening when contextual type is a type parameter constrained to a primitive
// and when the instantiated by the returnMapper type is that primitive type
getIntersectionType([contextualType, type]);
}
}
return contextualType;
Expand Down
105 changes: 105 additions & 0 deletions tests/baselines/reference/contextualTypeInNestedInference1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//// [tests/cases/compiler/contextualTypeInNestedInference1.ts] ////

=== contextualTypeInNestedInference1.ts ===
// https://github.com/microsoft/TypeScript/issues/56912

interface NameBag<Names extends Record<string, any> = {}> {
>NameBag : Symbol(NameBag, Decl(contextualTypeInNestedInference1.ts, 0, 0))
>Names : Symbol(Names, Decl(contextualTypeInNestedInference1.ts, 2, 18))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

addName<Name extends string>(options: {
>addName : Symbol(NameBag.addName, Decl(contextualTypeInNestedInference1.ts, 2, 59))
>Name : Symbol(Name, Decl(contextualTypeInNestedInference1.ts, 3, 10))
>options : Symbol(options, Decl(contextualTypeInNestedInference1.ts, 3, 31))

name: Name;
>name : Symbol(name, Decl(contextualTypeInNestedInference1.ts, 3, 41))
>Name : Symbol(Name, Decl(contextualTypeInNestedInference1.ts, 3, 10))

}): NameBag<
>NameBag : Symbol(NameBag, Decl(contextualTypeInNestedInference1.ts, 0, 0))

Names & {
>Names : Symbol(Names, Decl(contextualTypeInNestedInference1.ts, 2, 18))

[key in Name]: { name: true };
>key : Symbol(key, Decl(contextualTypeInNestedInference1.ts, 7, 7))
>Name : Symbol(Name, Decl(contextualTypeInNestedInference1.ts, 3, 10))
>name : Symbol(name, Decl(contextualTypeInNestedInference1.ts, 7, 22))
}
>;
}

const emptyBag: NameBag = null as any;
>emptyBag : Symbol(emptyBag, Decl(contextualTypeInNestedInference1.ts, 12, 5))
>NameBag : Symbol(NameBag, Decl(contextualTypeInNestedInference1.ts, 0, 0))

const standalone = emptyBag.addName({ name: "hey!" });
>standalone : Symbol(standalone, Decl(contextualTypeInNestedInference1.ts, 14, 5))
>emptyBag.addName : Symbol(NameBag.addName, Decl(contextualTypeInNestedInference1.ts, 2, 59))
>emptyBag : Symbol(emptyBag, Decl(contextualTypeInNestedInference1.ts, 12, 5))
>addName : Symbol(NameBag.addName, Decl(contextualTypeInNestedInference1.ts, 2, 59))
>name : Symbol(name, Decl(contextualTypeInNestedInference1.ts, 14, 37))

function wrapper1<Schema extends Record<string, NameBag>>(
>wrapper1 : Symbol(wrapper1, Decl(contextualTypeInNestedInference1.ts, 14, 54))
>Schema : Symbol(Schema, Decl(contextualTypeInNestedInference1.ts, 16, 18))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>NameBag : Symbol(NameBag, Decl(contextualTypeInNestedInference1.ts, 0, 0))

schema: Schema,
>schema : Symbol(schema, Decl(contextualTypeInNestedInference1.ts, 16, 58))
>Schema : Symbol(Schema, Decl(contextualTypeInNestedInference1.ts, 16, 18))

): Schema {
>Schema : Symbol(Schema, Decl(contextualTypeInNestedInference1.ts, 16, 18))

return schema;
>schema : Symbol(schema, Decl(contextualTypeInNestedInference1.ts, 16, 58))
}

const bagOfBags1 = wrapper1({
>bagOfBags1 : Symbol(bagOfBags1, Decl(contextualTypeInNestedInference1.ts, 22, 5))
>wrapper1 : Symbol(wrapper1, Decl(contextualTypeInNestedInference1.ts, 14, 54))

prop: emptyBag.addName({ name: "hey!" }),
>prop : Symbol(prop, Decl(contextualTypeInNestedInference1.ts, 22, 29))
>emptyBag.addName : Symbol(NameBag.addName, Decl(contextualTypeInNestedInference1.ts, 2, 59))
>emptyBag : Symbol(emptyBag, Decl(contextualTypeInNestedInference1.ts, 12, 5))
>addName : Symbol(NameBag.addName, Decl(contextualTypeInNestedInference1.ts, 2, 59))
>name : Symbol(name, Decl(contextualTypeInNestedInference1.ts, 23, 26))

});

function wrapper2<Schema extends Record<string, NameBag<Record<string, any>>>>(
>wrapper2 : Symbol(wrapper2, Decl(contextualTypeInNestedInference1.ts, 24, 3))
>Schema : Symbol(Schema, Decl(contextualTypeInNestedInference1.ts, 26, 18))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>NameBag : Symbol(NameBag, Decl(contextualTypeInNestedInference1.ts, 0, 0))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

schema: Schema,
>schema : Symbol(schema, Decl(contextualTypeInNestedInference1.ts, 26, 79))
>Schema : Symbol(Schema, Decl(contextualTypeInNestedInference1.ts, 26, 18))

): Schema {
>Schema : Symbol(Schema, Decl(contextualTypeInNestedInference1.ts, 26, 18))

return schema;
>schema : Symbol(schema, Decl(contextualTypeInNestedInference1.ts, 26, 79))
}

const bagOfBags2 = wrapper2({
>bagOfBags2 : Symbol(bagOfBags2, Decl(contextualTypeInNestedInference1.ts, 32, 5))
>wrapper2 : Symbol(wrapper2, Decl(contextualTypeInNestedInference1.ts, 24, 3))

prop: emptyBag.addName({ name: "hey!" }),
>prop : Symbol(prop, Decl(contextualTypeInNestedInference1.ts, 32, 29))
>emptyBag.addName : Symbol(NameBag.addName, Decl(contextualTypeInNestedInference1.ts, 2, 59))
>emptyBag : Symbol(emptyBag, Decl(contextualTypeInNestedInference1.ts, 12, 5))
>addName : Symbol(NameBag.addName, Decl(contextualTypeInNestedInference1.ts, 2, 59))
>name : Symbol(name, Decl(contextualTypeInNestedInference1.ts, 33, 26))

});

94 changes: 94 additions & 0 deletions tests/baselines/reference/contextualTypeInNestedInference1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//// [tests/cases/compiler/contextualTypeInNestedInference1.ts] ////

=== contextualTypeInNestedInference1.ts ===
// https://github.com/microsoft/TypeScript/issues/56912

interface NameBag<Names extends Record<string, any> = {}> {
addName<Name extends string>(options: {
>addName : <Name extends string>(options: { name: Name;}) => NameBag<Names & { [key in Name]: { name: true; }; }>
>options : { name: Name; }

name: Name;
>name : Name

}): NameBag<
Names & {
[key in Name]: { name: true };
>name : true
>true : true
}
>;
}

const emptyBag: NameBag = null as any;
>emptyBag : NameBag<{}>
>null as any : any

const standalone = emptyBag.addName({ name: "hey!" });
>standalone : NameBag<{ "hey!": { name: true; }; }>
>emptyBag.addName({ name: "hey!" }) : NameBag<{ "hey!": { name: true; }; }>
>emptyBag.addName : <Name extends string>(options: { name: Name; }) => NameBag<{ [key in Name]: { name: true; }; }>
>emptyBag : NameBag<{}>
>addName : <Name extends string>(options: { name: Name; }) => NameBag<{ [key in Name]: { name: true; }; }>
>{ name: "hey!" } : { name: "hey!"; }
>name : "hey!"
>"hey!" : "hey!"

function wrapper1<Schema extends Record<string, NameBag>>(
>wrapper1 : <Schema extends Record<string, NameBag<{}>>>(schema: Schema) => Schema

schema: Schema,
>schema : Schema

): Schema {
return schema;
>schema : Schema
}

const bagOfBags1 = wrapper1({
>bagOfBags1 : { prop: NameBag<{ "hey!": { name: true; }; }>; }
>wrapper1({ prop: emptyBag.addName({ name: "hey!" }),}) : { prop: NameBag<{ "hey!": { name: true; }; }>; }
>wrapper1 : <Schema extends Record<string, NameBag<{}>>>(schema: Schema) => Schema
>{ prop: emptyBag.addName({ name: "hey!" }),} : { prop: NameBag<{ "hey!": { name: true; }; }>; }

prop: emptyBag.addName({ name: "hey!" }),
>prop : NameBag<{ "hey!": { name: true; }; }>
>emptyBag.addName({ name: "hey!" }) : NameBag<{ "hey!": { name: true; }; }>
>emptyBag.addName : <Name extends string>(options: { name: Name; }) => NameBag<{ [key in Name]: { name: true; }; }>
>emptyBag : NameBag<{}>
>addName : <Name extends string>(options: { name: Name; }) => NameBag<{ [key in Name]: { name: true; }; }>
>{ name: "hey!" } : { name: "hey!"; }
>name : "hey!"
>"hey!" : "hey!"

});

function wrapper2<Schema extends Record<string, NameBag<Record<string, any>>>>(
>wrapper2 : <Schema extends Record<string, NameBag<Record<string, any>>>>(schema: Schema) => Schema

schema: Schema,
>schema : Schema

): Schema {
return schema;
>schema : Schema
}

const bagOfBags2 = wrapper2({
>bagOfBags2 : { prop: NameBag<{ "hey!": { name: true; }; }>; }
>wrapper2({ prop: emptyBag.addName({ name: "hey!" }),}) : { prop: NameBag<{ "hey!": { name: true; }; }>; }
>wrapper2 : <Schema extends Record<string, NameBag<Record<string, any>>>>(schema: Schema) => Schema
>{ prop: emptyBag.addName({ name: "hey!" }),} : { prop: NameBag<{ "hey!": { name: true; }; }>; }

prop: emptyBag.addName({ name: "hey!" }),
>prop : NameBag<{ "hey!": { name: true; }; }>
>emptyBag.addName({ name: "hey!" }) : NameBag<{ "hey!": { name: true; }; }>
>emptyBag.addName : <Name extends string>(options: { name: Name; }) => NameBag<{ [key in Name]: { name: true; }; }>
>emptyBag : NameBag<{}>
>addName : <Name extends string>(options: { name: Name; }) => NameBag<{ [key in Name]: { name: true; }; }>
>{ name: "hey!" } : { name: "hey!"; }
>name : "hey!"
>"hey!" : "hey!"

});

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//// [tests/cases/compiler/contextualTypeInNestedInference2.ts] ////

//// [contextualTypeInNestedInference2.ts]
// https://github.com/microsoft/TypeScript/issues/50787

type Model = { s: string; b: boolean }
declare let pick: <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
declare let transform1: <T>(obj: T) => T
declare let transform2: <T extends {}>(obj: T) => T

const result1 = transform1(pick(["s"]))
const result2 = transform2(pick(["s"]))

const intermediate = pick(["s"])
const result3 = transform1(intermediate)


//// [contextualTypeInNestedInference2.js]
// https://github.com/microsoft/TypeScript/issues/50787
var result1 = transform1(pick(["s"]));
var result2 = transform2(pick(["s"]));
var intermediate = pick(["s"]);
var result3 = transform1(intermediate);
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [tests/cases/compiler/contextualTypeInNestedInference2.ts] ////

=== contextualTypeInNestedInference2.ts ===
// https://github.com/microsoft/TypeScript/issues/50787

type Model = { s: string; b: boolean }
>Model : Symbol(Model, Decl(contextualTypeInNestedInference2.ts, 0, 0))
>s : Symbol(s, Decl(contextualTypeInNestedInference2.ts, 2, 14))
>b : Symbol(b, Decl(contextualTypeInNestedInference2.ts, 2, 25))

declare let pick: <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
>pick : Symbol(pick, Decl(contextualTypeInNestedInference2.ts, 3, 11))
>Keys : Symbol(Keys, Decl(contextualTypeInNestedInference2.ts, 3, 19))
>Model : Symbol(Model, Decl(contextualTypeInNestedInference2.ts, 0, 0))
>properties : Symbol(properties, Decl(contextualTypeInNestedInference2.ts, 3, 45))
>Keys : Symbol(Keys, Decl(contextualTypeInNestedInference2.ts, 3, 19))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>Model : Symbol(Model, Decl(contextualTypeInNestedInference2.ts, 0, 0))
>Keys : Symbol(Keys, Decl(contextualTypeInNestedInference2.ts, 3, 19))

declare let transform1: <T>(obj: T) => T
>transform1 : Symbol(transform1, Decl(contextualTypeInNestedInference2.ts, 4, 11))
>T : Symbol(T, Decl(contextualTypeInNestedInference2.ts, 4, 25))
>obj : Symbol(obj, Decl(contextualTypeInNestedInference2.ts, 4, 28))
>T : Symbol(T, Decl(contextualTypeInNestedInference2.ts, 4, 25))
>T : Symbol(T, Decl(contextualTypeInNestedInference2.ts, 4, 25))

declare let transform2: <T extends {}>(obj: T) => T
>transform2 : Symbol(transform2, Decl(contextualTypeInNestedInference2.ts, 5, 11))
>T : Symbol(T, Decl(contextualTypeInNestedInference2.ts, 5, 25))
>obj : Symbol(obj, Decl(contextualTypeInNestedInference2.ts, 5, 39))
>T : Symbol(T, Decl(contextualTypeInNestedInference2.ts, 5, 25))
>T : Symbol(T, Decl(contextualTypeInNestedInference2.ts, 5, 25))

const result1 = transform1(pick(["s"]))
>result1 : Symbol(result1, Decl(contextualTypeInNestedInference2.ts, 7, 5))
>transform1 : Symbol(transform1, Decl(contextualTypeInNestedInference2.ts, 4, 11))
>pick : Symbol(pick, Decl(contextualTypeInNestedInference2.ts, 3, 11))

const result2 = transform2(pick(["s"]))
>result2 : Symbol(result2, Decl(contextualTypeInNestedInference2.ts, 8, 5))
>transform2 : Symbol(transform2, Decl(contextualTypeInNestedInference2.ts, 5, 11))
>pick : Symbol(pick, Decl(contextualTypeInNestedInference2.ts, 3, 11))

const intermediate = pick(["s"])
>intermediate : Symbol(intermediate, Decl(contextualTypeInNestedInference2.ts, 10, 5))
>pick : Symbol(pick, Decl(contextualTypeInNestedInference2.ts, 3, 11))

const result3 = transform1(intermediate)
>result3 : Symbol(result3, Decl(contextualTypeInNestedInference2.ts, 11, 5))
>transform1 : Symbol(transform1, Decl(contextualTypeInNestedInference2.ts, 4, 11))
>intermediate : Symbol(intermediate, Decl(contextualTypeInNestedInference2.ts, 10, 5))

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [tests/cases/compiler/contextualTypeInNestedInference2.ts] ////

=== contextualTypeInNestedInference2.ts ===
// https://github.com/microsoft/TypeScript/issues/50787

type Model = { s: string; b: boolean }
>Model : { s: string; b: boolean; }
>s : string
>b : boolean

declare let pick: <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
>pick : <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
>properties : readonly Keys[]

declare let transform1: <T>(obj: T) => T
>transform1 : <T>(obj: T) => T
>obj : T

declare let transform2: <T extends {}>(obj: T) => T
>transform2 : <T extends {}>(obj: T) => T
>obj : T

const result1 = transform1(pick(["s"]))
>result1 : Pick<Model, "s">
>transform1(pick(["s"])) : Pick<Model, "s">
>transform1 : <T>(obj: T) => T
>pick(["s"]) : Pick<Model, "s">
>pick : <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
>["s"] : "s"[]
>"s" : "s"

const result2 = transform2(pick(["s"]))
>result2 : Pick<Model, "s">
>transform2(pick(["s"])) : Pick<Model, "s">
>transform2 : <T extends {}>(obj: T) => T
>pick(["s"]) : Pick<Model, "s">
>pick : <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
>["s"] : "s"[]
>"s" : "s"

const intermediate = pick(["s"])
>intermediate : Pick<Model, "s">
>pick(["s"]) : Pick<Model, "s">
>pick : <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
>["s"] : "s"[]
>"s" : "s"

const result3 = transform1(intermediate)
>result3 : Pick<Model, "s">
>transform1(intermediate) : Pick<Model, "s">
>transform1 : <T>(obj: T) => T
>intermediate : Pick<Model, "s">

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//// [tests/cases/compiler/contextualTypeInNestedInference2.ts] ////

//// [contextualTypeInNestedInference2.ts]
// https://github.com/microsoft/TypeScript/issues/50787

type Model = { s: string; b: boolean }
declare let pick: <Keys extends keyof Model>(properties: readonly Keys[]) => Pick<Model, Keys>
declare let transform1: <T>(obj: T) => T
declare let transform2: <T extends {}>(obj: T) => T

const result1 = transform1(pick(["s"]))
const result2 = transform2(pick(["s"]))

const intermediate = pick(["s"])
const result3 = transform1(intermediate)


//// [contextualTypeInNestedInference2.js]
// https://github.com/microsoft/TypeScript/issues/50787
var result1 = transform1(pick(["s"]));
var result2 = transform2(pick(["s"]));
var intermediate = pick(["s"]);
var result3 = transform1(intermediate);
Loading
Loading