Skip to content
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
26 changes: 18 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6800,7 +6800,7 @@ namespace ts {
getTypeWithFacts(type, TypeFacts.NEUndefined) :
type;
}
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype);
return getSupertypeOrUnion(getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration));
}

function getTypeForDeclarationFromJSDocComment(declaration: Node) {
Expand Down Expand Up @@ -16595,22 +16595,32 @@ namespace ts {
return true;
}

// If the left type is a supertype of the right type, return the left type. Otherwise, if
// the right type is a supertype of the left type, return the right type. Otherwise, return
// the subtype reduced union of the two types. This function ensures that we get a stable
// choice, unlike union subtype reduction which depends on type ID order.
function getSupertypeOrUnion(left: Type, right: Type) {
return isTypeSubtypeOf(right, left) ? left :
isTypeSubtypeOf(left, right) ? right :
getUnionType([left, right], UnionReduction.Subtype);
}

// When the candidate types are all literal types with the same base type, return a union
// of those literal types. Otherwise, return the leftmost type for which no type to the
// right is a supertype.
function getSupertypeOrUnion(types: Type[]): Type {
function getLeftmostSupertypeOrUnion(types: Type[]): Type {
return literalTypesWithSameBaseType(types) ?
getUnionType(types) :
reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!;
}

function getCommonSupertype(types: Type[]): Type {
if (!strictNullChecks) {
return getSupertypeOrUnion(types);
return getLeftmostSupertypeOrUnion(types);
}
const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable));
return primaryTypes.length ?
getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) :
getNullableType(getLeftmostSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) :
getUnionType(types, UnionReduction.Subtype);
}

Expand Down Expand Up @@ -26991,11 +27001,11 @@ namespace ts {
leftType;
case SyntaxKind.BarBarToken:
return getTypeFacts(leftType) & TypeFacts.Falsy ?
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
getSupertypeOrUnion(removeDefinitelyFalsyTypes(leftType), rightType) :
leftType;
case SyntaxKind.QuestionQuestionToken:
return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) :
getSupertypeOrUnion(getNonNullableType(leftType), rightType) :
leftType;
case SyntaxKind.EqualsToken:
const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None;
Expand Down Expand Up @@ -27260,7 +27270,7 @@ namespace ts {
checkTruthinessExpression(node.condition);
const type1 = checkExpression(node.whenTrue, checkMode);
const type2 = checkExpression(node.whenFalse, checkMode);
return getUnionType([type1, type2], UnionReduction.Subtype);
return getSupertypeOrUnion(type1, type2);
}

function checkTemplateExpression(node: TemplateExpression): Type {
Expand Down Expand Up @@ -30663,7 +30673,7 @@ namespace ts {
return stringType;
}

return getUnionType([arrayElementType, stringType], UnionReduction.Subtype);
return getSupertypeOrUnion(arrayElementType, stringType);
}

return arrayElementType;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/aliasUsageInOrExpression.types
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var d2: IHasVisualizationModel = i || moduleA;

var d2: IHasVisualizationModel = moduleA || i;
>d2 : IHasVisualizationModel
>moduleA || i : IHasVisualizationModel
>moduleA || i : typeof moduleA
>moduleA : typeof moduleA
>i : IHasVisualizationModel

Expand Down
25 changes: 25 additions & 0 deletions tests/baselines/reference/unionReductionMutualSubtypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [unionReductionMutualSubtypes.ts]
// Repro from #35414

interface ReturnVal {
something(): void;
}

const k: ReturnVal = { something() { } }

declare const val: ReturnVal;
function run(options: { something?(b?: string): void }) {
const something = options.something ?? val.something;
something('');
}


//// [unionReductionMutualSubtypes.js]
"use strict";
// Repro from #35414
var k = { something: function () { } };
function run(options) {
var _a;
var something = (_a = options.something) !== null && _a !== void 0 ? _a : val.something;
something('');
}
38 changes: 38 additions & 0 deletions tests/baselines/reference/unionReductionMutualSubtypes.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
=== tests/cases/compiler/unionReductionMutualSubtypes.ts ===
// Repro from #35414

interface ReturnVal {
>ReturnVal : Symbol(ReturnVal, Decl(unionReductionMutualSubtypes.ts, 0, 0))

something(): void;
>something : Symbol(ReturnVal.something, Decl(unionReductionMutualSubtypes.ts, 2, 21))
}

const k: ReturnVal = { something() { } }
>k : Symbol(k, Decl(unionReductionMutualSubtypes.ts, 6, 5))
>ReturnVal : Symbol(ReturnVal, Decl(unionReductionMutualSubtypes.ts, 0, 0))
>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 6, 22))

declare const val: ReturnVal;
>val : Symbol(val, Decl(unionReductionMutualSubtypes.ts, 8, 13))
>ReturnVal : Symbol(ReturnVal, Decl(unionReductionMutualSubtypes.ts, 0, 0))

function run(options: { something?(b?: string): void }) {
>run : Symbol(run, Decl(unionReductionMutualSubtypes.ts, 8, 29))
>options : Symbol(options, Decl(unionReductionMutualSubtypes.ts, 9, 13))
>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 9, 23))
>b : Symbol(b, Decl(unionReductionMutualSubtypes.ts, 9, 35))

const something = options.something ?? val.something;
>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 10, 9))
>options.something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 9, 23))
>options : Symbol(options, Decl(unionReductionMutualSubtypes.ts, 9, 13))
>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 9, 23))
>val.something : Symbol(ReturnVal.something, Decl(unionReductionMutualSubtypes.ts, 2, 21))
>val : Symbol(val, Decl(unionReductionMutualSubtypes.ts, 8, 13))
>something : Symbol(ReturnVal.something, Decl(unionReductionMutualSubtypes.ts, 2, 21))

something('');
>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 10, 9))
}

38 changes: 38 additions & 0 deletions tests/baselines/reference/unionReductionMutualSubtypes.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
=== tests/cases/compiler/unionReductionMutualSubtypes.ts ===
// Repro from #35414

interface ReturnVal {
something(): void;
>something : () => void
}

const k: ReturnVal = { something() { } }
>k : ReturnVal
>{ something() { } } : { something(): void; }
>something : () => void

declare const val: ReturnVal;
>val : ReturnVal

function run(options: { something?(b?: string): void }) {
>run : (options: { something?(b?: string | undefined): void; }) => void
>options : { something?(b?: string | undefined): void; }
>something : ((b?: string | undefined) => void) | undefined
>b : string | undefined

const something = options.something ?? val.something;
>something : (b?: string | undefined) => void
>options.something ?? val.something : (b?: string | undefined) => void
>options.something : ((b?: string | undefined) => void) | undefined
>options : { something?(b?: string | undefined): void; }
>something : ((b?: string | undefined) => void) | undefined
>val.something : () => void
>val : ReturnVal
>something : () => void

something('');
>something('') : void
>something : (b?: string | undefined) => void
>'' : ""
}

15 changes: 15 additions & 0 deletions tests/cases/compiler/unionReductionMutualSubtypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @strict: true

// Repro from #35414

interface ReturnVal {
something(): void;
}

const k: ReturnVal = { something() { } }

declare const val: ReturnVal;
function run(options: { something?(b?: string): void }) {
const something = options.something ?? val.something;
something('');
}