From 12d1f72c9ffa81b00215283e28db67a9e59b7a25 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 18 Oct 2017 19:57:50 -0700 Subject: [PATCH 1/3] Retain literal types in contextual unions --- src/compiler/checker.ts | 10 +- .../contextualTypeShouldBeLiteral.js | 73 ++++++++++ .../contextualTypeShouldBeLiteral.symbols | 120 ++++++++++++++++ .../contextualTypeShouldBeLiteral.types | 132 ++++++++++++++++++ .../compiler/contextualTypeShouldBeLiteral.ts | 53 +++++++ 5 files changed, 383 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/contextualTypeShouldBeLiteral.js create mode 100644 tests/baselines/reference/contextualTypeShouldBeLiteral.symbols create mode 100644 tests/baselines/reference/contextualTypeShouldBeLiteral.types create mode 100644 tests/cases/compiler/contextualTypeShouldBeLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 09cddc53c10e5..1c4a57c5b0c1e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7477,7 +7477,7 @@ namespace ts { // expression constructs such as array literals and the || and ?: operators). Named types can // circularly reference themselves and therefore cannot be subtype reduced during their declaration. // For example, "type Item = string | (() => Item" is a named type that circularly references itself. - function getUnionType(types: Type[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + function getUnionType(types: Type[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[], retainRedundantTypes?: boolean): Type { if (types.length === 0) { return neverType; } @@ -7492,7 +7492,7 @@ namespace ts { if (subtypeReduction) { removeSubtypes(typeSet); } - else if (typeSet.containsStringOrNumberLiteral) { + else if (typeSet.containsStringOrNumberLiteral && !retainRedundantTypes) { removeRedundantLiteralTypes(typeSet); } if (typeSet.length === 0) { @@ -11606,7 +11606,7 @@ namespace ts { // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type): Type { + function mapType(type: Type, mapper: (t: Type) => Type, retainRedundantTypes?: boolean): Type { if (!(type.flags & TypeFlags.Union)) { return mapper(type); } @@ -11627,7 +11627,7 @@ namespace ts { } } } - return mappedTypes ? getUnionType(mappedTypes) : mappedType; + return mappedTypes ? getUnionType(mappedTypes, /*subtypeReduction*/ undefined, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, retainRedundantTypes) : mappedType; } function extractTypesOfKind(type: Type, kind: TypeFlags) { @@ -13410,7 +13410,7 @@ namespace ts { return mapType(type, t => { const prop = t.flags & TypeFlags.StructuredType ? getPropertyOfType(t, name) : undefined; return prop ? getTypeOfSymbol(prop) : undefined; - }); + }, /*retainRedundantTypes*/ true); } function getIndexTypeOfContextualType(type: Type, kind: IndexKind) { diff --git a/tests/baselines/reference/contextualTypeShouldBeLiteral.js b/tests/baselines/reference/contextualTypeShouldBeLiteral.js new file mode 100644 index 0000000000000..24fec76b6ff90 --- /dev/null +++ b/tests/baselines/reference/contextualTypeShouldBeLiteral.js @@ -0,0 +1,73 @@ +//// [contextualTypeShouldBeLiteral.ts] +interface X { + type: 'x'; + value: string; +} + +interface Y { + type: 'y'; + value: 'none' | 'done'; +} + +function foo(bar: X | Y) { } + +foo({ + type: 'y', + value: 'done', +}); + +interface X2 { + type1: 'x'; + value: string; +} + +interface Y2 { + type2: 'y'; + value: 'none' | 'done'; +} + +function foo2(bar: X2 | Y2) { } + +foo2({ + type2: 'y', + value: 'done', +}); + +interface X3 { + type: 'x'; + value: 1 | 2 | 3; + xtra: number; +} + +interface Y3 { + type: 'y'; + value: 11 | 12 | 13; + ytra: number; +} + +let xy: X3 | Y3 = { + type: 'y', + value: 11, + ytra: 12 +}; + +xy; + + +//// [contextualTypeShouldBeLiteral.js] +function foo(bar) { } +foo({ + type: 'y', + value: 'done' +}); +function foo2(bar) { } +foo2({ + type2: 'y', + value: 'done' +}); +var xy = { + type: 'y', + value: 11, + ytra: 12 +}; +xy; diff --git a/tests/baselines/reference/contextualTypeShouldBeLiteral.symbols b/tests/baselines/reference/contextualTypeShouldBeLiteral.symbols new file mode 100644 index 0000000000000..40cc07cadba85 --- /dev/null +++ b/tests/baselines/reference/contextualTypeShouldBeLiteral.symbols @@ -0,0 +1,120 @@ +=== tests/cases/compiler/contextualTypeShouldBeLiteral.ts === +interface X { +>X : Symbol(X, Decl(contextualTypeShouldBeLiteral.ts, 0, 0)) + + type: 'x'; +>type : Symbol(X.type, Decl(contextualTypeShouldBeLiteral.ts, 0, 13)) + + value: string; +>value : Symbol(X.value, Decl(contextualTypeShouldBeLiteral.ts, 1, 14)) +} + +interface Y { +>Y : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 3, 1)) + + type: 'y'; +>type : Symbol(Y.type, Decl(contextualTypeShouldBeLiteral.ts, 5, 13)) + + value: 'none' | 'done'; +>value : Symbol(Y.value, Decl(contextualTypeShouldBeLiteral.ts, 6, 14)) +} + +function foo(bar: X | Y) { } +>foo : Symbol(foo, Decl(contextualTypeShouldBeLiteral.ts, 8, 1)) +>bar : Symbol(bar, Decl(contextualTypeShouldBeLiteral.ts, 10, 13)) +>X : Symbol(X, Decl(contextualTypeShouldBeLiteral.ts, 0, 0)) +>Y : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 3, 1)) + +foo({ +>foo : Symbol(foo, Decl(contextualTypeShouldBeLiteral.ts, 8, 1)) + + type: 'y', +>type : Symbol(type, Decl(contextualTypeShouldBeLiteral.ts, 12, 5)) + + value: 'done', +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 13, 14)) + +}); + +interface X2 { +>X2 : Symbol(X2, Decl(contextualTypeShouldBeLiteral.ts, 15, 3)) + + type1: 'x'; +>type1 : Symbol(X2.type1, Decl(contextualTypeShouldBeLiteral.ts, 17, 14)) + + value: string; +>value : Symbol(X2.value, Decl(contextualTypeShouldBeLiteral.ts, 18, 15)) +} + +interface Y2 { +>Y2 : Symbol(Y2, Decl(contextualTypeShouldBeLiteral.ts, 20, 1)) + + type2: 'y'; +>type2 : Symbol(Y2.type2, Decl(contextualTypeShouldBeLiteral.ts, 22, 14)) + + value: 'none' | 'done'; +>value : Symbol(Y2.value, Decl(contextualTypeShouldBeLiteral.ts, 23, 15)) +} + +function foo2(bar: X2 | Y2) { } +>foo2 : Symbol(foo2, Decl(contextualTypeShouldBeLiteral.ts, 25, 1)) +>bar : Symbol(bar, Decl(contextualTypeShouldBeLiteral.ts, 27, 14)) +>X2 : Symbol(X2, Decl(contextualTypeShouldBeLiteral.ts, 15, 3)) +>Y2 : Symbol(Y2, Decl(contextualTypeShouldBeLiteral.ts, 20, 1)) + +foo2({ +>foo2 : Symbol(foo2, Decl(contextualTypeShouldBeLiteral.ts, 25, 1)) + + type2: 'y', +>type2 : Symbol(type2, Decl(contextualTypeShouldBeLiteral.ts, 29, 6)) + + value: 'done', +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 30, 15)) + +}); + +interface X3 { +>X3 : Symbol(X3, Decl(contextualTypeShouldBeLiteral.ts, 32, 3)) + + type: 'x'; +>type : Symbol(X3.type, Decl(contextualTypeShouldBeLiteral.ts, 34, 14)) + + value: 1 | 2 | 3; +>value : Symbol(X3.value, Decl(contextualTypeShouldBeLiteral.ts, 35, 14)) + + xtra: number; +>xtra : Symbol(X3.xtra, Decl(contextualTypeShouldBeLiteral.ts, 36, 21)) +} + +interface Y3 { +>Y3 : Symbol(Y3, Decl(contextualTypeShouldBeLiteral.ts, 38, 1)) + + type: 'y'; +>type : Symbol(Y3.type, Decl(contextualTypeShouldBeLiteral.ts, 40, 14)) + + value: 11 | 12 | 13; +>value : Symbol(Y3.value, Decl(contextualTypeShouldBeLiteral.ts, 41, 14)) + + ytra: number; +>ytra : Symbol(Y3.ytra, Decl(contextualTypeShouldBeLiteral.ts, 42, 24)) +} + +let xy: X3 | Y3 = { +>xy : Symbol(xy, Decl(contextualTypeShouldBeLiteral.ts, 46, 3)) +>X3 : Symbol(X3, Decl(contextualTypeShouldBeLiteral.ts, 32, 3)) +>Y3 : Symbol(Y3, Decl(contextualTypeShouldBeLiteral.ts, 38, 1)) + + type: 'y', +>type : Symbol(type, Decl(contextualTypeShouldBeLiteral.ts, 46, 19)) + + value: 11, +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 47, 14)) + + ytra: 12 +>ytra : Symbol(ytra, Decl(contextualTypeShouldBeLiteral.ts, 48, 14)) + +}; + +xy; +>xy : Symbol(xy, Decl(contextualTypeShouldBeLiteral.ts, 46, 3)) + diff --git a/tests/baselines/reference/contextualTypeShouldBeLiteral.types b/tests/baselines/reference/contextualTypeShouldBeLiteral.types new file mode 100644 index 0000000000000..3c8d4fe9b40a4 --- /dev/null +++ b/tests/baselines/reference/contextualTypeShouldBeLiteral.types @@ -0,0 +1,132 @@ +=== tests/cases/compiler/contextualTypeShouldBeLiteral.ts === +interface X { +>X : X + + type: 'x'; +>type : "x" + + value: string; +>value : string +} + +interface Y { +>Y : Y + + type: 'y'; +>type : "y" + + value: 'none' | 'done'; +>value : "none" | "done" +} + +function foo(bar: X | Y) { } +>foo : (bar: X | Y) => void +>bar : X | Y +>X : X +>Y : Y + +foo({ +>foo({ type: 'y', value: 'done',}) : void +>foo : (bar: X | Y) => void +>{ type: 'y', value: 'done',} : { type: "y"; value: "done"; } + + type: 'y', +>type : string +>'y' : "y" + + value: 'done', +>value : string +>'done' : "done" + +}); + +interface X2 { +>X2 : X2 + + type1: 'x'; +>type1 : "x" + + value: string; +>value : string +} + +interface Y2 { +>Y2 : Y2 + + type2: 'y'; +>type2 : "y" + + value: 'none' | 'done'; +>value : "none" | "done" +} + +function foo2(bar: X2 | Y2) { } +>foo2 : (bar: X2 | Y2) => void +>bar : X2 | Y2 +>X2 : X2 +>Y2 : Y2 + +foo2({ +>foo2({ type2: 'y', value: 'done',}) : void +>foo2 : (bar: X2 | Y2) => void +>{ type2: 'y', value: 'done',} : { type2: "y"; value: "done"; } + + type2: 'y', +>type2 : string +>'y' : "y" + + value: 'done', +>value : string +>'done' : "done" + +}); + +interface X3 { +>X3 : X3 + + type: 'x'; +>type : "x" + + value: 1 | 2 | 3; +>value : 1 | 2 | 3 + + xtra: number; +>xtra : number +} + +interface Y3 { +>Y3 : Y3 + + type: 'y'; +>type : "y" + + value: 11 | 12 | 13; +>value : 11 | 12 | 13 + + ytra: number; +>ytra : number +} + +let xy: X3 | Y3 = { +>xy : X3 | Y3 +>X3 : X3 +>Y3 : Y3 +>{ type: 'y', value: 11, ytra: 12} : { type: "y"; value: 11; ytra: number; } + + type: 'y', +>type : string +>'y' : "y" + + value: 11, +>value : number +>11 : 11 + + ytra: 12 +>ytra : number +>12 : 12 + +}; + +xy; +>xy : Y3 + diff --git a/tests/cases/compiler/contextualTypeShouldBeLiteral.ts b/tests/cases/compiler/contextualTypeShouldBeLiteral.ts new file mode 100644 index 0000000000000..9f53ebc875485 --- /dev/null +++ b/tests/cases/compiler/contextualTypeShouldBeLiteral.ts @@ -0,0 +1,53 @@ +interface X { + type: 'x'; + value: string; +} + +interface Y { + type: 'y'; + value: 'none' | 'done'; +} + +function foo(bar: X | Y) { } + +foo({ + type: 'y', + value: 'done', +}); + +interface X2 { + type1: 'x'; + value: string; +} + +interface Y2 { + type2: 'y'; + value: 'none' | 'done'; +} + +function foo2(bar: X2 | Y2) { } + +foo2({ + type2: 'y', + value: 'done', +}); + +interface X3 { + type: 'x'; + value: 1 | 2 | 3; + xtra: number; +} + +interface Y3 { + type: 'y'; + value: 11 | 12 | 13; + ytra: number; +} + +let xy: X3 | Y3 = { + type: 'y', + value: 11, + ytra: 12 +}; + +xy; From 541e42e2f57e168d8d0eab220f31e1f175a8d452 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 19 Oct 2017 14:17:32 -0700 Subject: [PATCH 2/3] Discriminate contextual this --- src/compiler/checker.ts | 26 ++- .../contextualTypeShouldBeLiteral.js | 69 ++++++- .../contextualTypeShouldBeLiteral.symbols | 183 ++++++++++++++---- .../contextualTypeShouldBeLiteral.types | 124 +++++++++++- .../compiler/contextualTypeShouldBeLiteral.ts | 44 +++++ 5 files changed, 401 insertions(+), 45 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1c4a57c5b0c1e..e54c698adc91c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13149,7 +13149,31 @@ namespace ts { // We have an object literal method. Check if the containing object literal has a contextual type // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in // any directly enclosing object literals. - const contextualType = getApparentTypeOfContextualType(containingLiteral); + let contextualType = getApparentTypeOfContextualType(containingLiteral); + if (contextualType.flags & TypeFlags.Union) { + let match: Type | undefined; + propLoop: for (const prop of containingLiteral.properties) { + if (!prop.symbol) continue; + if (prop.kind !== SyntaxKind.PropertyAssignment) continue; + if (isDiscriminantProperty(contextualType, prop.symbol.escapedName)) { + const discriminatingType = getTypeOfNode((prop as PropertyAssignment).initializer); + for (const type of (contextualType as UnionType).types) { + const targetType = getTypeOfPropertyOfType(type, prop.symbol.escapedName); + if (targetType && checkTypeAssignableTo(discriminatingType, targetType, /*errorNode*/ undefined)) { + if (match) { + if (type === match) continue; // Finding multiple fields which discriminate to the same type is fine + match = undefined; + break propLoop; + } + match = type; + } + } + } + } + if (match) { + contextualType = match; + } + } let literal = containingLiteral; let type = contextualType; while (type) { diff --git a/tests/baselines/reference/contextualTypeShouldBeLiteral.js b/tests/baselines/reference/contextualTypeShouldBeLiteral.js index 24fec76b6ff90..dcb9da993c1cb 100644 --- a/tests/baselines/reference/contextualTypeShouldBeLiteral.js +++ b/tests/baselines/reference/contextualTypeShouldBeLiteral.js @@ -2,11 +2,13 @@ interface X { type: 'x'; value: string; + method(): void; } interface Y { type: 'y'; value: 'none' | 'done'; + method(): void; } function foo(bar: X | Y) { } @@ -14,16 +16,23 @@ function foo(bar: X | Y) { } foo({ type: 'y', value: 'done', + method() { + this; + this.type; + this.value; + } }); interface X2 { type1: 'x'; value: string; + method(): void; } interface Y2 { type2: 'y'; value: 'none' | 'done'; + method(): void; } function foo2(bar: X2 | Y2) { } @@ -31,6 +40,10 @@ function foo2(bar: X2 | Y2) { } foo2({ type2: 'y', value: 'done', + method() { + this; + this.value; + } }); interface X3 { @@ -52,18 +65,56 @@ let xy: X3 | Y3 = { }; xy; - + + +interface LikeA { + x: 'x'; + y: 'y'; + value: string; + method(): void; +} + +interface LikeB { + x: 'xx'; + y: 'yy'; + value: number; + method(): void; +} + +let xyz: LikeA | LikeB = { + x: 'x', + y: 'y', + value: "foo", + method() { + this; + this.x; + this.y; + this.value; + } +}; + +xyz; //// [contextualTypeShouldBeLiteral.js] +"use strict"; function foo(bar) { } foo({ type: 'y', - value: 'done' + value: 'done', + method: function () { + this; + this.type; + this.value; + } }); function foo2(bar) { } foo2({ type2: 'y', - value: 'done' + value: 'done', + method: function () { + this; + this.value; + } }); var xy = { type: 'y', @@ -71,3 +122,15 @@ var xy = { ytra: 12 }; xy; +var xyz = { + x: 'x', + y: 'y', + value: "foo", + method: function () { + this; + this.x; + this.y; + this.value; + } +}; +xyz; diff --git a/tests/baselines/reference/contextualTypeShouldBeLiteral.symbols b/tests/baselines/reference/contextualTypeShouldBeLiteral.symbols index 40cc07cadba85..314117b509d94 100644 --- a/tests/baselines/reference/contextualTypeShouldBeLiteral.symbols +++ b/tests/baselines/reference/contextualTypeShouldBeLiteral.symbols @@ -7,114 +7,223 @@ interface X { value: string; >value : Symbol(X.value, Decl(contextualTypeShouldBeLiteral.ts, 1, 14)) + + method(): void; +>method : Symbol(X.method, Decl(contextualTypeShouldBeLiteral.ts, 2, 18)) } interface Y { ->Y : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 3, 1)) +>Y : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 4, 1)) type: 'y'; ->type : Symbol(Y.type, Decl(contextualTypeShouldBeLiteral.ts, 5, 13)) +>type : Symbol(Y.type, Decl(contextualTypeShouldBeLiteral.ts, 6, 13)) value: 'none' | 'done'; ->value : Symbol(Y.value, Decl(contextualTypeShouldBeLiteral.ts, 6, 14)) +>value : Symbol(Y.value, Decl(contextualTypeShouldBeLiteral.ts, 7, 14)) + + method(): void; +>method : Symbol(Y.method, Decl(contextualTypeShouldBeLiteral.ts, 8, 27)) } function foo(bar: X | Y) { } ->foo : Symbol(foo, Decl(contextualTypeShouldBeLiteral.ts, 8, 1)) ->bar : Symbol(bar, Decl(contextualTypeShouldBeLiteral.ts, 10, 13)) +>foo : Symbol(foo, Decl(contextualTypeShouldBeLiteral.ts, 10, 1)) +>bar : Symbol(bar, Decl(contextualTypeShouldBeLiteral.ts, 12, 13)) >X : Symbol(X, Decl(contextualTypeShouldBeLiteral.ts, 0, 0)) ->Y : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 3, 1)) +>Y : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 4, 1)) foo({ ->foo : Symbol(foo, Decl(contextualTypeShouldBeLiteral.ts, 8, 1)) +>foo : Symbol(foo, Decl(contextualTypeShouldBeLiteral.ts, 10, 1)) type: 'y', ->type : Symbol(type, Decl(contextualTypeShouldBeLiteral.ts, 12, 5)) +>type : Symbol(type, Decl(contextualTypeShouldBeLiteral.ts, 14, 5)) value: 'done', ->value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 13, 14)) +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 15, 14)) + + method() { +>method : Symbol(method, Decl(contextualTypeShouldBeLiteral.ts, 16, 18)) + + this; +>this : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 4, 1)) + + this.type; +>this.type : Symbol(Y.type, Decl(contextualTypeShouldBeLiteral.ts, 6, 13)) +>this : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 4, 1)) +>type : Symbol(Y.type, Decl(contextualTypeShouldBeLiteral.ts, 6, 13)) + this.value; +>this.value : Symbol(Y.value, Decl(contextualTypeShouldBeLiteral.ts, 7, 14)) +>this : Symbol(Y, Decl(contextualTypeShouldBeLiteral.ts, 4, 1)) +>value : Symbol(Y.value, Decl(contextualTypeShouldBeLiteral.ts, 7, 14)) + } }); interface X2 { ->X2 : Symbol(X2, Decl(contextualTypeShouldBeLiteral.ts, 15, 3)) +>X2 : Symbol(X2, Decl(contextualTypeShouldBeLiteral.ts, 22, 3)) type1: 'x'; ->type1 : Symbol(X2.type1, Decl(contextualTypeShouldBeLiteral.ts, 17, 14)) +>type1 : Symbol(X2.type1, Decl(contextualTypeShouldBeLiteral.ts, 24, 14)) value: string; ->value : Symbol(X2.value, Decl(contextualTypeShouldBeLiteral.ts, 18, 15)) +>value : Symbol(X2.value, Decl(contextualTypeShouldBeLiteral.ts, 25, 15)) + + method(): void; +>method : Symbol(X2.method, Decl(contextualTypeShouldBeLiteral.ts, 26, 18)) } interface Y2 { ->Y2 : Symbol(Y2, Decl(contextualTypeShouldBeLiteral.ts, 20, 1)) +>Y2 : Symbol(Y2, Decl(contextualTypeShouldBeLiteral.ts, 28, 1)) type2: 'y'; ->type2 : Symbol(Y2.type2, Decl(contextualTypeShouldBeLiteral.ts, 22, 14)) +>type2 : Symbol(Y2.type2, Decl(contextualTypeShouldBeLiteral.ts, 30, 14)) value: 'none' | 'done'; ->value : Symbol(Y2.value, Decl(contextualTypeShouldBeLiteral.ts, 23, 15)) +>value : Symbol(Y2.value, Decl(contextualTypeShouldBeLiteral.ts, 31, 15)) + + method(): void; +>method : Symbol(Y2.method, Decl(contextualTypeShouldBeLiteral.ts, 32, 27)) } function foo2(bar: X2 | Y2) { } ->foo2 : Symbol(foo2, Decl(contextualTypeShouldBeLiteral.ts, 25, 1)) ->bar : Symbol(bar, Decl(contextualTypeShouldBeLiteral.ts, 27, 14)) ->X2 : Symbol(X2, Decl(contextualTypeShouldBeLiteral.ts, 15, 3)) ->Y2 : Symbol(Y2, Decl(contextualTypeShouldBeLiteral.ts, 20, 1)) +>foo2 : Symbol(foo2, Decl(contextualTypeShouldBeLiteral.ts, 34, 1)) +>bar : Symbol(bar, Decl(contextualTypeShouldBeLiteral.ts, 36, 14)) +>X2 : Symbol(X2, Decl(contextualTypeShouldBeLiteral.ts, 22, 3)) +>Y2 : Symbol(Y2, Decl(contextualTypeShouldBeLiteral.ts, 28, 1)) foo2({ ->foo2 : Symbol(foo2, Decl(contextualTypeShouldBeLiteral.ts, 25, 1)) +>foo2 : Symbol(foo2, Decl(contextualTypeShouldBeLiteral.ts, 34, 1)) type2: 'y', ->type2 : Symbol(type2, Decl(contextualTypeShouldBeLiteral.ts, 29, 6)) +>type2 : Symbol(type2, Decl(contextualTypeShouldBeLiteral.ts, 38, 6)) value: 'done', ->value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 30, 15)) +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 39, 15)) + method() { +>method : Symbol(method, Decl(contextualTypeShouldBeLiteral.ts, 40, 18)) + + this; + this.value; +>this.value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 25, 15), Decl(contextualTypeShouldBeLiteral.ts, 31, 15)) +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 25, 15), Decl(contextualTypeShouldBeLiteral.ts, 31, 15)) + } }); interface X3 { ->X3 : Symbol(X3, Decl(contextualTypeShouldBeLiteral.ts, 32, 3)) +>X3 : Symbol(X3, Decl(contextualTypeShouldBeLiteral.ts, 45, 3)) type: 'x'; ->type : Symbol(X3.type, Decl(contextualTypeShouldBeLiteral.ts, 34, 14)) +>type : Symbol(X3.type, Decl(contextualTypeShouldBeLiteral.ts, 47, 14)) value: 1 | 2 | 3; ->value : Symbol(X3.value, Decl(contextualTypeShouldBeLiteral.ts, 35, 14)) +>value : Symbol(X3.value, Decl(contextualTypeShouldBeLiteral.ts, 48, 14)) xtra: number; ->xtra : Symbol(X3.xtra, Decl(contextualTypeShouldBeLiteral.ts, 36, 21)) +>xtra : Symbol(X3.xtra, Decl(contextualTypeShouldBeLiteral.ts, 49, 21)) } interface Y3 { ->Y3 : Symbol(Y3, Decl(contextualTypeShouldBeLiteral.ts, 38, 1)) +>Y3 : Symbol(Y3, Decl(contextualTypeShouldBeLiteral.ts, 51, 1)) type: 'y'; ->type : Symbol(Y3.type, Decl(contextualTypeShouldBeLiteral.ts, 40, 14)) +>type : Symbol(Y3.type, Decl(contextualTypeShouldBeLiteral.ts, 53, 14)) value: 11 | 12 | 13; ->value : Symbol(Y3.value, Decl(contextualTypeShouldBeLiteral.ts, 41, 14)) +>value : Symbol(Y3.value, Decl(contextualTypeShouldBeLiteral.ts, 54, 14)) ytra: number; ->ytra : Symbol(Y3.ytra, Decl(contextualTypeShouldBeLiteral.ts, 42, 24)) +>ytra : Symbol(Y3.ytra, Decl(contextualTypeShouldBeLiteral.ts, 55, 24)) } let xy: X3 | Y3 = { ->xy : Symbol(xy, Decl(contextualTypeShouldBeLiteral.ts, 46, 3)) ->X3 : Symbol(X3, Decl(contextualTypeShouldBeLiteral.ts, 32, 3)) ->Y3 : Symbol(Y3, Decl(contextualTypeShouldBeLiteral.ts, 38, 1)) +>xy : Symbol(xy, Decl(contextualTypeShouldBeLiteral.ts, 59, 3)) +>X3 : Symbol(X3, Decl(contextualTypeShouldBeLiteral.ts, 45, 3)) +>Y3 : Symbol(Y3, Decl(contextualTypeShouldBeLiteral.ts, 51, 1)) type: 'y', ->type : Symbol(type, Decl(contextualTypeShouldBeLiteral.ts, 46, 19)) +>type : Symbol(type, Decl(contextualTypeShouldBeLiteral.ts, 59, 19)) value: 11, ->value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 47, 14)) +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 60, 14)) ytra: 12 ->ytra : Symbol(ytra, Decl(contextualTypeShouldBeLiteral.ts, 48, 14)) +>ytra : Symbol(ytra, Decl(contextualTypeShouldBeLiteral.ts, 61, 14)) }; xy; ->xy : Symbol(xy, Decl(contextualTypeShouldBeLiteral.ts, 46, 3)) +>xy : Symbol(xy, Decl(contextualTypeShouldBeLiteral.ts, 59, 3)) + + +interface LikeA { +>LikeA : Symbol(LikeA, Decl(contextualTypeShouldBeLiteral.ts, 65, 3)) + + x: 'x'; +>x : Symbol(LikeA.x, Decl(contextualTypeShouldBeLiteral.ts, 68, 17)) + + y: 'y'; +>y : Symbol(LikeA.y, Decl(contextualTypeShouldBeLiteral.ts, 69, 11)) + + value: string; +>value : Symbol(LikeA.value, Decl(contextualTypeShouldBeLiteral.ts, 70, 11)) + + method(): void; +>method : Symbol(LikeA.method, Decl(contextualTypeShouldBeLiteral.ts, 71, 18)) +} + +interface LikeB { +>LikeB : Symbol(LikeB, Decl(contextualTypeShouldBeLiteral.ts, 73, 1)) + + x: 'xx'; +>x : Symbol(LikeB.x, Decl(contextualTypeShouldBeLiteral.ts, 75, 17)) + + y: 'yy'; +>y : Symbol(LikeB.y, Decl(contextualTypeShouldBeLiteral.ts, 76, 12)) + + value: number; +>value : Symbol(LikeB.value, Decl(contextualTypeShouldBeLiteral.ts, 77, 12)) + + method(): void; +>method : Symbol(LikeB.method, Decl(contextualTypeShouldBeLiteral.ts, 78, 18)) +} + +let xyz: LikeA | LikeB = { +>xyz : Symbol(xyz, Decl(contextualTypeShouldBeLiteral.ts, 82, 3)) +>LikeA : Symbol(LikeA, Decl(contextualTypeShouldBeLiteral.ts, 65, 3)) +>LikeB : Symbol(LikeB, Decl(contextualTypeShouldBeLiteral.ts, 73, 1)) + + x: 'x', +>x : Symbol(x, Decl(contextualTypeShouldBeLiteral.ts, 82, 26)) + + y: 'y', +>y : Symbol(y, Decl(contextualTypeShouldBeLiteral.ts, 83, 11)) + + value: "foo", +>value : Symbol(value, Decl(contextualTypeShouldBeLiteral.ts, 84, 11)) + + method() { +>method : Symbol(method, Decl(contextualTypeShouldBeLiteral.ts, 85, 17)) + + this; +>this : Symbol(LikeA, Decl(contextualTypeShouldBeLiteral.ts, 65, 3)) + + this.x; +>this.x : Symbol(LikeA.x, Decl(contextualTypeShouldBeLiteral.ts, 68, 17)) +>this : Symbol(LikeA, Decl(contextualTypeShouldBeLiteral.ts, 65, 3)) +>x : Symbol(LikeA.x, Decl(contextualTypeShouldBeLiteral.ts, 68, 17)) + + this.y; +>this.y : Symbol(LikeA.y, Decl(contextualTypeShouldBeLiteral.ts, 69, 11)) +>this : Symbol(LikeA, Decl(contextualTypeShouldBeLiteral.ts, 65, 3)) +>y : Symbol(LikeA.y, Decl(contextualTypeShouldBeLiteral.ts, 69, 11)) + + this.value; +>this.value : Symbol(LikeA.value, Decl(contextualTypeShouldBeLiteral.ts, 70, 11)) +>this : Symbol(LikeA, Decl(contextualTypeShouldBeLiteral.ts, 65, 3)) +>value : Symbol(LikeA.value, Decl(contextualTypeShouldBeLiteral.ts, 70, 11)) + } +}; + +xyz; +>xyz : Symbol(xyz, Decl(contextualTypeShouldBeLiteral.ts, 82, 3)) diff --git a/tests/baselines/reference/contextualTypeShouldBeLiteral.types b/tests/baselines/reference/contextualTypeShouldBeLiteral.types index 3c8d4fe9b40a4..8ab09df66b832 100644 --- a/tests/baselines/reference/contextualTypeShouldBeLiteral.types +++ b/tests/baselines/reference/contextualTypeShouldBeLiteral.types @@ -7,6 +7,9 @@ interface X { value: string; >value : string + + method(): void; +>method : () => void } interface Y { @@ -17,6 +20,9 @@ interface Y { value: 'none' | 'done'; >value : "none" | "done" + + method(): void; +>method : () => void } function foo(bar: X | Y) { } @@ -26,9 +32,9 @@ function foo(bar: X | Y) { } >Y : Y foo({ ->foo({ type: 'y', value: 'done',}) : void +>foo({ type: 'y', value: 'done', method() { this; this.type; this.value; }}) : void >foo : (bar: X | Y) => void ->{ type: 'y', value: 'done',} : { type: "y"; value: "done"; } +>{ type: 'y', value: 'done', method() { this; this.type; this.value; }} : { type: "y"; value: "done"; method(): void; } type: 'y', >type : string @@ -38,6 +44,22 @@ foo({ >value : string >'done' : "done" + method() { +>method : () => void + + this; +>this : Y + + this.type; +>this.type : "y" +>this : Y +>type : "y" + + this.value; +>this.value : "none" | "done" +>this : Y +>value : "none" | "done" + } }); interface X2 { @@ -48,6 +70,9 @@ interface X2 { value: string; >value : string + + method(): void; +>method : () => void } interface Y2 { @@ -58,6 +83,9 @@ interface Y2 { value: 'none' | 'done'; >value : "none" | "done" + + method(): void; +>method : () => void } function foo2(bar: X2 | Y2) { } @@ -67,9 +95,9 @@ function foo2(bar: X2 | Y2) { } >Y2 : Y2 foo2({ ->foo2({ type2: 'y', value: 'done',}) : void +>foo2({ type2: 'y', value: 'done', method() { this; this.value; }}) : void >foo2 : (bar: X2 | Y2) => void ->{ type2: 'y', value: 'done',} : { type2: "y"; value: "done"; } +>{ type2: 'y', value: 'done', method() { this; this.value; }} : { type2: "y"; value: "done"; method(): void; } type2: 'y', >type2 : string @@ -79,6 +107,17 @@ foo2({ >value : string >'done' : "done" + method() { +>method : () => void + + this; +>this : X2 | Y2 + + this.value; +>this.value : string +>this : X2 | Y2 +>value : string + } }); interface X3 { @@ -130,3 +169,80 @@ let xy: X3 | Y3 = { xy; >xy : Y3 + +interface LikeA { +>LikeA : LikeA + + x: 'x'; +>x : "x" + + y: 'y'; +>y : "y" + + value: string; +>value : string + + method(): void; +>method : () => void +} + +interface LikeB { +>LikeB : LikeB + + x: 'xx'; +>x : "xx" + + y: 'yy'; +>y : "yy" + + value: number; +>value : number + + method(): void; +>method : () => void +} + +let xyz: LikeA | LikeB = { +>xyz : LikeA | LikeB +>LikeA : LikeA +>LikeB : LikeB +>{ x: 'x', y: 'y', value: "foo", method() { this; this.x; this.y; this.value; }} : { x: "x"; y: "y"; value: string; method(): void; } + + x: 'x', +>x : string +>'x' : "x" + + y: 'y', +>y : string +>'y' : "y" + + value: "foo", +>value : string +>"foo" : "foo" + + method() { +>method : () => void + + this; +>this : LikeA + + this.x; +>this.x : "x" +>this : LikeA +>x : "x" + + this.y; +>this.y : "y" +>this : LikeA +>y : "y" + + this.value; +>this.value : string +>this : LikeA +>value : string + } +}; + +xyz; +>xyz : LikeA + diff --git a/tests/cases/compiler/contextualTypeShouldBeLiteral.ts b/tests/cases/compiler/contextualTypeShouldBeLiteral.ts index 9f53ebc875485..2a2deda50ab62 100644 --- a/tests/cases/compiler/contextualTypeShouldBeLiteral.ts +++ b/tests/cases/compiler/contextualTypeShouldBeLiteral.ts @@ -1,11 +1,15 @@ + +// @strict: true interface X { type: 'x'; value: string; + method(): void; } interface Y { type: 'y'; value: 'none' | 'done'; + method(): void; } function foo(bar: X | Y) { } @@ -13,16 +17,23 @@ function foo(bar: X | Y) { } foo({ type: 'y', value: 'done', + method() { + this; + this.type; + this.value; + } }); interface X2 { type1: 'x'; value: string; + method(): void; } interface Y2 { type2: 'y'; value: 'none' | 'done'; + method(): void; } function foo2(bar: X2 | Y2) { } @@ -30,6 +41,10 @@ function foo2(bar: X2 | Y2) { } foo2({ type2: 'y', value: 'done', + method() { + this; + this.value; + } }); interface X3 { @@ -51,3 +66,32 @@ let xy: X3 | Y3 = { }; xy; + + +interface LikeA { + x: 'x'; + y: 'y'; + value: string; + method(): void; +} + +interface LikeB { + x: 'xx'; + y: 'yy'; + value: number; + method(): void; +} + +let xyz: LikeA | LikeB = { + x: 'x', + y: 'y', + value: "foo", + method() { + this; + this.x; + this.y; + this.value; + } +}; + +xyz; \ No newline at end of file From e8cd3b5624f9b3016b950ad5fff9acdc8b2e5d83 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 19 Oct 2017 14:39:34 -0700 Subject: [PATCH 3/3] Check if contextualType exists first --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e54c698adc91c..8876e9b8862a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13150,7 +13150,7 @@ namespace ts { // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in // any directly enclosing object literals. let contextualType = getApparentTypeOfContextualType(containingLiteral); - if (contextualType.flags & TypeFlags.Union) { + if (contextualType && contextualType.flags & TypeFlags.Union) { let match: Type | undefined; propLoop: for (const prop of containingLiteral.properties) { if (!prop.symbol) continue;