diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9c72a064b11de..00d73ea390c80 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3119,7 +3119,7 @@ namespace ts { } } if ((symbol as TransientSymbol).syntheticLiteralTypeOrigin) { - const stringValue = (symbol as TransientSymbol).syntheticLiteralTypeOrigin.value; + const stringValue = "" + (symbol as TransientSymbol).syntheticLiteralTypeOrigin.value; if (!isIdentifierText(stringValue, compilerOptions.target)) { return `"${escapeString(stringValue, CharacterCodes.doubleQuote)}"`; } @@ -5722,6 +5722,7 @@ namespace ts { function resolveMappedTypeMembers(type: MappedType) { const members: SymbolTable = createSymbolTable(); let stringIndexInfo: IndexInfo; + let numberIndexInfo: IndexInfo; // Resolve upfront such that recursive references see an empty object type. setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, @@ -5749,7 +5750,7 @@ namespace ts { const iterationType = keyType.flags & TypeFlags.Index ? getIndexType(getApparentType((keyType).type)) : keyType; forEachType(iterationType, addMemberForKeyType); } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); function addMemberForKeyType(t: Type, propertySymbolOrIndex?: Symbol | number) { let propertySymbol: Symbol; @@ -5765,25 +5766,34 @@ namespace ts { const iterationMapper = createTypeMapper([typeParameter], [t]); const templateMapper = type.mapper ? combineTypeMappers(type.mapper, iterationMapper) : iterationMapper; const propType = instantiateType(templateType, templateMapper); - // If the current iteration type constituent is a string literal type, create a property. + // If the current iteration type constituent is a string or number literal type, create a property. // Otherwise, for type string create a string index signature. - if (t.flags & TypeFlags.StringLiteral) { - const propName = escapeLeadingUnderscores((t).value); - const modifiersProp = getPropertyOfType(modifiersType, propName); - const isOptional = templateOptional || !!(modifiersProp && modifiersProp.flags & SymbolFlags.Optional); - const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName); - prop.checkFlags = templateReadonly || modifiersProp && isReadonlySymbol(modifiersProp) ? CheckFlags.Readonly : 0; - prop.type = propType; - if (propertySymbol) { - prop.syntheticOrigin = propertySymbol; - prop.declarations = propertySymbol.declarations; - } - prop.syntheticLiteralTypeOrigin = t as StringLiteralType; - members.set(propName, prop); + if (t.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + const propName = escapeLeadingUnderscores("" + (t).value); + if (members.has(propName)) { + // in case of a conflict between the literal types "1" and 1, for example, do not add either one + members.delete(propName); + } + else { + const modifiersProp = getPropertyOfType(modifiersType, propName); + const isOptional = templateOptional || !!(modifiersProp && modifiersProp.flags & SymbolFlags.Optional); + const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName); + prop.checkFlags = templateReadonly || modifiersProp && isReadonlySymbol(modifiersProp) ? CheckFlags.Readonly : 0; + prop.type = propType; + if (propertySymbol) { + prop.syntheticOrigin = propertySymbol; + prop.declarations = propertySymbol.declarations; + } + prop.syntheticLiteralTypeOrigin = t as StringLiteralType | NumberLiteralType; + members.set(propName, prop); + } } else if (t.flags & TypeFlags.String) { stringIndexInfo = createIndexInfo(propType, templateReadonly); } + else if (t.flags & (TypeFlags.Enum | TypeFlags.Number)) { + numberIndexInfo = createIndexInfo(propType, templateReadonly); + } } } @@ -18856,7 +18866,7 @@ namespace ts { checkSourceElement(node.type); const type = getTypeFromMappedTypeNode(node); const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, stringType, node.typeParameter.constraint); + checkTypeAssignableTo(constraintType, getUnionType([stringType, numberType]), node.typeParameter.constraint); } function isPrivateWithinAmbient(node: Node): boolean { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cfb08a7b05abd..aebc081392158 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3033,7 +3033,7 @@ namespace ts { leftSpread?: Symbol; // Left source for synthetic spread property rightSpread?: Symbol; // Right source for synthetic spread property syntheticOrigin?: Symbol; // For a property on a mapped or spread type, points back to the original property - syntheticLiteralTypeOrigin?: StringLiteralType; // For a property on a mapped type, indicates the type whose text to use as the declaration name, instead of the symbol name + syntheticLiteralTypeOrigin?: StringLiteralType | NumberLiteralType; // For a property on a mapped type, indicates the type whose text to use as the declaration name, instead of the symbol name isDiscriminantProperty?: boolean; // True if discriminant synthetic property resolvedExports?: SymbolTable; // Resolved exports of module exportsChecked?: boolean; // True if exports of external module have been checked @@ -3376,6 +3376,7 @@ namespace ts { } /* @internal */ + /** Syntax maps to property names like so: { [typeParameter in constraintType]: templateType } */ export interface MappedType extends AnonymousType { declaration: MappedTypeNode; typeParameter?: TypeParameter; diff --git a/tests/baselines/reference/mappedTypeErrors.errors.txt b/tests/baselines/reference/mappedTypeErrors.errors.txt index 3245f9932f009..991c7299497bc 100644 --- a/tests/baselines/reference/mappedTypeErrors.errors.txt +++ b/tests/baselines/reference/mappedTypeErrors.errors.txt @@ -1,6 +1,6 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(19,20): error TS2313: Type parameter 'P' has a circular constraint. -tests/cases/conformance/types/mapped/mappedTypeErrors.ts(20,20): error TS2322: Type 'number' is not assignable to type 'string'. -tests/cases/conformance/types/mapped/mappedTypeErrors.ts(21,20): error TS2322: Type 'Date' is not assignable to type 'string'. +tests/cases/conformance/types/mapped/mappedTypeErrors.ts(21,20): error TS2322: Type 'Date' is not assignable to type 'string | number'. + Type 'Date' is not assignable to type 'number'. tests/cases/conformance/types/mapped/mappedTypeErrors.ts(22,19): error TS2344: Type 'Date' does not satisfy the constraint 'string'. tests/cases/conformance/types/mapped/mappedTypeErrors.ts(25,24): error TS2344: Type '"foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'. tests/cases/conformance/types/mapped/mappedTypeErrors.ts(26,24): error TS2344: Type '"name" | "foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'. @@ -45,11 +45,12 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(129,5): error TS2322: T tests/cases/conformance/types/mapped/mappedTypeErrors.ts(130,5): error TS2322: Type '{ a: string; }' is not assignable to type '{ [x: string]: any; a?: number | undefined; }'. Types of property 'a' are incompatible. Type 'string' is not assignable to type 'number | undefined'. -tests/cases/conformance/types/mapped/mappedTypeErrors.ts(136,16): error TS2322: Type 'T' is not assignable to type 'string'. +tests/cases/conformance/types/mapped/mappedTypeErrors.ts(136,16): error TS2322: Type 'T' is not assignable to type 'string | number'. + Type 'T' is not assignable to type 'number'. tests/cases/conformance/types/mapped/mappedTypeErrors.ts(136,21): error TS2536: Type 'P' cannot be used to index type 'T'. -==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (26 errors) ==== +==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (25 errors) ==== interface Shape { name: string; width: number; @@ -72,11 +73,10 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(136,21): error TS2536: ~ !!! error TS2313: Type parameter 'P' has a circular constraint. type T01 = { [P in number]: string }; // Error - ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'string'. type T02 = { [P in Date]: number }; // Error ~~~~ -!!! error TS2322: Type 'Date' is not assignable to type 'string'. +!!! error TS2322: Type 'Date' is not assignable to type 'string | number'. +!!! error TS2322: Type 'Date' is not assignable to type 'number'. type T03 = Record; // Error ~~~~ !!! error TS2344: Type 'Date' does not satisfy the constraint 'string'. @@ -258,7 +258,8 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(136,21): error TS2536: pf: {[P in F]?: T[P]}, pt: {[P in T]?: T[P]}, // note: should be in keyof T ~ -!!! error TS2322: Type 'T' is not assignable to type 'string'. +!!! error TS2322: Type 'T' is not assignable to type 'string | number'. +!!! error TS2322: Type 'T' is not assignable to type 'number'. ~~~~ !!! error TS2536: Type 'P' cannot be used to index type 'T'. }; diff --git a/tests/baselines/reference/mappedTypeNumericEnum.errors.txt b/tests/baselines/reference/mappedTypeNumericEnum.errors.txt new file mode 100644 index 0000000000000..226e24968a784 --- /dev/null +++ b/tests/baselines/reference/mappedTypeNumericEnum.errors.txt @@ -0,0 +1,127 @@ +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(13,35): error TS2322: Type '{ '0': true; '2': boolean; }' is not assignable to type 'NumBool'. + Object literal may only specify known properties, and ''2'' does not exist in type 'NumBool'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(14,5): error TS2322: Type '{ '0': true; }' is not assignable to type 'NumBool'. + Property '"1"' is missing in type '{ '0': true; }'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(17,1): error TS7017: Element implicitly has an 'any' type because type 'NumBool' has no index signature. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(27,1): error TS7017: Element implicitly has an 'any' type because type 'StrAny' has no index signature. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(33,49): error TS2322: Type '{ '0': number; [Nums.B]: number; [Nums2.Gimel]: number; }' is not assignable to type 'NumNum'. + Object literal may only specify known properties, and '[Nums2.Gimel]' does not exist in type 'NumNum'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(41,40): error TS2322: Type '{ [Nums.A]: number; [Nums.B]: number; }' is not assignable to type 'OneNumNum'. + Object literal may only specify known properties, and '[Nums.B]' does not exist in type 'OneNumNum'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(44,1): error TS7017: Element implicitly has an 'any' type because type 'OneNumNum' has no index signature. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(50,6): error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(51,6): error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(63,4): error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(69,5): error TS2322: Type '{ [0]: number; [1]: number; }' is not assignable to type 'MixNum'. + Property 'a' is missing in type '{ [0]: number; [1]: number; }'. +tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts(75,30): error TS2322: Type '{ [0]: number; [1]: number; [2]: number; }' is not assignable to type 'MixConflictNum'. + Object literal may only specify known properties, and '[0]' does not exist in type 'MixConflictNum'. + + +==== tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts (12 errors) ==== + // with numbers + enum Nums { + A, + B + } + enum Nums2 { + Aleph, + Bet, + Gimel + } + type NumBool = { [K in Nums]: boolean } + let nb: NumBool = { '0': true, '1': false } + let wronb: NumBool = { '0': true, '2': false } + ~~~~~~~~~~ +!!! error TS2322: Type '{ '0': true; '2': boolean; }' is not assignable to type 'NumBool'. +!!! error TS2322: Object literal may only specify known properties, and ''2'' does not exist in type 'NumBool'. + let wronb2: NumBool = { '0': true } + ~~~~~~ +!!! error TS2322: Type '{ '0': true; }' is not assignable to type 'NumBool'. +!!! error TS2322: Property '"1"' is missing in type '{ '0': true; }'. + nb[Nums.A] = false; + nb[Nums2.Bet] = true; + nb[Nums2.Gimel] = false; // only disallowed with --strict + ~~~~~~~~~~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type 'NumBool' has no index signature. + + // with strings + enum Strs { + A = 'a', + B = 'b' + } + type StrAny = { [K in Strs]: any } + let sa: StrAny = { a: 1, b: 2 } + sa[Strs.A] = 'a' + sa['nope'] = 'not allowed' + ~~~~~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type 'StrAny' has no index signature. + + // union of numbers + type Ns = 0 | 1; + type NumNum = { [K in Ns]: number } + let nn: NumNum = { [Nums.A]: 3, [Nums.B]: 4 } + let omnomnom: NumNum = { '0': 12, [Nums.B]: 13, [Nums2.Gimel]: 14 } + ~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ '0': number; [Nums.B]: number; [Nums2.Gimel]: number; }' is not assignable to type 'NumNum'. +!!! error TS2322: Object literal may only specify known properties, and '[Nums2.Gimel]' does not exist in type 'NumNum'. + nn[0] = 5 + nn['1'] = 6 + + // single number + type N = 0; + type OneNumNum = { [K in N]: number } + let onn: OneNumNum = { [Nums.A]: 7 } + let wronng: OneNumNum = { [Nums.A]: 7, [Nums.B]: 11 } + ~~~~~~~~~~~~ +!!! error TS2322: Type '{ [Nums.A]: number; [Nums.B]: number; }' is not assignable to type 'OneNumNum'. +!!! error TS2322: Object literal may only specify known properties, and '[Nums.B]' does not exist in type 'OneNumNum'. + onn[0] = 8 + onn['0'] = 9 + onn[1] = 10 // only disallowed with --strict + ~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type 'OneNumNum' has no index signature. + + // just number + type NumberNum = { [K in number]: number } + let numn: NumberNum = { } + numn[0] = 31 + numn['1'] = 32 + ~~~ +!!! error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. + numn['oops'] = 33 + ~~~~~~ +!!! error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. + + // computed enum gets a string indexer + enum Comp { + A, + B = 1 << 3 + } + + type CompNum = { [K in Comp]: number } + let cn: CompNum = { [Comp.A]: 15 } + let cnn: CompNum = { [Comp.A]: 16, '101': 17 } + cn[1001] = 18 + cn['maybe?'] = 19 + ~~~~~~~~ +!!! error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. + + // manual string/number union mixes + type Mix = 0 | 1 | 'a' | 'i' | 'u'; + type MixNum = { [K in Mix]: number } + let mn: MixNum = { [0]: 20, '1': 21, a: 22, i: 23, u: 24 } + let mnn: MixNum = { [0]: 29, [1]: 30 } + ~~~ +!!! error TS2322: Type '{ [0]: number; [1]: number; }' is not assignable to type 'MixNum'. +!!! error TS2322: Property 'a' is missing in type '{ [0]: number; [1]: number; }'. + + // conflicts result in the property being thrown out + type MixConflict = 0 | 1 | 1 | 1 | 1 | 1 | 2 | '0' | '1'; + type MixConflictNum = { [K in MixConflict]: number } + let mcn: MixConflictNum = { [2]: 25 } + let mcnn: MixConflictNum = { [0]: 26, [1]: 27, [2]: 28 } + ~~~~~~~ +!!! error TS2322: Type '{ [0]: number; [1]: number; [2]: number; }' is not assignable to type 'MixConflictNum'. +!!! error TS2322: Object literal may only specify known properties, and '[0]' does not exist in type 'MixConflictNum'. + \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeNumericEnum.js b/tests/baselines/reference/mappedTypeNumericEnum.js new file mode 100644 index 0000000000000..6f3e2415d355d --- /dev/null +++ b/tests/baselines/reference/mappedTypeNumericEnum.js @@ -0,0 +1,135 @@ +//// [mappedTypeNumericEnum.ts] +// with numbers +enum Nums { + A, + B +} +enum Nums2 { + Aleph, + Bet, + Gimel +} +type NumBool = { [K in Nums]: boolean } +let nb: NumBool = { '0': true, '1': false } +let wronb: NumBool = { '0': true, '2': false } +let wronb2: NumBool = { '0': true } +nb[Nums.A] = false; +nb[Nums2.Bet] = true; +nb[Nums2.Gimel] = false; // only disallowed with --strict + +// with strings +enum Strs { + A = 'a', + B = 'b' +} +type StrAny = { [K in Strs]: any } +let sa: StrAny = { a: 1, b: 2 } +sa[Strs.A] = 'a' +sa['nope'] = 'not allowed' + +// union of numbers +type Ns = 0 | 1; +type NumNum = { [K in Ns]: number } +let nn: NumNum = { [Nums.A]: 3, [Nums.B]: 4 } +let omnomnom: NumNum = { '0': 12, [Nums.B]: 13, [Nums2.Gimel]: 14 } +nn[0] = 5 +nn['1'] = 6 + +// single number +type N = 0; +type OneNumNum = { [K in N]: number } +let onn: OneNumNum = { [Nums.A]: 7 } +let wronng: OneNumNum = { [Nums.A]: 7, [Nums.B]: 11 } +onn[0] = 8 +onn['0'] = 9 +onn[1] = 10 // only disallowed with --strict + +// just number +type NumberNum = { [K in number]: number } +let numn: NumberNum = { } +numn[0] = 31 +numn['1'] = 32 +numn['oops'] = 33 + +// computed enum gets a string indexer +enum Comp { + A, + B = 1 << 3 +} + +type CompNum = { [K in Comp]: number } +let cn: CompNum = { [Comp.A]: 15 } +let cnn: CompNum = { [Comp.A]: 16, '101': 17 } +cn[1001] = 18 +cn['maybe?'] = 19 + +// manual string/number union mixes +type Mix = 0 | 1 | 'a' | 'i' | 'u'; +type MixNum = { [K in Mix]: number } +let mn: MixNum = { [0]: 20, '1': 21, a: 22, i: 23, u: 24 } +let mnn: MixNum = { [0]: 29, [1]: 30 } + +// conflicts result in the property being thrown out +type MixConflict = 0 | 1 | 1 | 1 | 1 | 1 | 2 | '0' | '1'; +type MixConflictNum = { [K in MixConflict]: number } +let mcn: MixConflictNum = { [2]: 25 } +let mcnn: MixConflictNum = { [0]: 26, [1]: 27, [2]: 28 } + + +//// [mappedTypeNumericEnum.js] +"use strict"; +// with numbers +var Nums; +(function (Nums) { + Nums[Nums["A"] = 0] = "A"; + Nums[Nums["B"] = 1] = "B"; +})(Nums || (Nums = {})); +var Nums2; +(function (Nums2) { + Nums2[Nums2["Aleph"] = 0] = "Aleph"; + Nums2[Nums2["Bet"] = 1] = "Bet"; + Nums2[Nums2["Gimel"] = 2] = "Gimel"; +})(Nums2 || (Nums2 = {})); +var nb = { '0': true, '1': false }; +var wronb = { '0': true, '2': false }; +var wronb2 = { '0': true }; +nb[Nums.A] = false; +nb[Nums2.Bet] = true; +nb[Nums2.Gimel] = false; // only disallowed with --strict +// with strings +var Strs; +(function (Strs) { + Strs["A"] = "a"; + Strs["B"] = "b"; +})(Strs || (Strs = {})); +var sa = { a: 1, b: 2 }; +sa[Strs.A] = 'a'; +sa['nope'] = 'not allowed'; +var nn = (_a = {}, _a[Nums.A] = 3, _a[Nums.B] = 4, _a); +var omnomnom = (_b = { '0': 12 }, _b[Nums.B] = 13, _b[Nums2.Gimel] = 14, _b); +nn[0] = 5; +nn['1'] = 6; +var onn = (_c = {}, _c[Nums.A] = 7, _c); +var wronng = (_d = {}, _d[Nums.A] = 7, _d[Nums.B] = 11, _d); +onn[0] = 8; +onn['0'] = 9; +onn[1] = 10; // only disallowed with --strict +var numn = {}; +numn[0] = 31; +numn['1'] = 32; +numn['oops'] = 33; +// computed enum gets a string indexer +var Comp; +(function (Comp) { + Comp[Comp["A"] = 0] = "A"; + Comp[Comp["B"] = 8] = "B"; +})(Comp || (Comp = {})); +var cn = (_e = {}, _e[Comp.A] = 15, _e); +var cnn = (_f = {}, _f[Comp.A] = 16, _f['101'] = 17, _f); +cn[1001] = 18; +cn['maybe?'] = 19; +var mn = (_g = {}, _g[0] = 20, _g['1'] = 21, _g.a = 22, _g.i = 23, _g.u = 24, _g); +var mnn = (_h = {}, _h[0] = 29, _h[1] = 30, _h); +var mcn = (_j = {}, _j[2] = 25, _j); +var mcnn = (_k = {}, _k[0] = 26, _k[1] = 27, _k[2] = 28, _k); +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; diff --git a/tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts b/tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts new file mode 100644 index 0000000000000..5ea2409783dd8 --- /dev/null +++ b/tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts @@ -0,0 +1,76 @@ +// @strict: true +// with numbers +enum Nums { + A, + B +} +enum Nums2 { + Aleph, + Bet, + Gimel +} +type NumBool = { [K in Nums]: boolean } +let nb: NumBool = { '0': true, '1': false } +let wronb: NumBool = { '0': true, '2': false } +let wronb2: NumBool = { '0': true } +nb[Nums.A] = false; +nb[Nums2.Bet] = true; +nb[Nums2.Gimel] = false; // only disallowed with --strict + +// with strings +enum Strs { + A = 'a', + B = 'b' +} +type StrAny = { [K in Strs]: any } +let sa: StrAny = { a: 1, b: 2 } +sa[Strs.A] = 'a' +sa['nope'] = 'not allowed' + +// union of numbers +type Ns = 0 | 1; +type NumNum = { [K in Ns]: number } +let nn: NumNum = { [Nums.A]: 3, [Nums.B]: 4 } +let omnomnom: NumNum = { '0': 12, [Nums.B]: 13, [Nums2.Gimel]: 14 } +nn[0] = 5 +nn['1'] = 6 + +// single number +type N = 0; +type OneNumNum = { [K in N]: number } +let onn: OneNumNum = { [Nums.A]: 7 } +let wronng: OneNumNum = { [Nums.A]: 7, [Nums.B]: 11 } +onn[0] = 8 +onn['0'] = 9 +onn[1] = 10 // only disallowed with --strict + +// just number +type NumberNum = { [K in number]: number } +let numn: NumberNum = { } +numn[0] = 31 +numn['1'] = 32 +numn['oops'] = 33 + +// computed enum gets a string indexer +enum Comp { + A, + B = 1 << 3 +} + +type CompNum = { [K in Comp]: number } +let cn: CompNum = { [Comp.A]: 15 } +let cnn: CompNum = { [Comp.A]: 16, '101': 17 } +cn[1001] = 18 +cn['maybe?'] = 19 + +// manual string/number union mixes +type Mix = 0 | 1 | 'a' | 'i' | 'u'; +type MixNum = { [K in Mix]: number } +let mn: MixNum = { [0]: 20, '1': 21, a: 22, i: 23, u: 24 } +let mnn: MixNum = { [0]: 29, [1]: 30 } + +// conflicts result in the property being thrown out +type MixConflict = 0 | 1 | 1 | 1 | 1 | 1 | 2 | '0' | '1'; +type MixConflictNum = { [K in MixConflict]: number } +let mcn: MixConflictNum = { [2]: 25 } +let mcnn: MixConflictNum = { [0]: 26, [1]: 27, [2]: 28 }