Skip to content
Merged
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
12 changes: 10 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12896,6 +12896,14 @@ namespace ts {
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
}

function getBestChoiceType(type1: Type, type2: Type): Type {
const firstAssignableToSecond = isTypeAssignableTo(type1, type2);
const secondAssignableToFirst = isTypeAssignableTo(type2, type1);
return secondAssignableToFirst && !firstAssignableToSecond ? type1 :
firstAssignableToSecond && !secondAssignableToFirst ? type2 :
getUnionType([type1, type2], /*subtypeReduction*/ true);
}

function checkBinaryExpression(node: BinaryExpression, contextualMapper?: TypeMapper) {
return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, contextualMapper, node);
}
Expand Down Expand Up @@ -13039,7 +13047,7 @@ namespace ts {
leftType;
case SyntaxKind.BarBarToken:
return getTypeFacts(leftType) & TypeFacts.Falsy ?
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], /*subtypeReduction*/ true) :
getBestChoiceType(removeDefinitelyFalsyTypes(leftType), rightType) :
leftType;
case SyntaxKind.EqualsToken:
checkAssignmentOperator(rightType);
Expand Down Expand Up @@ -13166,7 +13174,7 @@ namespace ts {
checkExpression(node.condition);
const type1 = checkExpression(node.whenTrue, contextualMapper);
const type2 = checkExpression(node.whenFalse, contextualMapper);
return getUnionType([type1, type2], /*subtypeReduction*/ true);
return getBestChoiceType(type1, type2);
}

function typeContainsLiteralFromEnum(type: Type, enumType: EnumType) {
Expand Down
35 changes: 35 additions & 0 deletions tests/baselines/reference/bestChoiceType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//// [bestChoiceType.ts]

// Repro from #10041

(''.match(/ /) || []).map(s => s.toLowerCase());

// Similar cases

function f1() {
let x = ''.match(/ /);
let y = x || [];
let z = y.map(s => s.toLowerCase());
}

function f2() {
let x = ''.match(/ /);
let y = x ? x : [];
let z = y.map(s => s.toLowerCase());
}


//// [bestChoiceType.js]
// Repro from #10041
(''.match(/ /) || []).map(function (s) { return s.toLowerCase(); });
// Similar cases
function f1() {
var x = ''.match(/ /);
var y = x || [];
var z = y.map(function (s) { return s.toLowerCase(); });
}
function f2() {
var x = ''.match(/ /);
var y = x ? x : [];
var z = y.map(function (s) { return s.toLowerCase(); });
}
63 changes: 63 additions & 0 deletions tests/baselines/reference/bestChoiceType.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
=== tests/cases/compiler/bestChoiceType.ts ===

// Repro from #10041

(''.match(/ /) || []).map(s => s.toLowerCase());
>(''.match(/ /) || []).map : Symbol(Array.map, Decl(lib.d.ts, --, --))
>''.match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>map : Symbol(Array.map, Decl(lib.d.ts, --, --))
>s : Symbol(s, Decl(bestChoiceType.ts, 3, 26))
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
>s : Symbol(s, Decl(bestChoiceType.ts, 3, 26))
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))

// Similar cases

function f1() {
>f1 : Symbol(f1, Decl(bestChoiceType.ts, 3, 48))

let x = ''.match(/ /);
>x : Symbol(x, Decl(bestChoiceType.ts, 8, 7))
>''.match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))

let y = x || [];
>y : Symbol(y, Decl(bestChoiceType.ts, 9, 7))
>x : Symbol(x, Decl(bestChoiceType.ts, 8, 7))

let z = y.map(s => s.toLowerCase());
>z : Symbol(z, Decl(bestChoiceType.ts, 10, 7))
>y.map : Symbol(Array.map, Decl(lib.d.ts, --, --))
>y : Symbol(y, Decl(bestChoiceType.ts, 9, 7))
>map : Symbol(Array.map, Decl(lib.d.ts, --, --))
>s : Symbol(s, Decl(bestChoiceType.ts, 10, 18))
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
>s : Symbol(s, Decl(bestChoiceType.ts, 10, 18))
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
}

function f2() {
>f2 : Symbol(f2, Decl(bestChoiceType.ts, 11, 1))

let x = ''.match(/ /);
>x : Symbol(x, Decl(bestChoiceType.ts, 14, 7))
>''.match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))

let y = x ? x : [];
>y : Symbol(y, Decl(bestChoiceType.ts, 15, 7))
>x : Symbol(x, Decl(bestChoiceType.ts, 14, 7))
>x : Symbol(x, Decl(bestChoiceType.ts, 14, 7))

let z = y.map(s => s.toLowerCase());
>z : Symbol(z, Decl(bestChoiceType.ts, 16, 7))
>y.map : Symbol(Array.map, Decl(lib.d.ts, --, --))
>y : Symbol(y, Decl(bestChoiceType.ts, 15, 7))
>map : Symbol(Array.map, Decl(lib.d.ts, --, --))
>s : Symbol(s, Decl(bestChoiceType.ts, 16, 18))
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
>s : Symbol(s, Decl(bestChoiceType.ts, 16, 18))
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
}

88 changes: 88 additions & 0 deletions tests/baselines/reference/bestChoiceType.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
=== tests/cases/compiler/bestChoiceType.ts ===

// Repro from #10041

(''.match(/ /) || []).map(s => s.toLowerCase());
>(''.match(/ /) || []).map(s => s.toLowerCase()) : string[]
>(''.match(/ /) || []).map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>(''.match(/ /) || []) : RegExpMatchArray
>''.match(/ /) || [] : RegExpMatchArray
>''.match(/ /) : RegExpMatchArray | null
>''.match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
>'' : string
>match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
>/ / : RegExp
>[] : never[]
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>s => s.toLowerCase() : (s: string) => string
>s : string
>s.toLowerCase() : string
>s.toLowerCase : () => string
>s : string
>toLowerCase : () => string

// Similar cases

function f1() {
>f1 : () => void

let x = ''.match(/ /);
>x : RegExpMatchArray | null
>''.match(/ /) : RegExpMatchArray | null
>''.match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
>'' : string
>match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
>/ / : RegExp

let y = x || [];
>y : RegExpMatchArray
>x || [] : RegExpMatchArray
>x : RegExpMatchArray | null
>[] : never[]

let z = y.map(s => s.toLowerCase());
>z : string[]
>y.map(s => s.toLowerCase()) : string[]
>y.map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>y : RegExpMatchArray
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>s => s.toLowerCase() : (s: string) => string
>s : string
>s.toLowerCase() : string
>s.toLowerCase : () => string
>s : string
>toLowerCase : () => string
}

function f2() {
>f2 : () => void

let x = ''.match(/ /);
>x : RegExpMatchArray | null
>''.match(/ /) : RegExpMatchArray | null
>''.match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
>'' : string
>match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
>/ / : RegExp

let y = x ? x : [];
>y : RegExpMatchArray
>x ? x : [] : RegExpMatchArray
>x : RegExpMatchArray | null
>x : RegExpMatchArray
>[] : never[]

let z = y.map(s => s.toLowerCase());
>z : string[]
>y.map(s => s.toLowerCase()) : string[]
>y.map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>y : RegExpMatchArray
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>s => s.toLowerCase() : (s: string) => string
>s : string
>s.toLowerCase() : string
>s.toLowerCase : () => string
>s : string
>toLowerCase : () => string
}

Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ var e: Ellement;
>Ellement : Symbol(Ellement, Decl(nonContextuallyTypedLogicalOr.ts, 3, 1))

(c || e).dummy;
>(c || e).dummy : Symbol(dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22), Decl(nonContextuallyTypedLogicalOr.ts, 5, 20))
>(c || e).dummy : Symbol(Contextual.dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22))
>c : Symbol(c, Decl(nonContextuallyTypedLogicalOr.ts, 10, 3))
>e : Symbol(e, Decl(nonContextuallyTypedLogicalOr.ts, 11, 3))
>dummy : Symbol(dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22), Decl(nonContextuallyTypedLogicalOr.ts, 5, 20))
>dummy : Symbol(Contextual.dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22))

4 changes: 2 additions & 2 deletions tests/baselines/reference/nonContextuallyTypedLogicalOr.types
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ var e: Ellement;

(c || e).dummy;
>(c || e).dummy : any
>(c || e) : Contextual | Ellement
>c || e : Contextual | Ellement
>(c || e) : Contextual
>c || e : Contextual
>c : Contextual
>e : Ellement
>dummy : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ var b: { Foo2: Derived; }
>Derived : Derived

var r = true ? a : b; // ok
>r : { Foo?: Base; } | { Foo2: Derived; }
>true ? a : b : { Foo?: Base; } | { Foo2: Derived; }
>r : { Foo?: Base; }
>true ? a : b : { Foo?: Base; }
>true : boolean
>a : { Foo?: Base; }
>b : { Foo2: Derived; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ var b: { Foo2?: Derived; }
>Derived : Derived

var r = true ? a : b; // ok
>r : { Foo: Base; } | { Foo2?: Derived; }
>true ? a : b : { Foo: Base; } | { Foo2?: Derived; }
>r : { Foo2?: Derived; }
>true ? a : b : { Foo2?: Derived; }
>true : boolean
>a : { Foo: Base; }
>b : { Foo2?: Derived; }
Expand Down
19 changes: 19 additions & 0 deletions tests/cases/compiler/bestChoiceType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @strictNullChecks: true

// Repro from #10041

(''.match(/ /) || []).map(s => s.toLowerCase());

// Similar cases

function f1() {
let x = ''.match(/ /);
let y = x || [];
let z = y.map(s => s.toLowerCase());
}

function f2() {
let x = ''.match(/ /);
let y = x ? x : [];
let z = y.map(s => s.toLowerCase());
}