From fc85bc5a8aeafd69215bfdb308fbd064909b11c9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 31 Jul 2016 07:57:01 -0700 Subject: [PATCH 1/4] Use "best choice type" for || and ?: operators --- src/compiler/checker.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 33abcb2af12..7d878765494 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); } @@ -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); @@ -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) { From 47f6bb2e26a57e53346412c84b65f01029e6ddef Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 1 Aug 2016 09:49:27 -0700 Subject: [PATCH 2/4] Add test --- tests/cases/compiler/bestChoiceType.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/cases/compiler/bestChoiceType.ts diff --git a/tests/cases/compiler/bestChoiceType.ts b/tests/cases/compiler/bestChoiceType.ts new file mode 100644 index 00000000000..3f8ce4bc868 --- /dev/null +++ b/tests/cases/compiler/bestChoiceType.ts @@ -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()); +} From aa8c6c8f631b65705ead8019ffe0a955fff5aeab Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 1 Aug 2016 09:50:08 -0700 Subject: [PATCH 3/4] Add test baselines --- tests/baselines/reference/bestChoiceType.js | 35 ++++++++ .../reference/bestChoiceType.symbols | 63 +++++++++++++ .../baselines/reference/bestChoiceType.types | 88 +++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 tests/baselines/reference/bestChoiceType.js create mode 100644 tests/baselines/reference/bestChoiceType.symbols create mode 100644 tests/baselines/reference/bestChoiceType.types diff --git a/tests/baselines/reference/bestChoiceType.js b/tests/baselines/reference/bestChoiceType.js new file mode 100644 index 00000000000..0c1bf5df2d4 --- /dev/null +++ b/tests/baselines/reference/bestChoiceType.js @@ -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(); }); +} diff --git a/tests/baselines/reference/bestChoiceType.symbols b/tests/baselines/reference/bestChoiceType.symbols new file mode 100644 index 00000000000..25f7de2eeda --- /dev/null +++ b/tests/baselines/reference/bestChoiceType.symbols @@ -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, --, --)) +} + diff --git a/tests/baselines/reference/bestChoiceType.types b/tests/baselines/reference/bestChoiceType.types new file mode 100644 index 00000000000..f88cf64e5a2 --- /dev/null +++ b/tests/baselines/reference/bestChoiceType.types @@ -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 : (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 : (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 : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +>y : RegExpMatchArray +>map : (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 : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +>y : RegExpMatchArray +>map : (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 +} + From c7d2e5975ba21afce30af3cb083bee41e5367f6f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 1 Aug 2016 09:50:17 -0700 Subject: [PATCH 4/4] Accept new baselines --- .../baselines/reference/nonContextuallyTypedLogicalOr.symbols | 4 ++-- tests/baselines/reference/nonContextuallyTypedLogicalOr.types | 4 ++-- .../reference/subtypingWithObjectMembersOptionality3.types | 4 ++-- .../reference/subtypingWithObjectMembersOptionality4.types | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/nonContextuallyTypedLogicalOr.symbols b/tests/baselines/reference/nonContextuallyTypedLogicalOr.symbols index 2d921f6631c..43d7539b6b9 100644 --- a/tests/baselines/reference/nonContextuallyTypedLogicalOr.symbols +++ b/tests/baselines/reference/nonContextuallyTypedLogicalOr.symbols @@ -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)) diff --git a/tests/baselines/reference/nonContextuallyTypedLogicalOr.types b/tests/baselines/reference/nonContextuallyTypedLogicalOr.types index aeb9d1409c6..5061ad1d975 100644 --- a/tests/baselines/reference/nonContextuallyTypedLogicalOr.types +++ b/tests/baselines/reference/nonContextuallyTypedLogicalOr.types @@ -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 diff --git a/tests/baselines/reference/subtypingWithObjectMembersOptionality3.types b/tests/baselines/reference/subtypingWithObjectMembersOptionality3.types index 000d1cdc8ed..6490b0abd9b 100644 --- a/tests/baselines/reference/subtypingWithObjectMembersOptionality3.types +++ b/tests/baselines/reference/subtypingWithObjectMembersOptionality3.types @@ -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; } diff --git a/tests/baselines/reference/subtypingWithObjectMembersOptionality4.types b/tests/baselines/reference/subtypingWithObjectMembersOptionality4.types index 2dcda59f46f..ee636be020b 100644 --- a/tests/baselines/reference/subtypingWithObjectMembersOptionality4.types +++ b/tests/baselines/reference/subtypingWithObjectMembersOptionality4.types @@ -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; }