Skip to content

Commit 424fcdd

Browse files
authored
Merge pull request microsoft#28005 from Microsoft/noReductionUnion
Do not do any reduction (even if it contains any) to the union type when getting contextual type
2 parents ad55994 + 71d8961 commit 424fcdd

File tree

5 files changed

+90
-29
lines changed

5 files changed

+90
-29
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8855,39 +8855,46 @@ namespace ts {
88558855
return false;
88568856
}
88578857

8858-
function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) {
8858+
function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type, noReduction: boolean) {
88598859
const flags = type.flags;
88608860
if (flags & TypeFlags.Union) {
8861-
return addTypesToUnion(typeSet, includes, (<UnionType>type).types);
8861+
return addTypesToUnion(typeSet, includes, (<UnionType>type).types, noReduction);
88628862
}
88638863
// We ignore 'never' types in unions. Likewise, we ignore intersections of unit types as they are
88648864
// another form of 'never' (in that they have an empty value domain). We could in theory turn
88658865
// intersections of unit types into 'never' upon construction, but deferring the reduction makes it
88668866
// easier to reason about their origin.
88678867
if (!(flags & TypeFlags.Never || flags & TypeFlags.Intersection && isEmptyIntersectionType(<IntersectionType>type))) {
88688868
includes |= flags & ~TypeFlags.ConstructionFlags;
8869-
if (flags & TypeFlags.AnyOrUnknown) {
8869+
if (noReduction) {
8870+
addTypeToTypeSet(typeSet, type);
8871+
}
8872+
else if (flags & TypeFlags.AnyOrUnknown) {
88708873
if (type === wildcardType) includes |= TypeFlags.Wildcard;
88718874
}
88728875
else if (!strictNullChecks && flags & TypeFlags.Nullable) {
88738876
if (!(flags & TypeFlags.ContainsWideningType)) includes |= TypeFlags.NonWideningType;
88748877
}
88758878
else {
8876-
const len = typeSet.length;
8877-
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
8878-
if (index < 0) {
8879-
typeSet.splice(~index, 0, type);
8880-
}
8879+
addTypeToTypeSet(typeSet, type);
88818880
}
88828881
}
88838882
return includes;
88848883
}
88858884

8885+
function addTypeToTypeSet(typeSet: Type[], type: Type) {
8886+
const len = typeSet.length;
8887+
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
8888+
if (index < 0) {
8889+
typeSet.splice(~index, 0, type);
8890+
}
8891+
}
8892+
88868893
// Add the given types to the given type set. Order is preserved, duplicates are removed,
88878894
// and nested types of the given kind are flattened into the set.
8888-
function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: ReadonlyArray<Type>): TypeFlags {
8895+
function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: ReadonlyArray<Type>, noReduction: boolean): TypeFlags {
88898896
for (const type of types) {
8890-
includes = addTypeToUnion(typeSet, includes, type);
8897+
includes = addTypeToUnion(typeSet, includes, type, noReduction);
88918898
}
88928899
return includes;
88938900
}
@@ -8964,24 +8971,26 @@ namespace ts {
89648971
return types[0];
89658972
}
89668973
const typeSet: Type[] = [];
8967-
const includes = addTypesToUnion(typeSet, 0, types);
8968-
if (includes & TypeFlags.AnyOrUnknown) {
8969-
return includes & TypeFlags.Any ? includes & TypeFlags.Wildcard ? wildcardType : anyType : unknownType;
8970-
}
8971-
switch (unionReduction) {
8972-
case UnionReduction.Literal:
8973-
if (includes & TypeFlags.StringOrNumberLiteralOrUnique | TypeFlags.BooleanLiteral) {
8974-
removeRedundantLiteralTypes(typeSet, includes);
8975-
}
8976-
break;
8977-
case UnionReduction.Subtype:
8978-
removeSubtypes(typeSet);
8979-
break;
8980-
}
8981-
if (typeSet.length === 0) {
8982-
return includes & TypeFlags.Null ? includes & TypeFlags.NonWideningType ? nullType : nullWideningType :
8983-
includes & TypeFlags.Undefined ? includes & TypeFlags.NonWideningType ? undefinedType : undefinedWideningType :
8984-
neverType;
8974+
const includes = addTypesToUnion(typeSet, 0, types, unionReduction === UnionReduction.None);
8975+
if (unionReduction !== UnionReduction.None) {
8976+
if (includes & TypeFlags.AnyOrUnknown) {
8977+
return includes & TypeFlags.Any ? includes & TypeFlags.Wildcard ? wildcardType : anyType : unknownType;
8978+
}
8979+
switch (unionReduction) {
8980+
case UnionReduction.Literal:
8981+
if (includes & TypeFlags.StringOrNumberLiteralOrUnique | TypeFlags.BooleanLiteral) {
8982+
removeRedundantLiteralTypes(typeSet, includes);
8983+
}
8984+
break;
8985+
case UnionReduction.Subtype:
8986+
removeSubtypes(typeSet);
8987+
break;
8988+
}
8989+
if (typeSet.length === 0) {
8990+
return includes & TypeFlags.Null ? includes & TypeFlags.NonWideningType ? nullType : nullWideningType :
8991+
includes & TypeFlags.Undefined ? includes & TypeFlags.NonWideningType ? undefinedType : undefinedWideningType :
8992+
neverType;
8993+
}
89858994
}
89868995
return getUnionTypeFromSortedList(typeSet, includes & TypeFlags.NotPrimitiveUnion ? 0 : TypeFlags.UnionOfPrimitiveTypes, aliasSymbol, aliasTypeArguments);
89878996
}
@@ -16868,7 +16877,6 @@ namespace ts {
1686816877
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
1686916878
return arrayContextualType && (
1687016879
getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String)
16871-
|| getIndexTypeOfContextualType(arrayContextualType, IndexKind.Number)
1687216880
|| getIteratedTypeOrElementType(arrayContextualType, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false));
1687316881
}
1687416882

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [unionTypeWithIndexAndTuple.ts]
2+
interface I {
3+
[index: number]: any;
4+
someOtherProperty: number;
5+
}
6+
function f(args: ["a"] | I) { }
7+
f(["a"]);
8+
9+
//// [unionTypeWithIndexAndTuple.js]
10+
function f(args) { }
11+
f(["a"]);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/compiler/unionTypeWithIndexAndTuple.ts ===
2+
interface I {
3+
>I : Symbol(I, Decl(unionTypeWithIndexAndTuple.ts, 0, 0))
4+
5+
[index: number]: any;
6+
>index : Symbol(index, Decl(unionTypeWithIndexAndTuple.ts, 1, 5))
7+
8+
someOtherProperty: number;
9+
>someOtherProperty : Symbol(I.someOtherProperty, Decl(unionTypeWithIndexAndTuple.ts, 1, 25))
10+
}
11+
function f(args: ["a"] | I) { }
12+
>f : Symbol(f, Decl(unionTypeWithIndexAndTuple.ts, 3, 1))
13+
>args : Symbol(args, Decl(unionTypeWithIndexAndTuple.ts, 4, 11))
14+
>I : Symbol(I, Decl(unionTypeWithIndexAndTuple.ts, 0, 0))
15+
16+
f(["a"]);
17+
>f : Symbol(f, Decl(unionTypeWithIndexAndTuple.ts, 3, 1))
18+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/compiler/unionTypeWithIndexAndTuple.ts ===
2+
interface I {
3+
[index: number]: any;
4+
>index : number
5+
6+
someOtherProperty: number;
7+
>someOtherProperty : number
8+
}
9+
function f(args: ["a"] | I) { }
10+
>f : (args: I | ["a"]) => void
11+
>args : I | ["a"]
12+
13+
f(["a"]);
14+
>f(["a"]) : void
15+
>f : (args: I | ["a"]) => void
16+
>["a"] : ["a"]
17+
>"a" : "a"
18+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
interface I {
2+
[index: number]: any;
3+
someOtherProperty: number;
4+
}
5+
function f(args: ["a"] | I) { }
6+
f(["a"]);

0 commit comments

Comments
 (0)