diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1b38ca72a46cc..0af75f8848259 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16270,7 +16270,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // This function assumes the constituent type list is sorted and deduplicated. - function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + function getUnionTypeFromSortedList(types: Type[], precomputedObjectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { if (types.length === 0) { return neverType; } @@ -16285,7 +16285,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let type = unionTypes.get(id); if (!type) { type = createType(TypeFlags.Union) as UnionType; - type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + type.objectFlags = precomputedObjectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); type.types = types; type.origin = origin; type.aliasSymbol = aliasSymbol; @@ -25649,7 +25649,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered); } } - return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); + // filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly" + // it is purely an optimization hint so there is no harm in accidentally forwarding it + return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections), /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); } return type.flags & TypeFlags.Never || f(type) ? type : neverType; } diff --git a/tests/baselines/reference/unknownLikeUnionObjectFlagsNotPropagated.symbols b/tests/baselines/reference/unknownLikeUnionObjectFlagsNotPropagated.symbols new file mode 100644 index 0000000000000..56a4574560633 --- /dev/null +++ b/tests/baselines/reference/unknownLikeUnionObjectFlagsNotPropagated.symbols @@ -0,0 +1,41 @@ +=== tests/cases/compiler/unknownLikeUnionObjectFlagsNotPropagated.ts === +// repro from #52475#issuecomment-1411215277 + +type MyType = {} | null | undefined; +>MyType : Symbol(MyType, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 0, 0)) + +const myVar: MyType = null as MyType; +>myVar : Symbol(myVar, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 4, 5)) +>MyType : Symbol(MyType, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 0, 0)) +>MyType : Symbol(MyType, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 0, 0)) + +myVar?.toLocaleString; +>myVar?.toLocaleString : Symbol(Object.toLocaleString, Decl(lib.es5.d.ts, --, --)) +>myVar : Symbol(myVar, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 4, 5)) +>toLocaleString : Symbol(Object.toLocaleString, Decl(lib.es5.d.ts, --, --)) + +myVar; +>myVar : Symbol(myVar, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 4, 5)) + +async function myUnusedFunction() { +>myUnusedFunction : Symbol(myUnusedFunction, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 7, 6)) + + const fetch1 = Promise.resolve(['hello', 'world']); +>fetch1 : Symbol(fetch1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 10, 9)) +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>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, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) + + const [data1] = await Promise.all([fetch1]); +>data1 : Symbol(data1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 11, 11)) +>Promise.all : Symbol(PromiseConstructor.all, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>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, --, --)) +>all : Symbol(PromiseConstructor.all, Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>fetch1 : Symbol(fetch1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 10, 9)) + + data1.length; +>data1.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>data1 : Symbol(data1, Decl(unknownLikeUnionObjectFlagsNotPropagated.ts, 11, 11)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/unknownLikeUnionObjectFlagsNotPropagated.types b/tests/baselines/reference/unknownLikeUnionObjectFlagsNotPropagated.types new file mode 100644 index 0000000000000..cd30efee5cd59 --- /dev/null +++ b/tests/baselines/reference/unknownLikeUnionObjectFlagsNotPropagated.types @@ -0,0 +1,49 @@ +=== tests/cases/compiler/unknownLikeUnionObjectFlagsNotPropagated.ts === +// repro from #52475#issuecomment-1411215277 + +type MyType = {} | null | undefined; +>MyType : {} | null | undefined +>null : null + +const myVar: MyType = null as MyType; +>myVar : MyType +>null as MyType : MyType +>null : null + +myVar?.toLocaleString; +>myVar?.toLocaleString : (() => string) | undefined +>myVar : MyType +>toLocaleString : (() => string) | undefined + +myVar; +>myVar : MyType + +async function myUnusedFunction() { +>myUnusedFunction : () => Promise + + const fetch1 = Promise.resolve(['hello', 'world']); +>fetch1 : Promise +>Promise.resolve(['hello', 'world']) : Promise +>Promise.resolve : { (): Promise; (value: T): Promise>; (value: T | PromiseLike): Promise>; } +>Promise : PromiseConstructor +>resolve : { (): Promise; (value: T): Promise>; (value: T | PromiseLike): Promise>; } +>['hello', 'world'] : string[] +>'hello' : "hello" +>'world' : "world" + + const [data1] = await Promise.all([fetch1]); +>data1 : string[] +>await Promise.all([fetch1]) : [string[]] +>Promise.all([fetch1]) : Promise<[string[]]> +>Promise.all : { (values: Iterable>): Promise[]>; (values: T): Promise<{ -readonly [P in keyof T]: Awaited; }>; } +>Promise : PromiseConstructor +>all : { (values: Iterable>): Promise[]>; (values: T): Promise<{ -readonly [P in keyof T]: Awaited; }>; } +>[fetch1] : [Promise] +>fetch1 : Promise + + data1.length; +>data1.length : number +>data1 : string[] +>length : number +} + diff --git a/tests/cases/compiler/unknownLikeUnionObjectFlagsNotPropagated.ts b/tests/cases/compiler/unknownLikeUnionObjectFlagsNotPropagated.ts new file mode 100644 index 0000000000000..c2c61a89ee22d --- /dev/null +++ b/tests/cases/compiler/unknownLikeUnionObjectFlagsNotPropagated.ts @@ -0,0 +1,18 @@ +// @strict: true +// @noEmit: true +// @lib: esnext + +// repro from #52475#issuecomment-1411215277 + +type MyType = {} | null | undefined; + +const myVar: MyType = null as MyType; + +myVar?.toLocaleString; +myVar; + +async function myUnusedFunction() { + const fetch1 = Promise.resolve(['hello', 'world']); + const [data1] = await Promise.all([fetch1]); + data1.length; +}