From bd2c38698f61ce57fb31ad7861dae9d651356f28 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 8 Sep 2017 14:59:17 -0700 Subject: [PATCH 1/2] Mapped types allow numeric constraint types Mapped types now allow the constraint type to be `number`, a union of numeric literal types or an enum. When it is possible to treat an enum as a union of numeric literal types, the compiler does so. Numeric literal types create a property with the string form of the name. `number` and non-union enums create a number index signature on the mapped type. This enables most numeric enums to be used as the constraint type in mapped types: ```ts // @strict: true enum Nums { A, B } type NumBool = { [K in Nums]: boolean } let nb: NumBool = { '0': true, [1]: false } nb[Nums.A] = false ``` Note that any expression with the right literal type can be used to index into the resulting mapped type. This means that other enum entries are fine as long as they are in range of the original enum: ```ts enum Nums2 { Aleph, Bet, Gimel } nb[Nums.Aleph] = true // fine, equivalent to Nums.A and 0 and '0' nb[Nums.Gimel] = false // error only with 'strict': true ``` You can also manually create unions that mix string and number literal types: ```ts type Mixed = 0 | 1 | 'a' | '1' type MixNum = { [K in Mixed]: number } ``` If you do this be aware that if a number and a string literal conflict after the number has been converted to string, then both will be dropped from the mapped type: ```ts let mn: MixNum = { [0]: 8, [1]: 8, 'a': 8 } ~~~~~~ Object literal may only specify known properties, and '[1]' does not exist in type 'MixNum' ``` Non-union enums and `number` are just equivalent to a number index signature; `{ [K in number]: any }` is equivalent to `{ [n: number]: any }`. --- src/compiler/checker.ts | 44 +++++++++++++++++++++++++---------------- src/compiler/types.ts | 3 ++- 2 files changed, 29 insertions(+), 18 deletions(-) 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; From 3cd24b721a25e30f178fb01da1a929a523a05120 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 8 Sep 2017 15:00:25 -0700 Subject: [PATCH 2/2] Test:numeric constraint types for mapped types --- .../reference/mappedTypeErrors.errors.txt | 17 +-- .../mappedTypeNumericEnum.errors.txt | 127 ++++++++++++++++ .../reference/mappedTypeNumericEnum.js | 135 ++++++++++++++++++ .../types/mapped/mappedTypeNumericEnum.ts | 76 ++++++++++ 4 files changed, 347 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/mappedTypeNumericEnum.errors.txt create mode 100644 tests/baselines/reference/mappedTypeNumericEnum.js create mode 100644 tests/cases/conformance/types/mapped/mappedTypeNumericEnum.ts 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 }