diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8fa3c4fce2a4a..f0a4e99d61766 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33581,6 +33581,52 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; + + // When a computed property name has a union literal type (e.g., key: 'a' | 'b'), + // lift to a union of object types: { a: V } | { b: V } + // This is sound because at runtime { [key]: value } creates exactly ONE property. + // See: https://github.com/microsoft/TypeScript/issues/13948 + if ( + computedNameType && + (computedNameType.flags & TypeFlags.Union) && + every((computedNameType as UnionType).types, isTypeUsableAsPropertyName) + ) { + // Flush any accumulated properties into the spread + if (propertiesArray.length > 0) { + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); + propertiesArray = []; + propertiesTable = createSymbolTable(); + hasComputedStringProperty = false; + hasComputedNumberProperty = false; + hasComputedSymbolProperty = false; + } + // Create one object type per union member, then union them + const memberTypes: Type[] = []; + for (const literalType of (computedNameType as UnionType).types) { + const propName = getPropertyNameFromType(literalType as StringLiteralType | NumberLiteralType | UniqueESSymbolType); + const prop = createSymbol(SymbolFlags.Property | member.flags, propName, checkFlags | CheckFlags.Late); + prop.links.nameType = literalType; + prop.declarations = member.declarations; + prop.parent = member.parent; + if (member.valueDeclaration) { + prop.valueDeclaration = member.valueDeclaration; + } + prop.links.type = type; + prop.links.target = member; + + const singlePropTable = createSymbolTable(); + singlePropTable.set(propName, prop); + const singleObjType = createAnonymousType(node.symbol, singlePropTable, emptyArray, emptyArray, emptyArray); + singleObjType.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + memberTypes.push(singleObjType); + } + if (memberTypes.length > 0) { + spread = getSpreadType(spread, getUnionType(memberTypes), node.symbol, objectFlags, inConstContext); + } + offset = propertiesArray.length; + continue; + } + const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; const prop = nameType ? createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : diff --git a/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.js b/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.js new file mode 100644 index 0000000000000..68a27a9fae30e --- /dev/null +++ b/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.js @@ -0,0 +1,127 @@ +//// [tests/cases/conformance/es6/computedProperties/computedPropertyUnionLiftsToUnionType.ts] //// + +//// [computedPropertyUnionLiftsToUnionType.ts] +declare var ab: 'a' | 'b'; +declare var cd: 'c' | 'd'; +declare var onetwo: 1 | 2; +enum Alphabet { + Aleph, + Bet, +} +declare var alphabet: Alphabet; + +// Basic: union literal key lifts to union of object types +const x: { a: string } | { b: string } = { [ab]: 'hi' } + +// Multiple unions create cross-product +const y: { a: string, m: number, c: string } + | { a: string, m: number, d: string } + | { b: string, m: number, c: string } + | { b: string, m: number, d: string } = { [ab]: 'hi', m: 1, [cd]: 'there' } + +// Union + spread +const s: { a: string, c: string } | { b: string, c: string } = { [ab]: 'hi', ...{ c: 'no' }} + +// Number literal union +const n: { "1": string } | { "2": string } = { [onetwo]: 'hi' } + +// Enum literal union +const e: { "0": string } | { "1": string } = { [alphabet]: 'hi' } + +// Soundness check: accessing non-existent property should be error +const obj = { [ab]: 1 } +// obj should be { a: number } | { b: number }, not { a: number; b: number } +// So accessing both .a and .b should require a type guard + +// Methods and getters alongside union computed property +const m: { a: string, m(): number, p: number } | { b: string, m(): number, p: number } = + { [ab]: 'hi', m() { return 1 }, get p() { return 2 } } + + +//// [computedPropertyUnionLiftsToUnionType.js] +"use strict"; +var Alphabet; +(function (Alphabet) { + Alphabet[Alphabet["Aleph"] = 0] = "Aleph"; + Alphabet[Alphabet["Bet"] = 1] = "Bet"; +})(Alphabet || (Alphabet = {})); +// Basic: union literal key lifts to union of object types +const x = { [ab]: 'hi' }; +// Multiple unions create cross-product +const y = { [ab]: 'hi', m: 1, [cd]: 'there' }; +// Union + spread +const s = Object.assign({ [ab]: 'hi' }, { c: 'no' }); +// Number literal union +const n = { [onetwo]: 'hi' }; +// Enum literal union +const e = { [alphabet]: 'hi' }; +// Soundness check: accessing non-existent property should be error +const obj = { [ab]: 1 }; +// obj should be { a: number } | { b: number }, not { a: number; b: number } +// So accessing both .a and .b should require a type guard +// Methods and getters alongside union computed property +const m = { [ab]: 'hi', m() { return 1; }, get p() { return 2; } }; + + +//// [computedPropertyUnionLiftsToUnionType.d.ts] +declare var ab: 'a' | 'b'; +declare var cd: 'c' | 'd'; +declare var onetwo: 1 | 2; +declare enum Alphabet { + Aleph = 0, + Bet = 1 +} +declare var alphabet: Alphabet; +declare const x: { + a: string; +} | { + b: string; +}; +declare const y: { + a: string; + m: number; + c: string; +} | { + a: string; + m: number; + d: string; +} | { + b: string; + m: number; + c: string; +} | { + b: string; + m: number; + d: string; +}; +declare const s: { + a: string; + c: string; +} | { + b: string; + c: string; +}; +declare const n: { + "1": string; +} | { + "2": string; +}; +declare const e: { + "0": string; +} | { + "1": string; +}; +declare const obj: { + a: number; +} | { + b: number; +}; +declare const m: { + a: string; + m(): number; + p: number; +} | { + b: string; + m(): number; + p: number; +}; diff --git a/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.symbols b/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.symbols new file mode 100644 index 0000000000000..632c63932d09e --- /dev/null +++ b/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.symbols @@ -0,0 +1,112 @@ +//// [tests/cases/conformance/es6/computedProperties/computedPropertyUnionLiftsToUnionType.ts] //// + +=== computedPropertyUnionLiftsToUnionType.ts === +declare var ab: 'a' | 'b'; +>ab : Symbol(ab, Decl(computedPropertyUnionLiftsToUnionType.ts, 0, 11)) + +declare var cd: 'c' | 'd'; +>cd : Symbol(cd, Decl(computedPropertyUnionLiftsToUnionType.ts, 1, 11)) + +declare var onetwo: 1 | 2; +>onetwo : Symbol(onetwo, Decl(computedPropertyUnionLiftsToUnionType.ts, 2, 11)) + +enum Alphabet { +>Alphabet : Symbol(Alphabet, Decl(computedPropertyUnionLiftsToUnionType.ts, 2, 26)) + + Aleph, +>Aleph : Symbol(Alphabet.Aleph, Decl(computedPropertyUnionLiftsToUnionType.ts, 3, 15)) + + Bet, +>Bet : Symbol(Alphabet.Bet, Decl(computedPropertyUnionLiftsToUnionType.ts, 4, 10)) +} +declare var alphabet: Alphabet; +>alphabet : Symbol(alphabet, Decl(computedPropertyUnionLiftsToUnionType.ts, 7, 11)) +>Alphabet : Symbol(Alphabet, Decl(computedPropertyUnionLiftsToUnionType.ts, 2, 26)) + +// Basic: union literal key lifts to union of object types +const x: { a: string } | { b: string } = { [ab]: 'hi' } +>x : Symbol(x, Decl(computedPropertyUnionLiftsToUnionType.ts, 10, 5)) +>a : Symbol(a, Decl(computedPropertyUnionLiftsToUnionType.ts, 10, 10)) +>b : Symbol(b, Decl(computedPropertyUnionLiftsToUnionType.ts, 10, 26)) +>[ab] : Symbol([ab], Decl(computedPropertyUnionLiftsToUnionType.ts, 10, 42)) +>ab : Symbol(ab, Decl(computedPropertyUnionLiftsToUnionType.ts, 0, 11)) + +// Multiple unions create cross-product +const y: { a: string, m: number, c: string } +>y : Symbol(y, Decl(computedPropertyUnionLiftsToUnionType.ts, 13, 5)) +>a : Symbol(a, Decl(computedPropertyUnionLiftsToUnionType.ts, 13, 10)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 13, 21)) +>c : Symbol(c, Decl(computedPropertyUnionLiftsToUnionType.ts, 13, 32)) + + | { a: string, m: number, d: string } +>a : Symbol(a, Decl(computedPropertyUnionLiftsToUnionType.ts, 14, 7)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 14, 18)) +>d : Symbol(d, Decl(computedPropertyUnionLiftsToUnionType.ts, 14, 29)) + + | { b: string, m: number, c: string } +>b : Symbol(b, Decl(computedPropertyUnionLiftsToUnionType.ts, 15, 7)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 15, 18)) +>c : Symbol(c, Decl(computedPropertyUnionLiftsToUnionType.ts, 15, 29)) + + | { b: string, m: number, d: string } = { [ab]: 'hi', m: 1, [cd]: 'there' } +>b : Symbol(b, Decl(computedPropertyUnionLiftsToUnionType.ts, 16, 7)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 16, 18)) +>d : Symbol(d, Decl(computedPropertyUnionLiftsToUnionType.ts, 16, 29)) +>[ab] : Symbol([ab], Decl(computedPropertyUnionLiftsToUnionType.ts, 16, 45)) +>ab : Symbol(ab, Decl(computedPropertyUnionLiftsToUnionType.ts, 0, 11)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 16, 57)) +>[cd] : Symbol([cd], Decl(computedPropertyUnionLiftsToUnionType.ts, 16, 63)) +>cd : Symbol(cd, Decl(computedPropertyUnionLiftsToUnionType.ts, 1, 11)) + +// Union + spread +const s: { a: string, c: string } | { b: string, c: string } = { [ab]: 'hi', ...{ c: 'no' }} +>s : Symbol(s, Decl(computedPropertyUnionLiftsToUnionType.ts, 19, 5)) +>a : Symbol(a, Decl(computedPropertyUnionLiftsToUnionType.ts, 19, 10)) +>c : Symbol(c, Decl(computedPropertyUnionLiftsToUnionType.ts, 19, 21)) +>b : Symbol(b, Decl(computedPropertyUnionLiftsToUnionType.ts, 19, 37)) +>c : Symbol(c, Decl(computedPropertyUnionLiftsToUnionType.ts, 19, 48)) +>[ab] : Symbol([ab], Decl(computedPropertyUnionLiftsToUnionType.ts, 19, 64)) +>ab : Symbol(ab, Decl(computedPropertyUnionLiftsToUnionType.ts, 0, 11)) +>c : Symbol(c, Decl(computedPropertyUnionLiftsToUnionType.ts, 19, 81)) + +// Number literal union +const n: { "1": string } | { "2": string } = { [onetwo]: 'hi' } +>n : Symbol(n, Decl(computedPropertyUnionLiftsToUnionType.ts, 22, 5)) +>"1" : Symbol("1", Decl(computedPropertyUnionLiftsToUnionType.ts, 22, 10)) +>"2" : Symbol("2", Decl(computedPropertyUnionLiftsToUnionType.ts, 22, 28)) +>[onetwo] : Symbol([onetwo], Decl(computedPropertyUnionLiftsToUnionType.ts, 22, 46)) +>onetwo : Symbol(onetwo, Decl(computedPropertyUnionLiftsToUnionType.ts, 2, 11)) + +// Enum literal union +const e: { "0": string } | { "1": string } = { [alphabet]: 'hi' } +>e : Symbol(e, Decl(computedPropertyUnionLiftsToUnionType.ts, 25, 5)) +>"0" : Symbol("0", Decl(computedPropertyUnionLiftsToUnionType.ts, 25, 10)) +>"1" : Symbol("1", Decl(computedPropertyUnionLiftsToUnionType.ts, 25, 28)) +>[alphabet] : Symbol([alphabet], Decl(computedPropertyUnionLiftsToUnionType.ts, 25, 46)) +>alphabet : Symbol(alphabet, Decl(computedPropertyUnionLiftsToUnionType.ts, 7, 11)) + +// Soundness check: accessing non-existent property should be error +const obj = { [ab]: 1 } +>obj : Symbol(obj, Decl(computedPropertyUnionLiftsToUnionType.ts, 28, 5)) +>[ab] : Symbol([ab], Decl(computedPropertyUnionLiftsToUnionType.ts, 28, 13)) +>ab : Symbol(ab, Decl(computedPropertyUnionLiftsToUnionType.ts, 0, 11)) + +// obj should be { a: number } | { b: number }, not { a: number; b: number } +// So accessing both .a and .b should require a type guard + +// Methods and getters alongside union computed property +const m: { a: string, m(): number, p: number } | { b: string, m(): number, p: number } = +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 33, 5)) +>a : Symbol(a, Decl(computedPropertyUnionLiftsToUnionType.ts, 33, 10)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 33, 21)) +>p : Symbol(p, Decl(computedPropertyUnionLiftsToUnionType.ts, 33, 34)) +>b : Symbol(b, Decl(computedPropertyUnionLiftsToUnionType.ts, 33, 50)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 33, 61)) +>p : Symbol(p, Decl(computedPropertyUnionLiftsToUnionType.ts, 33, 74)) + + { [ab]: 'hi', m() { return 1 }, get p() { return 2 } } +>[ab] : Symbol([ab], Decl(computedPropertyUnionLiftsToUnionType.ts, 34, 5)) +>ab : Symbol(ab, Decl(computedPropertyUnionLiftsToUnionType.ts, 0, 11)) +>m : Symbol(m, Decl(computedPropertyUnionLiftsToUnionType.ts, 34, 17)) +>p : Symbol(p, Decl(computedPropertyUnionLiftsToUnionType.ts, 34, 35)) + diff --git a/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.types b/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.types new file mode 100644 index 0000000000000..abc22bced595e --- /dev/null +++ b/tests/baselines/reference/computedPropertyUnionLiftsToUnionType.types @@ -0,0 +1,213 @@ +//// [tests/cases/conformance/es6/computedProperties/computedPropertyUnionLiftsToUnionType.ts] //// + +=== computedPropertyUnionLiftsToUnionType.ts === +declare var ab: 'a' | 'b'; +>ab : "a" | "b" +> : ^^^^^^^^^ + +declare var cd: 'c' | 'd'; +>cd : "c" | "d" +> : ^^^^^^^^^ + +declare var onetwo: 1 | 2; +>onetwo : 1 | 2 +> : ^^^^^ + +enum Alphabet { +>Alphabet : Alphabet +> : ^^^^^^^^ + + Aleph, +>Aleph : Alphabet.Aleph +> : ^^^^^^^^^^^^^^ + + Bet, +>Bet : Alphabet.Bet +> : ^^^^^^^^^^^^ +} +declare var alphabet: Alphabet; +>alphabet : Alphabet +> : ^^^^^^^^ + +// Basic: union literal key lifts to union of object types +const x: { a: string } | { b: string } = { [ab]: 'hi' } +>x : { a: string; } | { b: string; } +> : ^^^^^ ^^^^^^^^^^^ ^^^ +>a : string +> : ^^^^^^ +>b : string +> : ^^^^^^ +>{ [ab]: 'hi' } : { a: string; } | { b: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[ab] : string +> : ^^^^^^ +>ab : "a" | "b" +> : ^^^^^^^^^ +>'hi' : "hi" +> : ^^^^ + +// Multiple unions create cross-product +const y: { a: string, m: number, c: string } +>y : { a: string; m: number; c: string; } | { a: string; m: number; d: string; } | { b: string; m: number; c: string; } | { b: string; m: number; d: string; } +> : ^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^ +>a : string +> : ^^^^^^ +>m : number +> : ^^^^^^ +>c : string +> : ^^^^^^ + + | { a: string, m: number, d: string } +>a : string +> : ^^^^^^ +>m : number +> : ^^^^^^ +>d : string +> : ^^^^^^ + + | { b: string, m: number, c: string } +>b : string +> : ^^^^^^ +>m : number +> : ^^^^^^ +>c : string +> : ^^^^^^ + + | { b: string, m: number, d: string } = { [ab]: 'hi', m: 1, [cd]: 'there' } +>b : string +> : ^^^^^^ +>m : number +> : ^^^^^^ +>d : string +> : ^^^^^^ +>{ [ab]: 'hi', m: 1, [cd]: 'there' } : { c: string; m: number; a: string; } | { d: string; m: number; a: string; } | { c: string; m: number; b: string; } | { d: string; m: number; b: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[ab] : string +> : ^^^^^^ +>ab : "a" | "b" +> : ^^^^^^^^^ +>'hi' : "hi" +> : ^^^^ +>m : number +> : ^^^^^^ +>1 : 1 +> : ^ +>[cd] : string +> : ^^^^^^ +>cd : "c" | "d" +> : ^^^^^^^^^ +>'there' : "there" +> : ^^^^^^^ + +// Union + spread +const s: { a: string, c: string } | { b: string, c: string } = { [ab]: 'hi', ...{ c: 'no' }} +>s : { a: string; c: string; } | { b: string; c: string; } +> : ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^ +>a : string +> : ^^^^^^ +>c : string +> : ^^^^^^ +>b : string +> : ^^^^^^ +>c : string +> : ^^^^^^ +>{ [ab]: 'hi', ...{ c: 'no' }} : { c: string; a: string; } | { c: string; b: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[ab] : string +> : ^^^^^^ +>ab : "a" | "b" +> : ^^^^^^^^^ +>'hi' : "hi" +> : ^^^^ +>{ c: 'no' } : { c: string; } +> : ^^^^^^^^^^^^^^ +>c : string +> : ^^^^^^ +>'no' : "no" +> : ^^^^ + +// Number literal union +const n: { "1": string } | { "2": string } = { [onetwo]: 'hi' } +>n : { "1": string; } | { "2": string; } +> : ^^^^^^^ ^^^^^^^^^^^^^ ^^^ +>"1" : string +> : ^^^^^^ +>"2" : string +> : ^^^^^^ +>{ [onetwo]: 'hi' } : { 1: string; } | { 2: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[onetwo] : string +> : ^^^^^^ +>onetwo : 1 | 2 +> : ^^^^^ +>'hi' : "hi" +> : ^^^^ + +// Enum literal union +const e: { "0": string } | { "1": string } = { [alphabet]: 'hi' } +>e : { "0": string; } | { "1": string; } +> : ^^^^^^^ ^^^^^^^^^^^^^ ^^^ +>"0" : string +> : ^^^^^^ +>"1" : string +> : ^^^^^^ +>{ [alphabet]: 'hi' } : { 0: string; } | { 1: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[alphabet] : string +> : ^^^^^^ +>alphabet : Alphabet +> : ^^^^^^^^ +>'hi' : "hi" +> : ^^^^ + +// Soundness check: accessing non-existent property should be error +const obj = { [ab]: 1 } +>obj : { a: number; } | { b: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ [ab]: 1 } : { a: number; } | { b: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[ab] : number +> : ^^^^^^ +>ab : "a" | "b" +> : ^^^^^^^^^ +>1 : 1 +> : ^ + +// obj should be { a: number } | { b: number }, not { a: number; b: number } +// So accessing both .a and .b should require a type guard + +// Methods and getters alongside union computed property +const m: { a: string, m(): number, p: number } | { b: string, m(): number, p: number } = +>m : { a: string; m(): number; p: number; } | { b: string; m(): number; p: number; } +> : ^^^^^ ^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^^^ ^^^^^ ^^^ +>a : string +> : ^^^^^^ +>m : () => number +> : ^^^^^^ +>p : number +> : ^^^^^^ +>b : string +> : ^^^^^^ +>m : () => number +> : ^^^^^^ +>p : number +> : ^^^^^^ + + { [ab]: 'hi', m() { return 1 }, get p() { return 2 } } +>{ [ab]: 'hi', m() { return 1 }, get p() { return 2 } } : { m(): number; p: number; a: string; } | { m(): number; p: number; b: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[ab] : string +> : ^^^^^^ +>ab : "a" | "b" +> : ^^^^^^^^^ +>'hi' : "hi" +> : ^^^^ +>m : () => number +> : ^^^^^^^^^^^^ +>1 : 1 +> : ^ +>p : number +> : ^^^^^^ +>2 : 2 +> : ^ + diff --git a/tests/baselines/reference/declarationEmitSimpleComputedNames1.js b/tests/baselines/reference/declarationEmitSimpleComputedNames1.js index 317aaefcdc557..b88b398687a8a 100644 --- a/tests/baselines/reference/declarationEmitSimpleComputedNames1.js +++ b/tests/baselines/reference/declarationEmitSimpleComputedNames1.js @@ -71,7 +71,9 @@ exports.instanceLookup = (new Holder())["some" + "thing"]; //// [declarationEmitSimpleComputedNames1.d.ts] export declare const fieldName: string; export declare const conatainer: { - [fieldName]: () => string; + f1(): string; +} | { + f2(): string; }; declare const classFieldName: string; declare const otherField: string; diff --git a/tests/baselines/reference/declarationEmitSimpleComputedNames1.types b/tests/baselines/reference/declarationEmitSimpleComputedNames1.types index 773301e49e9d7..7928747bde8e4 100644 --- a/tests/baselines/reference/declarationEmitSimpleComputedNames1.types +++ b/tests/baselines/reference/declarationEmitSimpleComputedNames1.types @@ -24,10 +24,10 @@ export const fieldName = Math.random() > 0.5 ? "f1" : "f2"; > : ^^^^ export const conatainer = { ->conatainer : { [fieldName]: () => string; } -> : ^^ ^^^^^^^^^^^^ ^^ ->{ [fieldName]() { return "result"; }} : { [fieldName]: () => string; } -> : ^^ ^^^^^^^^^^^^ ^^ +>conatainer : { f1(): string; } | { f2(): string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ [fieldName]() { return "result"; }} : { f1(): string; } | { f2(): string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [fieldName]() { >[fieldName] : () => string diff --git a/tests/baselines/reference/transpile/declarationComputedPropertyNames.d.ts b/tests/baselines/reference/transpile/declarationComputedPropertyNames.d.ts index 3c81cf66f0e84..f7cab0806f210 100644 --- a/tests/baselines/reference/transpile/declarationComputedPropertyNames.d.ts +++ b/tests/baselines/reference/transpile/declarationComputedPropertyNames.d.ts @@ -93,8 +93,13 @@ export declare class C { ["2"]: number; } export declare const D: { - [x: string]: number; - [x: number]: number; + f1: number; + [presentNs.a]: number; + [aliasing.toStringTag]: number; + 1: number; + "2": number; +} | { + f2: number; [presentNs.a]: number; [aliasing.toStringTag]: number; 1: number; diff --git a/tests/cases/conformance/es6/computedProperties/computedPropertyUnionLiftsToUnionType.ts b/tests/cases/conformance/es6/computedProperties/computedPropertyUnionLiftsToUnionType.ts new file mode 100644 index 0000000000000..40bb1a9a83858 --- /dev/null +++ b/tests/cases/conformance/es6/computedProperties/computedPropertyUnionLiftsToUnionType.ts @@ -0,0 +1,39 @@ +// @strict: true +// @target: es6 +// @declaration: true + +declare var ab: 'a' | 'b'; +declare var cd: 'c' | 'd'; +declare var onetwo: 1 | 2; +enum Alphabet { + Aleph, + Bet, +} +declare var alphabet: Alphabet; + +// Basic: union literal key lifts to union of object types +const x: { a: string } | { b: string } = { [ab]: 'hi' } + +// Multiple unions create cross-product +const y: { a: string, m: number, c: string } + | { a: string, m: number, d: string } + | { b: string, m: number, c: string } + | { b: string, m: number, d: string } = { [ab]: 'hi', m: 1, [cd]: 'there' } + +// Union + spread +const s: { a: string, c: string } | { b: string, c: string } = { [ab]: 'hi', ...{ c: 'no' }} + +// Number literal union +const n: { "1": string } | { "2": string } = { [onetwo]: 'hi' } + +// Enum literal union +const e: { "0": string } | { "1": string } = { [alphabet]: 'hi' } + +// Soundness check: accessing non-existent property should be error +const obj = { [ab]: 1 } +// obj should be { a: number } | { b: number }, not { a: number; b: number } +// So accessing both .a and .b should require a type guard + +// Methods and getters alongside union computed property +const m: { a: string, m(): number, p: number } | { b: string, m(): number, p: number } = + { [ab]: 'hi', m() { return 1 }, get p() { return 2 } }