Skip to content

Strip literal freshness when contextually typed by instantiable type that has return mapper constraint inferences #50759

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

Closed
wants to merge 3 commits into from
Closed
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
15 changes: 10 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21533,9 +21533,12 @@ namespace ts {
type;
}

function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) {
function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined, node?: Node | undefined) {
if (!isLiteralOfContextualType(type, contextualType)) {
type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type));
const instantiatedContextualType = node && instantiateContextualType(contextualType, node, /*contextFlags*/ undefined);
if (!instantiatedContextualType || instantiatedContextualType === contextualType || !isLiteralOfContextualType(type, instantiatedContextualType)) {
type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type));
}
}
return getRegularTypeOfLiteralType(type);
}
Expand Down Expand Up @@ -30340,7 +30343,7 @@ namespace ts {
// A return type inference from a binding pattern can be used in instantiating the contextual
// type of an argument later in inference, but cannot stand on its own as the final return type.
// It is incorporated into `context.returnMapper` which is used in `instantiateContextualType`,
// but doesn't need to go into `context.inferences`. This allows a an array binding pattern to
// but doesn't need to go into `context.inferences`. This allows an array binding pattern to
// produce a tuple for `T` in
// declare function f<T>(cb: () => T): T;
// const [e1, e2, e3] = f(() => [1, "hi", true]);
Expand Down Expand Up @@ -34796,7 +34799,9 @@ namespace ts {
// We strip literal freshness when an appropriate contextual type is present such that contextually typed
// literals always preserve their literal types (otherwise they might widen during type inference). An alternative
// here would be to not mark contextually typed literals as fresh in the first place.
const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ?
const result = maybeTypeOfKind(type, TypeFlags.Literal) && (
isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ||
isLiteralOfContextualType(type, contextualType)) ?
getRegularTypeOfLiteralType(type) : type;
return result;
}
Expand Down Expand Up @@ -34923,7 +34928,7 @@ namespace ts {
const type = checkExpression(node, checkMode, forceTuple);
return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) :
isTypeAssertion(node) ? type :
getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node, /*contextFlags*/ undefined) : contextualType, node, /*contextFlags*/ undefined));
getWidenedLiteralLikeTypeForContextualType(type, arguments.length === 2 ? getContextualType(node, /*contextFlags*/ undefined) : contextualType, node);
}

function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//// [stringLiteralFreshnessContextuallyTypedByGeneric1.ts]
interface Guard<T> {
(val: unknown): val is T;
}

type ObjectGuard<T> = {
[key in keyof T]: Guard<T[key]>;
};

function isObject(val: unknown): val is Record<string, unknown> {
return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
}

declare function createObjectGuard<T>(guard: ObjectGuard<T>): (val: unknown) => val is T;

declare function asLiteral<T extends (string | boolean | number)[]>(...literals: T): (val: unknown) => val is T[number]

// See type of `isWorking` - should include the type key as a union of strings
const isWorking = createObjectGuard({
// ^?
type: asLiteral('these', 'should', 'be', 'a', 'union'),
});


//// [stringLiteralFreshnessContextuallyTypedByGeneric1.js]
function isObject(val) {
return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
}
// See type of `isWorking` - should include the type key as a union of strings
var isWorking = createObjectGuard({
// ^?
type: asLiteral('these', 'should', 'be', 'a', 'union')
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
=== tests/cases/compiler/stringLiteralFreshnessContextuallyTypedByGeneric1.ts ===
interface Guard<T> {
>Guard : Symbol(Guard, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 0, 0))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 0, 16))

(val: unknown): val is T;
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 1, 3))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 1, 3))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 0, 16))
}

type ObjectGuard<T> = {
>ObjectGuard : Symbol(ObjectGuard, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 2, 1))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 4, 17))

[key in keyof T]: Guard<T[key]>;
>key : Symbol(key, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 5, 3))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 4, 17))
>Guard : Symbol(Guard, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 0, 0))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 4, 17))
>key : Symbol(key, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 5, 3))

};

function isObject(val: unknown): val is Record<string, unknown> {
>isObject : Symbol(isObject, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 6, 2))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 8, 18))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 8, 18))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 8, 18))
>undefined : Symbol(undefined)
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 8, 18))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 8, 18))
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 8, 18))
}

declare function createObjectGuard<T>(guard: ObjectGuard<T>): (val: unknown) => val is T;
>createObjectGuard : Symbol(createObjectGuard, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 10, 1))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 35))
>guard : Symbol(guard, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 38))
>ObjectGuard : Symbol(ObjectGuard, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 2, 1))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 35))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 63))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 63))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 35))

declare function asLiteral<T extends (string | boolean | number)[]>(...literals: T): (val: unknown) => val is T[number]
>asLiteral : Symbol(asLiteral, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 89))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 14, 27))
>literals : Symbol(literals, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 14, 68))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 14, 27))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 14, 86))
>val : Symbol(val, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 14, 86))
>T : Symbol(T, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 14, 27))

// See type of `isWorking` - should include the type key as a union of strings
const isWorking = createObjectGuard({
>isWorking : Symbol(isWorking, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 17, 5))
>createObjectGuard : Symbol(createObjectGuard, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 10, 1))

// ^?
type: asLiteral('these', 'should', 'be', 'a', 'union'),
>type : Symbol(type, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 17, 37))
>asLiteral : Symbol(asLiteral, Decl(stringLiteralFreshnessContextuallyTypedByGeneric1.ts, 12, 89))

});

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
=== tests/cases/compiler/stringLiteralFreshnessContextuallyTypedByGeneric1.ts ===
interface Guard<T> {
(val: unknown): val is T;
>val : unknown
}

type ObjectGuard<T> = {
>ObjectGuard : ObjectGuard<T>

[key in keyof T]: Guard<T[key]>;
};

function isObject(val: unknown): val is Record<string, unknown> {
>isObject : (val: unknown) => val is Record<string, unknown>
>val : unknown

return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
>val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val) : boolean
>val !== undefined && val !== null && typeof val === 'object' : boolean
>val !== undefined && val !== null : boolean
>val !== undefined : boolean
>val : unknown
>undefined : undefined
>val !== null : boolean
>val : unknown
>null : null
>typeof val === 'object' : boolean
>typeof val : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>val : unknown
>'object' : "object"
>!Array.isArray(val) : boolean
>Array.isArray(val) : boolean
>Array.isArray : (arg: any) => arg is any[]
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
>val : object
}

declare function createObjectGuard<T>(guard: ObjectGuard<T>): (val: unknown) => val is T;
>createObjectGuard : <T>(guard: ObjectGuard<T>) => (val: unknown) => val is T
>guard : ObjectGuard<T>
>val : unknown

declare function asLiteral<T extends (string | boolean | number)[]>(...literals: T): (val: unknown) => val is T[number]
>asLiteral : <T extends (string | number | boolean)[]>(...literals: T) => (val: unknown) => val is T[number]
>literals : T
>val : unknown

// See type of `isWorking` - should include the type key as a union of strings
const isWorking = createObjectGuard({
>isWorking : (val: unknown) => val is { type: "these" | "should" | "be" | "a" | "union"; }
>createObjectGuard({// ^? type: asLiteral('these', 'should', 'be', 'a', 'union'),}) : (val: unknown) => val is { type: "these" | "should" | "be" | "a" | "union"; }
>createObjectGuard : <T>(guard: ObjectGuard<T>) => (val: unknown) => val is T
>{// ^? type: asLiteral('these', 'should', 'be', 'a', 'union'),} : { type: (val: unknown) => val is "these" | "should" | "be" | "a" | "union"; }

// ^?
type: asLiteral('these', 'should', 'be', 'a', 'union'),
>type : (val: unknown) => val is "these" | "should" | "be" | "a" | "union"
>asLiteral('these', 'should', 'be', 'a', 'union') : (val: unknown) => val is "these" | "should" | "be" | "a" | "union"
>asLiteral : <T extends (string | number | boolean)[]>(...literals: T) => (val: unknown) => val is T[number]
>'these' : "these"
>'should' : "should"
>'be' : "be"
>'a' : "a"
>'union' : "union"

});

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//// [stringLiteralFreshnessContextuallyTypedByGeneric2.ts]
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)


//// [stringLiteralFreshnessContextuallyTypedByGeneric2.js]
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,49 @@
=== tests/cases/compiler/stringLiteralFreshnessContextuallyTypedByGeneric2.ts ===
type Model = { s: string; b: boolean }
>Model : Symbol(Model, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 0, 0))
>s : Symbol(s, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 0, 14))
>b : Symbol(b, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 0, 25))

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

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

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

const result1 = transform1(pick(["s"]))
>result1 : Symbol(result1, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 5, 5))
>transform1 : Symbol(transform1, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 2, 11))
>pick : Symbol(pick, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 1, 11))

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

const intermediate = pick(["s"])
>intermediate : Symbol(intermediate, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 8, 5))
>pick : Symbol(pick, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 1, 11))

const result3 = transform1(intermediate)
>result3 : Symbol(result3, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 9, 5))
>transform1 : Symbol(transform1, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 2, 11))
>intermediate : Symbol(intermediate, Decl(stringLiteralFreshnessContextuallyTypedByGeneric2.ts, 8, 5))

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
=== tests/cases/compiler/stringLiteralFreshnessContextuallyTypedByGeneric2.ts ===
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,18 @@
//// [stringLiteralFreshnessContextuallyTypedByGeneric2.ts]
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)


//// [stringLiteralFreshnessContextuallyTypedByGeneric2.js]
var result1 = transform1(pick(["s"]));
var result2 = transform2(pick(["s"]));
var intermediate = pick(["s"]);
var result3 = transform1(intermediate);
Loading