From b69652b137c280b16f7325b8dedd3bf029339f27 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 5 Oct 2017 09:01:39 -0700 Subject: [PATCH 1/4] Set symbol on union of spreads Previously, it was only set on the top-level type, and only if that top-level type was an object type. Now it uses `forEachType` to set the symbol on every object type in the union as well, if `getSpreadType` returns a union. --- src/compiler/checker.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fbe192c5629cf..d7585bd6e9a80 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13917,13 +13917,15 @@ namespace ts { if (propertiesArray.length > 0) { spread = getSpreadType(spread, createObjectLiteralType()); } - if (spread.flags & TypeFlags.Object) { - // only set the symbol and flags if this is a (fresh) object type - spread.flags |= propagatedFlags; - spread.flags |= TypeFlags.FreshLiteral; - (spread as ObjectType).objectFlags |= ObjectFlags.ObjectLiteral; - spread.symbol = node.symbol; - } + // only set the symbol and flags if this is a (fresh) object type + forEachType(spread, t => { + if (t.flags & TypeFlags.Object) { + t.flags |= propagatedFlags; + t.flags |= TypeFlags.FreshLiteral; + (t as ObjectType).objectFlags |= ObjectFlags.ObjectLiteral; + t.symbol = node.symbol + } + }); return spread; } From 0cb12b32a5abbe37b847fd93c94a435319480e61 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 5 Oct 2017 09:03:03 -0700 Subject: [PATCH 2/4] Test:{} in union from spread gets implicit index signature Also tighten up the existing test code in the file. --- .../objectSpreadIndexSignature.errors.txt | 19 ++++++ .../reference/objectSpreadIndexSignature.js | 28 ++++----- .../objectSpreadIndexSignature.symbols | 63 +++++++++---------- .../objectSpreadIndexSignature.types | 52 +++++++-------- .../spread/objectSpreadIndexSignature.ts | 21 +++---- 5 files changed, 96 insertions(+), 87 deletions(-) create mode 100644 tests/baselines/reference/objectSpreadIndexSignature.errors.txt diff --git a/tests/baselines/reference/objectSpreadIndexSignature.errors.txt b/tests/baselines/reference/objectSpreadIndexSignature.errors.txt new file mode 100644 index 0000000000000..ee7425909c29f --- /dev/null +++ b/tests/baselines/reference/objectSpreadIndexSignature.errors.txt @@ -0,0 +1,19 @@ +tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts(6,1): error TS7017: Element implicitly has an 'any' type because type '{ b: number; a: number; }' has no index signature. + + +==== tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts (1 errors) ==== + declare let indexed1: { [n: string]: number; a: number; }; + declare let indexed2: { [n: string]: boolean; c: boolean; }; + declare let indexed3: { [n: string]: number }; + let i = { ...indexed1, b: 11 }; + // only indexed has indexer, so i[101]: any + i[101]; + ~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type '{ b: number; a: number; }' has no index signature. + let ii = { ...indexed1, ...indexed2 }; + // both have indexer, so i[1001]: number | boolean + ii[1001]; + + declare const b: boolean; + indexed3 = { ...b ? indexed3 : undefined }; + \ No newline at end of file diff --git a/tests/baselines/reference/objectSpreadIndexSignature.js b/tests/baselines/reference/objectSpreadIndexSignature.js index 22e92e6a844bf..283129036daeb 100644 --- a/tests/baselines/reference/objectSpreadIndexSignature.js +++ b/tests/baselines/reference/objectSpreadIndexSignature.js @@ -1,23 +1,20 @@ //// [objectSpreadIndexSignature.ts] -interface Indexed { - [n: string]: number; - a: number; -} -interface Indexed2 { - [n: string]: boolean; - c: boolean; -} -let indexed: Indexed; -let indexed2: Indexed2; -let i = { ...indexed, b: 11 }; +declare let indexed1: { [n: string]: number; a: number; }; +declare let indexed2: { [n: string]: boolean; c: boolean; }; +declare let indexed3: { [n: string]: number }; +let i = { ...indexed1, b: 11 }; // only indexed has indexer, so i[101]: any i[101]; -let ii = { ...indexed, ...indexed2 }; +let ii = { ...indexed1, ...indexed2 }; // both have indexer, so i[1001]: number | boolean ii[1001]; + +declare const b: boolean; +indexed3 = { ...b ? indexed3 : undefined }; //// [objectSpreadIndexSignature.js] +"use strict"; var __assign = (this && this.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; @@ -26,11 +23,10 @@ var __assign = (this && this.__assign) || Object.assign || function(t) { } return t; }; -var indexed; -var indexed2; -var i = __assign({}, indexed, { b: 11 }); +var i = __assign({}, indexed1, { b: 11 }); // only indexed has indexer, so i[101]: any i[101]; -var ii = __assign({}, indexed, indexed2); +var ii = __assign({}, indexed1, indexed2); // both have indexer, so i[1001]: number | boolean ii[1001]; +indexed3 = __assign({}, b ? indexed3 : undefined); diff --git a/tests/baselines/reference/objectSpreadIndexSignature.symbols b/tests/baselines/reference/objectSpreadIndexSignature.symbols index cd64b15719699..d08cfff53ff8b 100644 --- a/tests/baselines/reference/objectSpreadIndexSignature.symbols +++ b/tests/baselines/reference/objectSpreadIndexSignature.symbols @@ -1,45 +1,42 @@ === tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts === -interface Indexed { ->Indexed : Symbol(Indexed, Decl(objectSpreadIndexSignature.ts, 0, 0)) +declare let indexed1: { [n: string]: number; a: number; }; +>indexed1 : Symbol(indexed1, Decl(objectSpreadIndexSignature.ts, 0, 11)) +>n : Symbol(n, Decl(objectSpreadIndexSignature.ts, 0, 25)) +>a : Symbol(a, Decl(objectSpreadIndexSignature.ts, 0, 44)) - [n: string]: number; ->n : Symbol(n, Decl(objectSpreadIndexSignature.ts, 1, 5)) +declare let indexed2: { [n: string]: boolean; c: boolean; }; +>indexed2 : Symbol(indexed2, Decl(objectSpreadIndexSignature.ts, 1, 11)) +>n : Symbol(n, Decl(objectSpreadIndexSignature.ts, 1, 25)) +>c : Symbol(c, Decl(objectSpreadIndexSignature.ts, 1, 45)) - a: number; ->a : Symbol(Indexed.a, Decl(objectSpreadIndexSignature.ts, 1, 24)) -} -interface Indexed2 { ->Indexed2 : Symbol(Indexed2, Decl(objectSpreadIndexSignature.ts, 3, 1)) +declare let indexed3: { [n: string]: number }; +>indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) +>n : Symbol(n, Decl(objectSpreadIndexSignature.ts, 2, 25)) - [n: string]: boolean; ->n : Symbol(n, Decl(objectSpreadIndexSignature.ts, 5, 5)) - - c: boolean; ->c : Symbol(Indexed2.c, Decl(objectSpreadIndexSignature.ts, 5, 25)) -} -let indexed: Indexed; ->indexed : Symbol(indexed, Decl(objectSpreadIndexSignature.ts, 8, 3)) ->Indexed : Symbol(Indexed, Decl(objectSpreadIndexSignature.ts, 0, 0)) - -let indexed2: Indexed2; ->indexed2 : Symbol(indexed2, Decl(objectSpreadIndexSignature.ts, 9, 3)) ->Indexed2 : Symbol(Indexed2, Decl(objectSpreadIndexSignature.ts, 3, 1)) - -let i = { ...indexed, b: 11 }; ->i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 10, 3)) ->indexed : Symbol(indexed, Decl(objectSpreadIndexSignature.ts, 8, 3)) ->b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 10, 21)) +let i = { ...indexed1, b: 11 }; +>i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 3, 3)) +>indexed1 : Symbol(indexed1, Decl(objectSpreadIndexSignature.ts, 0, 11)) +>b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 3, 22)) // only indexed has indexer, so i[101]: any i[101]; ->i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 10, 3)) +>i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 3, 3)) -let ii = { ...indexed, ...indexed2 }; ->ii : Symbol(ii, Decl(objectSpreadIndexSignature.ts, 13, 3)) ->indexed : Symbol(indexed, Decl(objectSpreadIndexSignature.ts, 8, 3)) ->indexed2 : Symbol(indexed2, Decl(objectSpreadIndexSignature.ts, 9, 3)) +let ii = { ...indexed1, ...indexed2 }; +>ii : Symbol(ii, Decl(objectSpreadIndexSignature.ts, 6, 3)) +>indexed1 : Symbol(indexed1, Decl(objectSpreadIndexSignature.ts, 0, 11)) +>indexed2 : Symbol(indexed2, Decl(objectSpreadIndexSignature.ts, 1, 11)) // both have indexer, so i[1001]: number | boolean ii[1001]; ->ii : Symbol(ii, Decl(objectSpreadIndexSignature.ts, 13, 3)) +>ii : Symbol(ii, Decl(objectSpreadIndexSignature.ts, 6, 3)) + +declare const b: boolean; +>b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 10, 13)) + +indexed3 = { ...b ? indexed3 : undefined }; +>indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) +>b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 10, 13)) +>indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) +>undefined : Symbol(undefined) diff --git a/tests/baselines/reference/objectSpreadIndexSignature.types b/tests/baselines/reference/objectSpreadIndexSignature.types index 5eebc2ffa02b7..eff3b04b8f618 100644 --- a/tests/baselines/reference/objectSpreadIndexSignature.types +++ b/tests/baselines/reference/objectSpreadIndexSignature.types @@ -1,34 +1,22 @@ === tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts === -interface Indexed { ->Indexed : Indexed - - [n: string]: number; +declare let indexed1: { [n: string]: number; a: number; }; +>indexed1 : { [n: string]: number; a: number; } >n : string - - a: number; >a : number -} -interface Indexed2 { ->Indexed2 : Indexed2 - [n: string]: boolean; +declare let indexed2: { [n: string]: boolean; c: boolean; }; +>indexed2 : { [n: string]: boolean; c: boolean; } >n : string - - c: boolean; >c : boolean -} -let indexed: Indexed; ->indexed : Indexed ->Indexed : Indexed -let indexed2: Indexed2; ->indexed2 : Indexed2 ->Indexed2 : Indexed2 +declare let indexed3: { [n: string]: number }; +>indexed3 : { [n: string]: number; } +>n : string -let i = { ...indexed, b: 11 }; +let i = { ...indexed1, b: 11 }; >i : { b: number; a: number; } ->{ ...indexed, b: 11 } : { b: number; a: number; } ->indexed : Indexed +>{ ...indexed1, b: 11 } : { b: number; a: number; } +>indexed1 : { [n: string]: number; a: number; } >b : number >11 : 11 @@ -38,11 +26,11 @@ i[101]; >i : { b: number; a: number; } >101 : 101 -let ii = { ...indexed, ...indexed2 }; +let ii = { ...indexed1, ...indexed2 }; >ii : { [x: string]: number | boolean; c: boolean; a: number; } ->{ ...indexed, ...indexed2 } : { [x: string]: number | boolean; c: boolean; a: number; } ->indexed : Indexed ->indexed2 : Indexed2 +>{ ...indexed1, ...indexed2 } : { [x: string]: number | boolean; c: boolean; a: number; } +>indexed1 : { [n: string]: number; a: number; } +>indexed2 : { [n: string]: boolean; c: boolean; } // both have indexer, so i[1001]: number | boolean ii[1001]; @@ -50,3 +38,15 @@ ii[1001]; >ii : { [x: string]: number | boolean; c: boolean; a: number; } >1001 : 1001 +declare const b: boolean; +>b : boolean + +indexed3 = { ...b ? indexed3 : undefined }; +>indexed3 = { ...b ? indexed3 : undefined } : {} | { [n: string]: number; } +>indexed3 : { [n: string]: number; } +>{ ...b ? indexed3 : undefined } : {} | { [n: string]: number; } +>b ? indexed3 : undefined : { [n: string]: number; } | undefined +>b : boolean +>indexed3 : { [n: string]: number; } +>undefined : undefined + diff --git a/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts b/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts index ae46f2547d54c..83649d465f183 100644 --- a/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts +++ b/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts @@ -1,16 +1,13 @@ -interface Indexed { - [n: string]: number; - a: number; -} -interface Indexed2 { - [n: string]: boolean; - c: boolean; -} -let indexed: Indexed; -let indexed2: Indexed2; -let i = { ...indexed, b: 11 }; +// @strict: true +declare let indexed1: { [n: string]: number; a: number; }; +declare let indexed2: { [n: string]: boolean; c: boolean; }; +declare let indexed3: { [n: string]: number }; +let i = { ...indexed1, b: 11 }; // only indexed has indexer, so i[101]: any i[101]; -let ii = { ...indexed, ...indexed2 }; +let ii = { ...indexed1, ...indexed2 }; // both have indexer, so i[1001]: number | boolean ii[1001]; + +declare const b: boolean; +indexed3 = { ...b ? indexed3 : undefined }; From 517dbf3ca77863daa5376dfb4a95088d1f0dffab Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 9 Oct 2017 11:14:24 -0700 Subject: [PATCH 3/4] Fix semicolon lint --- 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 d7585bd6e9a80..a61ded007f449 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13923,7 +13923,7 @@ namespace ts { t.flags |= propagatedFlags; t.flags |= TypeFlags.FreshLiteral; (t as ObjectType).objectFlags |= ObjectFlags.ObjectLiteral; - t.symbol = node.symbol + t.symbol = node.symbol; } }); return spread; From de68f067d5bf1b60aaae6c0162e482480807944e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 11 Oct 2017 08:17:40 -0700 Subject: [PATCH 4/4] Set flags on fresh object types from getSpreadType Previously, getSpreadType didn't set any flags and relied on its callers to do so. This was error-prone because getSpreadType often returns non-fresh types. --- src/compiler/checker.ts | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a61ded007f449..7c62dd4468c1c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7846,7 +7846,7 @@ namespace ts { * this function should be called in a left folding style, with left = previous result of getSpreadType * and right = the new element to be spread. */ - function getSpreadType(left: Type, right: Type): Type { + function getSpreadType(left: Type, right: Type, symbol: Symbol, propagatedFlags: TypeFlags): Type { if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { return anyType; } @@ -7857,10 +7857,10 @@ namespace ts { return left; } if (left.flags & TypeFlags.Union) { - return mapType(left, t => getSpreadType(t, right)); + return mapType(left, t => getSpreadType(t, right, symbol, propagatedFlags)); } if (right.flags & TypeFlags.Union) { - return mapType(right, t => getSpreadType(left, t)); + return mapType(right, t => getSpreadType(left, t, symbol, propagatedFlags)); } if (right.flags & TypeFlags.NonPrimitive) { return nonPrimitiveType; @@ -7918,7 +7918,13 @@ namespace ts { members.set(leftProp.escapedName, getNonReadonlySymbol(leftProp)); } } - return createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); + + const spread = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); + spread.flags |= propagatedFlags; + spread.flags |= TypeFlags.FreshLiteral; + (spread as ObjectType).objectFlags |= ObjectFlags.ObjectLiteral; + spread.symbol = symbol; + return spread; } function getNonReadonlySymbol(prop: Symbol) { @@ -13858,7 +13864,7 @@ namespace ts { checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); } if (propertiesArray.length > 0) { - spread = getSpreadType(spread, createObjectLiteralType()); + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, propagatedFlags); propertiesArray = []; propertiesTable = createSymbolTable(); hasComputedStringProperty = false; @@ -13870,7 +13876,7 @@ namespace ts { error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); return unknownType; } - spread = getSpreadType(spread, type); + spread = getSpreadType(spread, type, node.symbol, propagatedFlags); offset = i + 1; continue; } @@ -13915,17 +13921,8 @@ namespace ts { if (spread !== emptyObjectType) { if (propertiesArray.length > 0) { - spread = getSpreadType(spread, createObjectLiteralType()); + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, propagatedFlags); } - // only set the symbol and flags if this is a (fresh) object type - forEachType(spread, t => { - if (t.flags & TypeFlags.Object) { - t.flags |= propagatedFlags; - t.flags |= TypeFlags.FreshLiteral; - (t as ObjectType).objectFlags |= ObjectFlags.ObjectLiteral; - t.symbol = node.symbol; - } - }); return spread; } @@ -14045,7 +14042,7 @@ namespace ts { else { Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); if (attributesArray.length > 0) { - spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable)); + spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable), openingLikeElement.symbol, /*propagatedFlags*/ 0); attributesArray = []; attributesTable = createSymbolTable(); } @@ -14054,7 +14051,7 @@ namespace ts { hasSpreadAnyType = true; } if (isValidSpreadType(exprType)) { - spread = getSpreadType(spread, exprType); + spread = getSpreadType(spread, exprType, openingLikeElement.symbol, /*propagatedFlags*/ 0); } else { typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; @@ -14065,7 +14062,7 @@ namespace ts { if (!hasSpreadAnyType) { if (spread !== emptyObjectType) { if (attributesArray.length > 0) { - spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable)); + spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable), openingLikeElement.symbol, /*propagatedFlags*/ 0); } attributesArray = getPropertiesOfType(spread); }