diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c9eb0176c45e0..10cfe40a70ecb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5938,6 +5938,12 @@ namespace ts { && isTypeUsableAsLateBoundName(checkComputedPropertyName(node)); } + function isLateBoundName(name: __String): boolean { + return (name as string).charCodeAt(0) === CharacterCodes._ && + (name as string).charCodeAt(1) === CharacterCodes._ && + (name as string).charCodeAt(2) === CharacterCodes.at; + } + /** * Indicates whether a declaration has a late-bindable dynamic name. */ @@ -7024,6 +7030,7 @@ namespace ts { function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined { let props: Symbol[] | undefined; + let indexTypes: Type[] | undefined; const isUnion = containingType.flags & TypeFlags.Union; const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; // Flags we want to propagate to the result if they exist in all source symbols @@ -7048,14 +7055,21 @@ namespace ts { } } else if (isUnion) { - checkFlags |= CheckFlags.Partial; + const index = !isLateBoundName(name) && ((isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number)) || getIndexInfoOfType(type, IndexKind.String)); + if (index) { + checkFlags |= index.isReadonly ? CheckFlags.Readonly : 0; + indexTypes = append(indexTypes, index.type); + } + else { + checkFlags |= CheckFlags.Partial; + } } } } if (!props) { return undefined; } - if (props.length === 1 && !(checkFlags & CheckFlags.Partial)) { + if (props.length === 1 && !(checkFlags & CheckFlags.Partial) && !indexTypes) { return props[0]; } let declarations: Declaration[] | undefined; @@ -7086,6 +7100,7 @@ namespace ts { } propTypes.push(type); } + addRange(propTypes, indexTypes); const result = createSymbol(SymbolFlags.Property | commonFlags, name, syntheticFlag | checkFlags); result.containingType = containingType; if (!hasNonUniformValueDeclaration && commonValueDeclaration) { diff --git a/tests/baselines/reference/typedefCrossModule2.symbols b/tests/baselines/reference/typedefCrossModule2.symbols index 79fbb1ab509a2..b2143e14aa4fb 100644 --- a/tests/baselines/reference/typedefCrossModule2.symbols +++ b/tests/baselines/reference/typedefCrossModule2.symbols @@ -14,7 +14,9 @@ var bb; var bbb = new mod.Baz(); >bbb : Symbol(bbb, Decl(use.js, 5, 3)) +>mod.Baz : Symbol(Baz) >mod : Symbol(mod, Decl(use.js, 0, 3)) +>Baz : Symbol(Baz) === tests/cases/conformance/jsdoc/mod1.js === // error diff --git a/tests/baselines/reference/unionTypeWithIndexSignature.errors.txt b/tests/baselines/reference/unionTypeWithIndexSignature.errors.txt new file mode 100644 index 0000000000000..e95a184d15a5b --- /dev/null +++ b/tests/baselines/reference/unionTypeWithIndexSignature.errors.txt @@ -0,0 +1,47 @@ +tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(11,3): error TS2339: Property 'bar' does not exist on type 'Missing'. + Property 'bar' does not exist on type '{ [s: string]: string; }'. +tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(14,4): error TS2540: Cannot assign to 'foo' because it is a constant or a read-only property. +tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(24,1): error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. +tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(25,1): error TS2322: Type '"not ok"' is not assignable to type 'number'. +tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(26,1): error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. + + +==== tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts (5 errors) ==== + type Two = { foo: { bar: true }, baz: true } | { [s: string]: string }; + declare var u: Two + u.foo = 'bye' + u.baz = 'hi' + type Three = { foo: number } | { [s: string]: string } | { [s: string]: boolean }; + declare var v: Three + v.foo = false + type Missing = { foo: number, bar: true } | { [s: string]: string } | { foo: boolean } + declare var m: Missing + m.foo = 'hi' + m.bar + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'Missing'. +!!! error TS2339: Property 'bar' does not exist on type '{ [s: string]: string; }'. + type RO = { foo: number } | { readonly [s: string]: string } + declare var ro: RO + ro.foo = 'not allowed' + ~~~ +!!! error TS2540: Cannot assign to 'foo' because it is a constant or a read-only property. + type Num = { '0': string } | { [n: number]: number } + declare var num: Num + num[0] = 1 + num['0'] = 'ok' + const sym = Symbol() + type Both = { s: number, '0': number, [sym]: boolean } | { [n: number]: number, [s: string]: string | number } + declare var both: Both + both['s'] = 'ok' + both[0] = 1 + both[1] = 0 // not ok + ~~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. + both[0] = 'not ok' + ~~~~~~~ +!!! error TS2322: Type '"not ok"' is not assignable to type 'number'. + both[sym] = 'not ok' + ~~~~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. + \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeWithIndexSignature.js b/tests/baselines/reference/unionTypeWithIndexSignature.js new file mode 100644 index 0000000000000..53cf98c612000 --- /dev/null +++ b/tests/baselines/reference/unionTypeWithIndexSignature.js @@ -0,0 +1,45 @@ +//// [unionTypeWithIndexSignature.ts] +type Two = { foo: { bar: true }, baz: true } | { [s: string]: string }; +declare var u: Two +u.foo = 'bye' +u.baz = 'hi' +type Three = { foo: number } | { [s: string]: string } | { [s: string]: boolean }; +declare var v: Three +v.foo = false +type Missing = { foo: number, bar: true } | { [s: string]: string } | { foo: boolean } +declare var m: Missing +m.foo = 'hi' +m.bar +type RO = { foo: number } | { readonly [s: string]: string } +declare var ro: RO +ro.foo = 'not allowed' +type Num = { '0': string } | { [n: number]: number } +declare var num: Num +num[0] = 1 +num['0'] = 'ok' +const sym = Symbol() +type Both = { s: number, '0': number, [sym]: boolean } | { [n: number]: number, [s: string]: string | number } +declare var both: Both +both['s'] = 'ok' +both[0] = 1 +both[1] = 0 // not ok +both[0] = 'not ok' +both[sym] = 'not ok' + + +//// [unionTypeWithIndexSignature.js] +"use strict"; +u.foo = 'bye'; +u.baz = 'hi'; +v.foo = false; +m.foo = 'hi'; +m.bar; +ro.foo = 'not allowed'; +num[0] = 1; +num['0'] = 'ok'; +const sym = Symbol(); +both['s'] = 'ok'; +both[0] = 1; +both[1] = 0; // not ok +both[0] = 'not ok'; +both[sym] = 'not ok'; diff --git a/tests/baselines/reference/unionTypeWithIndexSignature.symbols b/tests/baselines/reference/unionTypeWithIndexSignature.symbols new file mode 100644 index 0000000000000..b207423057b8b --- /dev/null +++ b/tests/baselines/reference/unionTypeWithIndexSignature.symbols @@ -0,0 +1,123 @@ +=== tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts === +type Two = { foo: { bar: true }, baz: true } | { [s: string]: string }; +>Two : Symbol(Two, Decl(unionTypeWithIndexSignature.ts, 0, 0)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 0, 12)) +>bar : Symbol(bar, Decl(unionTypeWithIndexSignature.ts, 0, 19)) +>baz : Symbol(baz, Decl(unionTypeWithIndexSignature.ts, 0, 32)) +>s : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 0, 50)) + +declare var u: Two +>u : Symbol(u, Decl(unionTypeWithIndexSignature.ts, 1, 11)) +>Two : Symbol(Two, Decl(unionTypeWithIndexSignature.ts, 0, 0)) + +u.foo = 'bye' +>u.foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 0, 12)) +>u : Symbol(u, Decl(unionTypeWithIndexSignature.ts, 1, 11)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 0, 12)) + +u.baz = 'hi' +>u.baz : Symbol(baz, Decl(unionTypeWithIndexSignature.ts, 0, 32)) +>u : Symbol(u, Decl(unionTypeWithIndexSignature.ts, 1, 11)) +>baz : Symbol(baz, Decl(unionTypeWithIndexSignature.ts, 0, 32)) + +type Three = { foo: number } | { [s: string]: string } | { [s: string]: boolean }; +>Three : Symbol(Three, Decl(unionTypeWithIndexSignature.ts, 3, 12)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 4, 14)) +>s : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 4, 34)) +>s : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 4, 60)) + +declare var v: Three +>v : Symbol(v, Decl(unionTypeWithIndexSignature.ts, 5, 11)) +>Three : Symbol(Three, Decl(unionTypeWithIndexSignature.ts, 3, 12)) + +v.foo = false +>v.foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 4, 14)) +>v : Symbol(v, Decl(unionTypeWithIndexSignature.ts, 5, 11)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 4, 14)) + +type Missing = { foo: number, bar: true } | { [s: string]: string } | { foo: boolean } +>Missing : Symbol(Missing, Decl(unionTypeWithIndexSignature.ts, 6, 13)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 7, 16)) +>bar : Symbol(bar, Decl(unionTypeWithIndexSignature.ts, 7, 29)) +>s : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 7, 47)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 7, 71)) + +declare var m: Missing +>m : Symbol(m, Decl(unionTypeWithIndexSignature.ts, 8, 11)) +>Missing : Symbol(Missing, Decl(unionTypeWithIndexSignature.ts, 6, 13)) + +m.foo = 'hi' +>m.foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 7, 16), Decl(unionTypeWithIndexSignature.ts, 7, 71)) +>m : Symbol(m, Decl(unionTypeWithIndexSignature.ts, 8, 11)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 7, 16), Decl(unionTypeWithIndexSignature.ts, 7, 71)) + +m.bar +>m : Symbol(m, Decl(unionTypeWithIndexSignature.ts, 8, 11)) + +type RO = { foo: number } | { readonly [s: string]: string } +>RO : Symbol(RO, Decl(unionTypeWithIndexSignature.ts, 10, 5)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 11, 11)) +>s : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 11, 40)) + +declare var ro: RO +>ro : Symbol(ro, Decl(unionTypeWithIndexSignature.ts, 12, 11)) +>RO : Symbol(RO, Decl(unionTypeWithIndexSignature.ts, 10, 5)) + +ro.foo = 'not allowed' +>ro.foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 11, 11)) +>ro : Symbol(ro, Decl(unionTypeWithIndexSignature.ts, 12, 11)) +>foo : Symbol(foo, Decl(unionTypeWithIndexSignature.ts, 11, 11)) + +type Num = { '0': string } | { [n: number]: number } +>Num : Symbol(Num, Decl(unionTypeWithIndexSignature.ts, 13, 22)) +>'0' : Symbol('0', Decl(unionTypeWithIndexSignature.ts, 14, 12)) +>n : Symbol(n, Decl(unionTypeWithIndexSignature.ts, 14, 32)) + +declare var num: Num +>num : Symbol(num, Decl(unionTypeWithIndexSignature.ts, 15, 11)) +>Num : Symbol(Num, Decl(unionTypeWithIndexSignature.ts, 13, 22)) + +num[0] = 1 +>num : Symbol(num, Decl(unionTypeWithIndexSignature.ts, 15, 11)) +>0 : Symbol('0', Decl(unionTypeWithIndexSignature.ts, 14, 12)) + +num['0'] = 'ok' +>num : Symbol(num, Decl(unionTypeWithIndexSignature.ts, 15, 11)) +>'0' : Symbol('0', Decl(unionTypeWithIndexSignature.ts, 14, 12)) + +const sym = Symbol() +>sym : Symbol(sym, Decl(unionTypeWithIndexSignature.ts, 18, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.symbol.d.ts, --, --)) + +type Both = { s: number, '0': number, [sym]: boolean } | { [n: number]: number, [s: string]: string | number } +>Both : Symbol(Both, Decl(unionTypeWithIndexSignature.ts, 18, 20)) +>s : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 19, 13)) +>'0' : Symbol('0', Decl(unionTypeWithIndexSignature.ts, 19, 24)) +>[sym] : Symbol([sym], Decl(unionTypeWithIndexSignature.ts, 19, 37)) +>sym : Symbol(sym, Decl(unionTypeWithIndexSignature.ts, 18, 5)) +>n : Symbol(n, Decl(unionTypeWithIndexSignature.ts, 19, 60)) +>s : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 19, 81)) + +declare var both: Both +>both : Symbol(both, Decl(unionTypeWithIndexSignature.ts, 20, 11)) +>Both : Symbol(Both, Decl(unionTypeWithIndexSignature.ts, 18, 20)) + +both['s'] = 'ok' +>both : Symbol(both, Decl(unionTypeWithIndexSignature.ts, 20, 11)) +>'s' : Symbol(s, Decl(unionTypeWithIndexSignature.ts, 19, 13)) + +both[0] = 1 +>both : Symbol(both, Decl(unionTypeWithIndexSignature.ts, 20, 11)) +>0 : Symbol('0', Decl(unionTypeWithIndexSignature.ts, 19, 24)) + +both[1] = 0 // not ok +>both : Symbol(both, Decl(unionTypeWithIndexSignature.ts, 20, 11)) + +both[0] = 'not ok' +>both : Symbol(both, Decl(unionTypeWithIndexSignature.ts, 20, 11)) +>0 : Symbol('0', Decl(unionTypeWithIndexSignature.ts, 19, 24)) + +both[sym] = 'not ok' +>both : Symbol(both, Decl(unionTypeWithIndexSignature.ts, 20, 11)) +>sym : Symbol(sym, Decl(unionTypeWithIndexSignature.ts, 18, 5)) + diff --git a/tests/baselines/reference/unionTypeWithIndexSignature.types b/tests/baselines/reference/unionTypeWithIndexSignature.types new file mode 100644 index 0000000000000..b8d12eb63d90c --- /dev/null +++ b/tests/baselines/reference/unionTypeWithIndexSignature.types @@ -0,0 +1,161 @@ +=== tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts === +type Two = { foo: { bar: true }, baz: true } | { [s: string]: string }; +>Two : Two +>foo : { bar: true; } +>bar : true +>true : true +>baz : true +>true : true +>s : string + +declare var u: Two +>u : Two +>Two : Two + +u.foo = 'bye' +>u.foo = 'bye' : "bye" +>u.foo : string | { bar: true; } +>u : Two +>foo : string | { bar: true; } +>'bye' : "bye" + +u.baz = 'hi' +>u.baz = 'hi' : "hi" +>u.baz : string | true +>u : Two +>baz : string | true +>'hi' : "hi" + +type Three = { foo: number } | { [s: string]: string } | { [s: string]: boolean }; +>Three : Three +>foo : number +>s : string +>s : string + +declare var v: Three +>v : Three +>Three : Three + +v.foo = false +>v.foo = false : false +>v.foo : string | number | boolean +>v : Three +>foo : string | number | boolean +>false : false + +type Missing = { foo: number, bar: true } | { [s: string]: string } | { foo: boolean } +>Missing : Missing +>foo : number +>bar : true +>true : true +>s : string +>foo : boolean + +declare var m: Missing +>m : Missing +>Missing : Missing + +m.foo = 'hi' +>m.foo = 'hi' : "hi" +>m.foo : string | number | boolean +>m : Missing +>foo : string | number | boolean +>'hi' : "hi" + +m.bar +>m.bar : any +>m : Missing +>bar : any + +type RO = { foo: number } | { readonly [s: string]: string } +>RO : RO +>foo : number +>s : string + +declare var ro: RO +>ro : RO +>RO : RO + +ro.foo = 'not allowed' +>ro.foo = 'not allowed' : "not allowed" +>ro.foo : any +>ro : RO +>foo : any +>'not allowed' : "not allowed" + +type Num = { '0': string } | { [n: number]: number } +>Num : Num +>'0' : string +>n : number + +declare var num: Num +>num : Num +>Num : Num + +num[0] = 1 +>num[0] = 1 : 1 +>num[0] : string | number +>num : Num +>0 : 0 +>1 : 1 + +num['0'] = 'ok' +>num['0'] = 'ok' : "ok" +>num['0'] : string | number +>num : Num +>'0' : "0" +>'ok' : "ok" + +const sym = Symbol() +>sym : unique symbol +>Symbol() : unique symbol +>Symbol : SymbolConstructor + +type Both = { s: number, '0': number, [sym]: boolean } | { [n: number]: number, [s: string]: string | number } +>Both : Both +>s : number +>'0' : number +>[sym] : boolean +>sym : unique symbol +>n : number +>s : string + +declare var both: Both +>both : Both +>Both : Both + +both['s'] = 'ok' +>both['s'] = 'ok' : "ok" +>both['s'] : string | number +>both : Both +>'s' : "s" +>'ok' : "ok" + +both[0] = 1 +>both[0] = 1 : 1 +>both[0] : number +>both : Both +>0 : 0 +>1 : 1 + +both[1] = 0 // not ok +>both[1] = 0 : 0 +>both[1] : any +>both : Both +>1 : 1 +>0 : 0 + +both[0] = 'not ok' +>both[0] = 'not ok' : "not ok" +>both[0] : number +>both : Both +>0 : 0 +>'not ok' : "not ok" + +both[sym] = 'not ok' +>both[sym] = 'not ok' : "not ok" +>both[sym] : any +>both : Both +>sym : unique symbol +>'not ok' : "not ok" + diff --git a/tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts b/tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts new file mode 100644 index 0000000000000..cda563811d7aa --- /dev/null +++ b/tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts @@ -0,0 +1,28 @@ +// @target: esnext +// @strict: true +type Two = { foo: { bar: true }, baz: true } | { [s: string]: string }; +declare var u: Two +u.foo = 'bye' +u.baz = 'hi' +type Three = { foo: number } | { [s: string]: string } | { [s: string]: boolean }; +declare var v: Three +v.foo = false +type Missing = { foo: number, bar: true } | { [s: string]: string } | { foo: boolean } +declare var m: Missing +m.foo = 'hi' +m.bar +type RO = { foo: number } | { readonly [s: string]: string } +declare var ro: RO +ro.foo = 'not allowed' +type Num = { '0': string } | { [n: number]: number } +declare var num: Num +num[0] = 1 +num['0'] = 'ok' +const sym = Symbol() +type Both = { s: number, '0': number, [sym]: boolean } | { [n: number]: number, [s: string]: string | number } +declare var both: Both +both['s'] = 'ok' +both[0] = 1 +both[1] = 0 // not ok +both[0] = 'not ok' +both[sym] = 'not ok'