From a626e4efee6fffd67bfa1f013841bf045c24606a Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Thu, 27 Dec 2018 21:47:10 +0100 Subject: [PATCH] don't drop index signatures when spreading object Fixes: #27273 --- src/compiler/checker.ts | 20 +++- tests/baselines/reference/objectRest.types | 2 +- .../objectSpreadComputedProperty.types | 12 +-- .../objectSpreadIndexSignature.errors.txt | 23 ----- .../reference/objectSpreadIndexSignature.js | 34 ++++++- .../objectSpreadIndexSignature.symbols | 90 ++++++++++++++--- .../objectSpreadIndexSignature.types | 98 +++++++++++++++++-- .../spread/objectSpreadIndexSignature.ts | 19 ++++ 8 files changed, 248 insertions(+), 50 deletions(-) delete mode 100644 tests/baselines/reference/objectSpreadIndexSignature.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 23c5568f2e5f4..d103c84553ea4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6949,7 +6949,7 @@ namespace ts { } function unionSpreadIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined { - return info1 && info2 && createIndexInfo( + return !info1 ? info2 : !info2 ? info1 : createIndexInfo( getUnionType([info1.type, info2.type]), info1.isReadonly || info2.isReadonly); } @@ -10273,6 +10273,24 @@ namespace ts { } } + if (stringIndexInfo || numberIndexInfo) { + const addToNumberIndex: Type[] = []; + const addToStringIndex: Type[] = []; + members.forEach((symbol, key) => { + const type = getTypeOfSymbol(symbol); + addToStringIndex.push(type); + if (isNumericLiteralName(key)) { + addToNumberIndex.push(type); + } + }); + if (stringIndexInfo) { + stringIndexInfo = createIndexInfo(getUnionType([stringIndexInfo.type, ...addToStringIndex]), stringIndexInfo.isReadonly); + } + if (numberIndexInfo) { + numberIndexInfo = createIndexInfo(getUnionType([numberIndexInfo.type, ...addToNumberIndex]), numberIndexInfo.isReadonly); + } + } + const spread = createAnonymousType( symbol, members, diff --git a/tests/baselines/reference/objectRest.types b/tests/baselines/reference/objectRest.types index dfce7e47912c0..83013c717f086 100644 --- a/tests/baselines/reference/objectRest.types +++ b/tests/baselines/reference/objectRest.types @@ -206,7 +206,7 @@ var { [computed]: stillNotGreat, [computed2]: soSo, ...o } = o; ({ [computed]: stillNotGreat, [computed2]: soSo, ...o } = o); >({ [computed]: stillNotGreat, [computed2]: soSo, ...o } = o) : { a: number; b: string; } >{ [computed]: stillNotGreat, [computed2]: soSo, ...o } = o : { a: number; b: string; } ->{ [computed]: stillNotGreat, [computed2]: soSo, ...o } : { a: number; b: string; } +>{ [computed]: stillNotGreat, [computed2]: soSo, ...o } : { [x: string]: any; a: number; b: string; } >[computed] : any >computed : string >stillNotGreat : any diff --git a/tests/baselines/reference/objectSpreadComputedProperty.types b/tests/baselines/reference/objectSpreadComputedProperty.types index d1cb2da117e3f..d780c04405465 100644 --- a/tests/baselines/reference/objectSpreadComputedProperty.types +++ b/tests/baselines/reference/objectSpreadComputedProperty.types @@ -16,24 +16,24 @@ function f() { >null : null const o1 = { ...{}, [n]: n }; ->o1 : {} ->{ ...{}, [n]: n } : {} +>o1 : { [x: number]: number; } +>{ ...{}, [n]: n } : { [x: number]: number; } >{} : {} >[n] : number >n : number >n : number const o2 = { ...{}, [a]: n }; ->o2 : {} ->{ ...{}, [a]: n } : {} +>o2 : { [x: number]: number; } +>{ ...{}, [a]: n } : { [x: number]: number; } >{} : {} >[a] : number >a : any >n : number const o3 = { [a]: n, ...{}, [n]: n, ...{}, [m]: m }; ->o3 : {} ->{ [a]: n, ...{}, [n]: n, ...{}, [m]: m } : {} +>o3 : { [x: number]: number; } +>{ [a]: n, ...{}, [n]: n, ...{}, [m]: m } : { [x: number]: number; } >[a] : number >a : any >n : number diff --git a/tests/baselines/reference/objectSpreadIndexSignature.errors.txt b/tests/baselines/reference/objectSpreadIndexSignature.errors.txt deleted file mode 100644 index dede126d063fe..0000000000000 --- a/tests/baselines/reference/objectSpreadIndexSignature.errors.txt +++ /dev/null @@ -1,23 +0,0 @@ -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 }; - - declare var roindex: { readonly [x:string]: number }; - var writable = { ...roindex }; - writable.a = 0; // should be ok. - \ No newline at end of file diff --git a/tests/baselines/reference/objectSpreadIndexSignature.js b/tests/baselines/reference/objectSpreadIndexSignature.js index aef78611f38fe..b4e1c0fc8eb7c 100644 --- a/tests/baselines/reference/objectSpreadIndexSignature.js +++ b/tests/baselines/reference/objectSpreadIndexSignature.js @@ -2,6 +2,7 @@ declare let indexed1: { [n: string]: number; a: number; }; declare let indexed2: { [n: string]: boolean; c: boolean; }; declare let indexed3: { [n: string]: number }; +declare let indexed4: { [n: number]: number }; let i = { ...indexed1, b: 11 }; // only indexed has indexer, so i[101]: any i[101]; @@ -15,7 +16,24 @@ indexed3 = { ...b ? indexed3 : undefined }; declare var roindex: { readonly [x:string]: number }; var writable = { ...roindex }; writable.a = 0; // should be ok. - + +var mixed = { foo: true, ...indexed3, bar: 'bar' }; +mixed.foo; // boolean +mixed.bar; // string +mixed.baz; // string | number | boolean + +var mixed2 = { foo: true, ...indexed4, bar: 'bar' }; +mixed2.foo; // boolean +mixed2.bar; // string +mixed2[1]; // number + +var mixed3 = { ...indexed4, '1': 'foo'}; +mixed3[0]; // string | number +mixed3[1]; // string + +var mixed4 = { ...indexed3, '1': 'foo'}; +mixed4.foo; // string | number +mixed4[1]; // string //// [objectSpreadIndexSignature.js] "use strict"; @@ -39,3 +57,17 @@ ii[1001]; indexed3 = __assign({}, b ? indexed3 : undefined); var writable = __assign({}, roindex); writable.a = 0; // should be ok. +var mixed = __assign({ foo: true }, indexed3, { bar: 'bar' }); +mixed.foo; // boolean +mixed.bar; // string +mixed.baz; // string | number | boolean +var mixed2 = __assign({ foo: true }, indexed4, { bar: 'bar' }); +mixed2.foo; // boolean +mixed2.bar; // string +mixed2[1]; // number +var mixed3 = __assign({}, indexed4, { '1': 'foo' }); +mixed3[0]; // string | number +mixed3[1]; // string +var mixed4 = __assign({}, indexed3, { '1': 'foo' }); +mixed4.foo; // string | number +mixed4[1]; // string diff --git a/tests/baselines/reference/objectSpreadIndexSignature.symbols b/tests/baselines/reference/objectSpreadIndexSignature.symbols index 439463c41316a..79f4656493eea 100644 --- a/tests/baselines/reference/objectSpreadIndexSignature.symbols +++ b/tests/baselines/reference/objectSpreadIndexSignature.symbols @@ -13,41 +13,107 @@ declare let indexed3: { [n: string]: number }; >indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) >n : Symbol(n, Decl(objectSpreadIndexSignature.ts, 2, 25)) +declare let indexed4: { [n: number]: number }; +>indexed4 : Symbol(indexed4, Decl(objectSpreadIndexSignature.ts, 3, 11)) +>n : Symbol(n, Decl(objectSpreadIndexSignature.ts, 3, 25)) + let i = { ...indexed1, b: 11 }; ->i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 3, 3)) +>i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 4, 3)) >indexed1 : Symbol(indexed1, Decl(objectSpreadIndexSignature.ts, 0, 11)) ->b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 3, 22)) +>b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 4, 22)) // only indexed has indexer, so i[101]: any i[101]; ->i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 3, 3)) +>i : Symbol(i, Decl(objectSpreadIndexSignature.ts, 4, 3)) let ii = { ...indexed1, ...indexed2 }; ->ii : Symbol(ii, Decl(objectSpreadIndexSignature.ts, 6, 3)) +>ii : Symbol(ii, Decl(objectSpreadIndexSignature.ts, 7, 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, 6, 3)) +>ii : Symbol(ii, Decl(objectSpreadIndexSignature.ts, 7, 3)) declare const b: boolean; ->b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 10, 13)) +>b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 11, 13)) indexed3 = { ...b ? indexed3 : undefined }; >indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) ->b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 10, 13)) +>b : Symbol(b, Decl(objectSpreadIndexSignature.ts, 11, 13)) >indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) >undefined : Symbol(undefined) declare var roindex: { readonly [x:string]: number }; ->roindex : Symbol(roindex, Decl(objectSpreadIndexSignature.ts, 13, 11)) ->x : Symbol(x, Decl(objectSpreadIndexSignature.ts, 13, 33)) +>roindex : Symbol(roindex, Decl(objectSpreadIndexSignature.ts, 14, 11)) +>x : Symbol(x, Decl(objectSpreadIndexSignature.ts, 14, 33)) var writable = { ...roindex }; ->writable : Symbol(writable, Decl(objectSpreadIndexSignature.ts, 14, 3)) ->roindex : Symbol(roindex, Decl(objectSpreadIndexSignature.ts, 13, 11)) +>writable : Symbol(writable, Decl(objectSpreadIndexSignature.ts, 15, 3)) +>roindex : Symbol(roindex, Decl(objectSpreadIndexSignature.ts, 14, 11)) writable.a = 0; // should be ok. ->writable : Symbol(writable, Decl(objectSpreadIndexSignature.ts, 14, 3)) +>writable : Symbol(writable, Decl(objectSpreadIndexSignature.ts, 15, 3)) + +var mixed = { foo: true, ...indexed3, bar: 'bar' }; +>mixed : Symbol(mixed, Decl(objectSpreadIndexSignature.ts, 18, 3)) +>foo : Symbol(foo, Decl(objectSpreadIndexSignature.ts, 18, 13)) +>indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) +>bar : Symbol(bar, Decl(objectSpreadIndexSignature.ts, 18, 37)) + +mixed.foo; // boolean +>mixed.foo : Symbol(foo, Decl(objectSpreadIndexSignature.ts, 18, 13)) +>mixed : Symbol(mixed, Decl(objectSpreadIndexSignature.ts, 18, 3)) +>foo : Symbol(foo, Decl(objectSpreadIndexSignature.ts, 18, 13)) + +mixed.bar; // string +>mixed.bar : Symbol(bar, Decl(objectSpreadIndexSignature.ts, 18, 37)) +>mixed : Symbol(mixed, Decl(objectSpreadIndexSignature.ts, 18, 3)) +>bar : Symbol(bar, Decl(objectSpreadIndexSignature.ts, 18, 37)) + +mixed.baz; // string | number | boolean +>mixed : Symbol(mixed, Decl(objectSpreadIndexSignature.ts, 18, 3)) + +var mixed2 = { foo: true, ...indexed4, bar: 'bar' }; +>mixed2 : Symbol(mixed2, Decl(objectSpreadIndexSignature.ts, 23, 3)) +>foo : Symbol(foo, Decl(objectSpreadIndexSignature.ts, 23, 14)) +>indexed4 : Symbol(indexed4, Decl(objectSpreadIndexSignature.ts, 3, 11)) +>bar : Symbol(bar, Decl(objectSpreadIndexSignature.ts, 23, 38)) + +mixed2.foo; // boolean +>mixed2.foo : Symbol(foo, Decl(objectSpreadIndexSignature.ts, 23, 14)) +>mixed2 : Symbol(mixed2, Decl(objectSpreadIndexSignature.ts, 23, 3)) +>foo : Symbol(foo, Decl(objectSpreadIndexSignature.ts, 23, 14)) + +mixed2.bar; // string +>mixed2.bar : Symbol(bar, Decl(objectSpreadIndexSignature.ts, 23, 38)) +>mixed2 : Symbol(mixed2, Decl(objectSpreadIndexSignature.ts, 23, 3)) +>bar : Symbol(bar, Decl(objectSpreadIndexSignature.ts, 23, 38)) + +mixed2[1]; // number +>mixed2 : Symbol(mixed2, Decl(objectSpreadIndexSignature.ts, 23, 3)) + +var mixed3 = { ...indexed4, '1': 'foo'}; +>mixed3 : Symbol(mixed3, Decl(objectSpreadIndexSignature.ts, 28, 3)) +>indexed4 : Symbol(indexed4, Decl(objectSpreadIndexSignature.ts, 3, 11)) +>'1' : Symbol('1', Decl(objectSpreadIndexSignature.ts, 28, 27)) + +mixed3[0]; // string | number +>mixed3 : Symbol(mixed3, Decl(objectSpreadIndexSignature.ts, 28, 3)) + +mixed3[1]; // string +>mixed3 : Symbol(mixed3, Decl(objectSpreadIndexSignature.ts, 28, 3)) +>1 : Symbol('1', Decl(objectSpreadIndexSignature.ts, 28, 27)) + +var mixed4 = { ...indexed3, '1': 'foo'}; +>mixed4 : Symbol(mixed4, Decl(objectSpreadIndexSignature.ts, 32, 3)) +>indexed3 : Symbol(indexed3, Decl(objectSpreadIndexSignature.ts, 2, 11)) +>'1' : Symbol('1', Decl(objectSpreadIndexSignature.ts, 32, 27)) + +mixed4.foo; // string | number +>mixed4 : Symbol(mixed4, Decl(objectSpreadIndexSignature.ts, 32, 3)) + +mixed4[1]; // string +>mixed4 : Symbol(mixed4, Decl(objectSpreadIndexSignature.ts, 32, 3)) +>1 : Symbol('1', Decl(objectSpreadIndexSignature.ts, 32, 27)) diff --git a/tests/baselines/reference/objectSpreadIndexSignature.types b/tests/baselines/reference/objectSpreadIndexSignature.types index 3ce4d00584ba9..93ee9d0de7a7d 100644 --- a/tests/baselines/reference/objectSpreadIndexSignature.types +++ b/tests/baselines/reference/objectSpreadIndexSignature.types @@ -13,17 +13,21 @@ declare let indexed3: { [n: string]: number }; >indexed3 : { [n: string]: number; } >n : string +declare let indexed4: { [n: number]: number }; +>indexed4 : { [n: number]: number; } +>n : number + let i = { ...indexed1, b: 11 }; ->i : { b: number; a: number; } ->{ ...indexed1, b: 11 } : { b: number; a: number; } +>i : { [x: string]: number; b: number; a: number; } +>{ ...indexed1, b: 11 } : { [x: string]: number; b: number; a: number; } >indexed1 : { [n: string]: number; a: number; } >b : number >11 : 11 // only indexed has indexer, so i[101]: any i[101]; ->i[101] : any ->i : { b: number; a: number; } +>i[101] : number +>i : { [x: string]: number; b: number; a: number; } >101 : 101 let ii = { ...indexed1, ...indexed2 }; @@ -42,9 +46,9 @@ declare const b: boolean; >b : boolean indexed3 = { ...b ? indexed3 : undefined }; ->indexed3 = { ...b ? indexed3 : undefined } : {} | { [n: string]: number; } +>indexed3 = { ...b ? indexed3 : undefined } : {} | { [x: string]: number; } >indexed3 : { [n: string]: number; } ->{ ...b ? indexed3 : undefined } : {} | { [n: string]: number; } +>{ ...b ? indexed3 : undefined } : {} | { [x: string]: number; } >b ? indexed3 : undefined : { [n: string]: number; } | undefined >b : boolean >indexed3 : { [n: string]: number; } @@ -66,3 +70,85 @@ writable.a = 0; // should be ok. >a : number >0 : 0 +var mixed = { foo: true, ...indexed3, bar: 'bar' }; +>mixed : { [x: string]: string | number | boolean; bar: string; foo: boolean; } +>{ foo: true, ...indexed3, bar: 'bar' } : { [x: string]: string | number | boolean; bar: string; foo: boolean; } +>foo : boolean +>true : true +>indexed3 : { [n: string]: number; } +>bar : string +>'bar' : "bar" + +mixed.foo; // boolean +>mixed.foo : boolean +>mixed : { [x: string]: string | number | boolean; bar: string; foo: boolean; } +>foo : boolean + +mixed.bar; // string +>mixed.bar : string +>mixed : { [x: string]: string | number | boolean; bar: string; foo: boolean; } +>bar : string + +mixed.baz; // string | number | boolean +>mixed.baz : string | number | boolean +>mixed : { [x: string]: string | number | boolean; bar: string; foo: boolean; } +>baz : string | number | boolean + +var mixed2 = { foo: true, ...indexed4, bar: 'bar' }; +>mixed2 : { [x: number]: number; bar: string; foo: boolean; } +>{ foo: true, ...indexed4, bar: 'bar' } : { [x: number]: number; bar: string; foo: boolean; } +>foo : boolean +>true : true +>indexed4 : { [n: number]: number; } +>bar : string +>'bar' : "bar" + +mixed2.foo; // boolean +>mixed2.foo : boolean +>mixed2 : { [x: number]: number; bar: string; foo: boolean; } +>foo : boolean + +mixed2.bar; // string +>mixed2.bar : string +>mixed2 : { [x: number]: number; bar: string; foo: boolean; } +>bar : string + +mixed2[1]; // number +>mixed2[1] : number +>mixed2 : { [x: number]: number; bar: string; foo: boolean; } +>1 : 1 + +var mixed3 = { ...indexed4, '1': 'foo'}; +>mixed3 : { [x: number]: string | number; '1': string; } +>{ ...indexed4, '1': 'foo'} : { [x: number]: string | number; '1': string; } +>indexed4 : { [n: number]: number; } +>'1' : string +>'foo' : "foo" + +mixed3[0]; // string | number +>mixed3[0] : string | number +>mixed3 : { [x: number]: string | number; '1': string; } +>0 : 0 + +mixed3[1]; // string +>mixed3[1] : string +>mixed3 : { [x: number]: string | number; '1': string; } +>1 : 1 + +var mixed4 = { ...indexed3, '1': 'foo'}; +>mixed4 : { [x: string]: string | number; '1': string; } +>{ ...indexed3, '1': 'foo'} : { [x: string]: string | number; '1': string; } +>indexed3 : { [n: string]: number; } +>'1' : string +>'foo' : "foo" + +mixed4.foo; // string | number +>mixed4.foo : string | number +>mixed4 : { [x: string]: string | number; '1': string; } +>foo : string | number + +mixed4[1]; // string +>mixed4[1] : string +>mixed4 : { [x: string]: string | number; '1': string; } +>1 : 1 + diff --git a/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts b/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts index 13ddc4f71d36d..0b995a4802a61 100644 --- a/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts +++ b/tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts @@ -2,6 +2,7 @@ declare let indexed1: { [n: string]: number; a: number; }; declare let indexed2: { [n: string]: boolean; c: boolean; }; declare let indexed3: { [n: string]: number }; +declare let indexed4: { [n: number]: number }; let i = { ...indexed1, b: 11 }; // only indexed has indexer, so i[101]: any i[101]; @@ -15,3 +16,21 @@ indexed3 = { ...b ? indexed3 : undefined }; declare var roindex: { readonly [x:string]: number }; var writable = { ...roindex }; writable.a = 0; // should be ok. + +var mixed = { foo: true, ...indexed3, bar: 'bar' }; +mixed.foo; // boolean +mixed.bar; // string +mixed.baz; // string | number | boolean + +var mixed2 = { foo: true, ...indexed4, bar: 'bar' }; +mixed2.foo; // boolean +mixed2.bar; // string +mixed2[1]; // number + +var mixed3 = { ...indexed4, '1': 'foo'}; +mixed3[0]; // string | number +mixed3[1]; // string + +var mixed4 = { ...indexed3, '1': 'foo'}; +mixed4.foo; // string | number +mixed4[1]; // string \ No newline at end of file