Skip to content

Commit 410d2f7

Browse files
committed
Strip literal freshness when contextually typed by instantiable type that has return mapper constraint inferences
1 parent e37ea53 commit 410d2f7

5 files changed

+201
-1
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34796,7 +34796,9 @@ namespace ts {
3479634796
// We strip literal freshness when an appropriate contextual type is present such that contextually typed
3479734797
// literals always preserve their literal types (otherwise they might widen during type inference). An alternative
3479834798
// here would be to not mark contextually typed literals as fresh in the first place.
34799-
const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ?
34799+
const result = maybeTypeOfKind(type, TypeFlags.Literal) && (
34800+
isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ||
34801+
isLiteralOfContextualType(type, contextualType)) ?
3480034802
getRegularTypeOfLiteralType(type) : type;
3480134803
return result;
3480234804
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [stringLiteralFreshnessInGenericTypeGuard.ts]
2+
interface Guard<T> {
3+
(val: unknown): val is T;
4+
}
5+
6+
type ObjectGuard<T> = {
7+
[key in keyof T]: Guard<T[key]>;
8+
};
9+
10+
function isObject(val: unknown): val is Record<string, unknown> {
11+
return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
12+
}
13+
14+
declare function createObjectGuard<T>(guard: ObjectGuard<T>): (val: unknown) => val is T;
15+
16+
declare function asLiteral<T extends (string | boolean | number)[]>(...literals: T): (val: unknown) => val is T[number]
17+
18+
// See type of `isWorking` - should include the type key as a union of strings
19+
const isWorking = createObjectGuard({
20+
// ^?
21+
type: asLiteral('these', 'should', 'be', 'a', 'union'),
22+
});
23+
24+
25+
26+
//// [stringLiteralFreshnessInGenericTypeGuard.js]
27+
function isObject(val) {
28+
return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
29+
}
30+
// See type of `isWorking` - should include the type key as a union of strings
31+
var isWorking = createObjectGuard({
32+
// ^?
33+
type: asLiteral('these', 'should', 'be', 'a', 'union')
34+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/compiler/stringLiteralFreshnessInGenericTypeGuard.ts ===
2+
interface Guard<T> {
3+
>Guard : Symbol(Guard, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 0, 0))
4+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 0, 16))
5+
6+
(val: unknown): val is T;
7+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 1, 3))
8+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 1, 3))
9+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 0, 16))
10+
}
11+
12+
type ObjectGuard<T> = {
13+
>ObjectGuard : Symbol(ObjectGuard, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 2, 1))
14+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 4, 17))
15+
16+
[key in keyof T]: Guard<T[key]>;
17+
>key : Symbol(key, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 5, 3))
18+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 4, 17))
19+
>Guard : Symbol(Guard, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 0, 0))
20+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 4, 17))
21+
>key : Symbol(key, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 5, 3))
22+
23+
};
24+
25+
function isObject(val: unknown): val is Record<string, unknown> {
26+
>isObject : Symbol(isObject, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 6, 2))
27+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 8, 18))
28+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 8, 18))
29+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
30+
31+
return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
32+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 8, 18))
33+
>undefined : Symbol(undefined)
34+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 8, 18))
35+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 8, 18))
36+
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
37+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
38+
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
39+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 8, 18))
40+
}
41+
42+
declare function createObjectGuard<T>(guard: ObjectGuard<T>): (val: unknown) => val is T;
43+
>createObjectGuard : Symbol(createObjectGuard, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 10, 1))
44+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 35))
45+
>guard : Symbol(guard, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 38))
46+
>ObjectGuard : Symbol(ObjectGuard, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 2, 1))
47+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 35))
48+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 63))
49+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 63))
50+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 35))
51+
52+
declare function asLiteral<T extends (string | boolean | number)[]>(...literals: T): (val: unknown) => val is T[number]
53+
>asLiteral : Symbol(asLiteral, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 89))
54+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 14, 27))
55+
>literals : Symbol(literals, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 14, 68))
56+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 14, 27))
57+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 14, 86))
58+
>val : Symbol(val, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 14, 86))
59+
>T : Symbol(T, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 14, 27))
60+
61+
// See type of `isWorking` - should include the type key as a union of strings
62+
const isWorking = createObjectGuard({
63+
>isWorking : Symbol(isWorking, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 17, 5))
64+
>createObjectGuard : Symbol(createObjectGuard, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 10, 1))
65+
66+
// ^?
67+
type: asLiteral('these', 'should', 'be', 'a', 'union'),
68+
>type : Symbol(type, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 17, 37))
69+
>asLiteral : Symbol(asLiteral, Decl(stringLiteralFreshnessInGenericTypeGuard.ts, 12, 89))
70+
71+
});
72+
73+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
=== tests/cases/compiler/stringLiteralFreshnessInGenericTypeGuard.ts ===
2+
interface Guard<T> {
3+
(val: unknown): val is T;
4+
>val : unknown
5+
}
6+
7+
type ObjectGuard<T> = {
8+
>ObjectGuard : ObjectGuard<T>
9+
10+
[key in keyof T]: Guard<T[key]>;
11+
};
12+
13+
function isObject(val: unknown): val is Record<string, unknown> {
14+
>isObject : (val: unknown) => val is Record<string, unknown>
15+
>val : unknown
16+
17+
return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
18+
>val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val) : boolean
19+
>val !== undefined && val !== null && typeof val === 'object' : boolean
20+
>val !== undefined && val !== null : boolean
21+
>val !== undefined : boolean
22+
>val : unknown
23+
>undefined : undefined
24+
>val !== null : boolean
25+
>val : unknown
26+
>null : null
27+
>typeof val === 'object' : boolean
28+
>typeof val : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
29+
>val : unknown
30+
>'object' : "object"
31+
>!Array.isArray(val) : boolean
32+
>Array.isArray(val) : boolean
33+
>Array.isArray : (arg: any) => arg is any[]
34+
>Array : ArrayConstructor
35+
>isArray : (arg: any) => arg is any[]
36+
>val : object
37+
}
38+
39+
declare function createObjectGuard<T>(guard: ObjectGuard<T>): (val: unknown) => val is T;
40+
>createObjectGuard : <T>(guard: ObjectGuard<T>) => (val: unknown) => val is T
41+
>guard : ObjectGuard<T>
42+
>val : unknown
43+
44+
declare function asLiteral<T extends (string | boolean | number)[]>(...literals: T): (val: unknown) => val is T[number]
45+
>asLiteral : <T extends (string | number | boolean)[]>(...literals: T) => (val: unknown) => val is T[number]
46+
>literals : T
47+
>val : unknown
48+
49+
// See type of `isWorking` - should include the type key as a union of strings
50+
const isWorking = createObjectGuard({
51+
>isWorking : (val: unknown) => val is { type: "these" | "should" | "be" | "a" | "union"; }
52+
>createObjectGuard({// ^? type: asLiteral('these', 'should', 'be', 'a', 'union'),}) : (val: unknown) => val is { type: "these" | "should" | "be" | "a" | "union"; }
53+
>createObjectGuard : <T>(guard: ObjectGuard<T>) => (val: unknown) => val is T
54+
>{// ^? type: asLiteral('these', 'should', 'be', 'a', 'union'),} : { type: (val: unknown) => val is "these" | "should" | "be" | "a" | "union"; }
55+
56+
// ^?
57+
type: asLiteral('these', 'should', 'be', 'a', 'union'),
58+
>type : (val: unknown) => val is "these" | "should" | "be" | "a" | "union"
59+
>asLiteral('these', 'should', 'be', 'a', 'union') : (val: unknown) => val is "these" | "should" | "be" | "a" | "union"
60+
>asLiteral : <T extends (string | number | boolean)[]>(...literals: T) => (val: unknown) => val is T[number]
61+
>'these' : "these"
62+
>'should' : "should"
63+
>'be' : "be"
64+
>'a' : "a"
65+
>'union' : "union"
66+
67+
});
68+
69+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
interface Guard<T> {
2+
(val: unknown): val is T;
3+
}
4+
5+
type ObjectGuard<T> = {
6+
[key in keyof T]: Guard<T[key]>;
7+
};
8+
9+
function isObject(val: unknown): val is Record<string, unknown> {
10+
return val !== undefined && val !== null && typeof val === 'object' && !Array.isArray(val);
11+
}
12+
13+
declare function createObjectGuard<T>(guard: ObjectGuard<T>): (val: unknown) => val is T;
14+
15+
declare function asLiteral<T extends (string | boolean | number)[]>(...literals: T): (val: unknown) => val is T[number]
16+
17+
// See type of `isWorking` - should include the type key as a union of strings
18+
const isWorking = createObjectGuard({
19+
// ^?
20+
type: asLiteral('these', 'should', 'be', 'a', 'union'),
21+
});
22+

0 commit comments

Comments
 (0)