Skip to content

Commit c72d929

Browse files
authored
Fixed accidental propagation of caching-related objectFlags (#52546)
1 parent 3880fce commit c72d929

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed

src/compiler/checker.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -16270,7 +16270,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1627016270
}
1627116271

1627216272
// This function assumes the constituent type list is sorted and deduplicated.
16273-
function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type {
16273+
function getUnionTypeFromSortedList(types: Type[], precomputedObjectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type {
1627416274
if (types.length === 0) {
1627516275
return neverType;
1627616276
}
@@ -16285,7 +16285,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1628516285
let type = unionTypes.get(id);
1628616286
if (!type) {
1628716287
type = createType(TypeFlags.Union) as UnionType;
16288-
type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
16288+
type.objectFlags = precomputedObjectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
1628916289
type.types = types;
1629016290
type.origin = origin;
1629116291
type.aliasSymbol = aliasSymbol;
@@ -25649,7 +25649,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2564925649
newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered);
2565025650
}
2565125651
}
25652-
return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin);
25652+
// filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly"
25653+
// it is purely an optimization hint so there is no harm in accidentally forwarding it
25654+
return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections), /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin);
2565325655
}
2565425656
return type.flags & TypeFlags.Never || f(type) ? type : neverType;
2565525657
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/compiler/unknownLikeUnionObjectFlagsNotPropagated.ts ===
2+
// repro from #52475#issuecomment-1411215277
3+
4+
type MyType = {} | null | undefined;
5+
>MyType : Symbol(MyType, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 0, 0))
6+
7+
const myVar: MyType = null as MyType;
8+
>myVar : Symbol(myVar, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 4, 5))
9+
>MyType : Symbol(MyType, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 0, 0))
10+
>MyType : Symbol(MyType, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 0, 0))
11+
12+
myVar?.toLocaleString;
13+
>myVar?.toLocaleString : Symbol(Object.toLocaleString, Decl(lib.es5.d.ts, --, --))
14+
>myVar : Symbol(myVar, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 4, 5))
15+
>toLocaleString : Symbol(Object.toLocaleString, Decl(lib.es5.d.ts, --, --))
16+
17+
myVar;
18+
>myVar : Symbol(myVar, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 4, 5))
19+
20+
async function myUnusedFunction() {
21+
>myUnusedFunction : Symbol(myUnusedFunction, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 7, 6))
22+
23+
const fetch1 = Promise.resolve(['hello', 'world']);
24+
>fetch1 : Symbol(fetch1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 10, 9))
25+
>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
26+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
27+
>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
28+
29+
const [data1] = await Promise.all([fetch1]);
30+
>data1 : Symbol(data1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 11, 11))
31+
>Promise.all : Symbol(PromiseConstructor.all, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
32+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
33+
>all : Symbol(PromiseConstructor.all, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
34+
>fetch1 : Symbol(fetch1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 10, 9))
35+
36+
data1.length;
37+
>data1.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
38+
>data1 : Symbol(data1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 11, 11))
39+
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
40+
}
41+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/compiler/unknownLikeUnionObjectFlagsNotPropagated.ts ===
2+
// repro from #52475#issuecomment-1411215277
3+
4+
type MyType = {} | null | undefined;
5+
>MyType : {} | null | undefined
6+
>null : null
7+
8+
const myVar: MyType = null as MyType;
9+
>myVar : MyType
10+
>null as MyType : MyType
11+
>null : null
12+
13+
myVar?.toLocaleString;
14+
>myVar?.toLocaleString : (() => string) | undefined
15+
>myVar : MyType
16+
>toLocaleString : (() => string) | undefined
17+
18+
myVar;
19+
>myVar : MyType
20+
21+
async function myUnusedFunction() {
22+
>myUnusedFunction : () => Promise<void>
23+
24+
const fetch1 = Promise.resolve(['hello', 'world']);
25+
>fetch1 : Promise<string[]>
26+
>Promise.resolve(['hello', 'world']) : Promise<string[]>
27+
>Promise.resolve : { (): Promise<void>; <T>(value: T): Promise<Awaited<T>>; <T>(value: T | PromiseLike<T>): Promise<Awaited<T>>; }
28+
>Promise : PromiseConstructor
29+
>resolve : { (): Promise<void>; <T>(value: T): Promise<Awaited<T>>; <T>(value: T | PromiseLike<T>): Promise<Awaited<T>>; }
30+
>['hello', 'world'] : string[]
31+
>'hello' : "hello"
32+
>'world' : "world"
33+
34+
const [data1] = await Promise.all([fetch1]);
35+
>data1 : string[]
36+
>await Promise.all([fetch1]) : [string[]]
37+
>Promise.all([fetch1]) : Promise<[string[]]>
38+
>Promise.all : { <T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>; <T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>; }
39+
>Promise : PromiseConstructor
40+
>all : { <T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>; <T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>; }
41+
>[fetch1] : [Promise<string[]>]
42+
>fetch1 : Promise<string[]>
43+
44+
data1.length;
45+
>data1.length : number
46+
>data1 : string[]
47+
>length : number
48+
}
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @strict: true
2+
// @noEmit: true
3+
// @lib: esnext
4+
5+
// repro from #52475#issuecomment-1411215277
6+
7+
type MyType = {} | null | undefined;
8+
9+
const myVar: MyType = null as MyType;
10+
11+
myVar?.toLocaleString;
12+
myVar;
13+
14+
async function myUnusedFunction() {
15+
const fetch1 = Promise.resolve(['hello', 'world']);
16+
const [data1] = await Promise.all([fetch1]);
17+
data1.length;
18+
}

0 commit comments

Comments
 (0)