diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 26164a528f3..5246bbfc700 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4536,15 +4536,27 @@ namespace ts { const typeParameter = getTypeParameterFromMappedType(type); const constraintType = getConstraintTypeFromMappedType(type); const templateType = getTemplateTypeFromMappedType(type); - const modifiersType = getModifiersTypeFromMappedType(type); + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); const templateReadonly = !!type.declaration.readonlyToken; const templateOptional = !!type.declaration.questionToken; - // First, if the constraint type is a type parameter, obtain the base constraint. Then, - // if the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X. - // Finally, iterate over the constituents of the resulting iteration type. - const keyType = constraintType.flags & TypeFlags.TypeVariable ? getApparentType(constraintType) : constraintType; - const iterationType = keyType.flags & TypeFlags.Index ? getIndexType(getApparentType((keyType).type)) : keyType; - forEachType(iterationType, t => { + if (type.declaration.typeParameter.constraint.kind === SyntaxKind.TypeOperator) { + // We have a { [P in keyof T]: X } + forEachType(getLiteralTypeFromPropertyNames(modifiersType), addMemberForKeyType); + if (getIndexInfoOfType(modifiersType, IndexKind.String)) { + addMemberForKeyType(stringType); + } + } + else { + // First, if the constraint type is a type parameter, obtain the base constraint. Then, + // if the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X. + // Finally, iterate over the constituents of the resulting iteration type. + const keyType = constraintType.flags & TypeFlags.TypeVariable ? getApparentType(constraintType) : constraintType; + const iterationType = keyType.flags & TypeFlags.Index ? getIndexType(getApparentType((keyType).type)) : keyType; + forEachType(iterationType, addMemberForKeyType); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); + + function addMemberForKeyType(t: Type) { // Create a mapper from T to the current iteration type constituent. Then, if the // mapped type is itself an instantiated type, combine the iteration mapper with the // instantiation mapper. @@ -4565,8 +4577,7 @@ namespace ts { else if (t.flags & TypeFlags.String) { stringIndexInfo = createIndexInfo(propType, templateReadonly); } - }); - setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); + } } function getTypeParameterFromMappedType(type: MappedType) { diff --git a/tests/baselines/reference/mappedTypeErrors.errors.txt b/tests/baselines/reference/mappedTypeErrors.errors.txt index 17c77fa50a0..f98a11dbaf9 100644 --- a/tests/baselines/reference/mappedTypeErrors.errors.txt +++ b/tests/baselines/reference/mappedTypeErrors.errors.txt @@ -36,9 +36,18 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(124,12): error TS2345: Type 'undefined' is not assignable to type 'string'. tests/cases/conformance/types/mapped/mappedTypeErrors.ts(125,14): error TS2345: Argument of type '{ c: boolean; }' is not assignable to parameter of type 'Pick'. Object literal may only specify known properties, and 'c' does not exist in type 'Pick'. +tests/cases/conformance/types/mapped/mappedTypeErrors.ts(129,5): error TS2322: Type '{ a: string; }' is not assignable to type 'T2'. + Types of property 'a' are incompatible. + Type 'string' is not assignable to type 'number | undefined'. +tests/cases/conformance/types/mapped/mappedTypeErrors.ts(130,5): error TS2322: Type '{ a: string; }' is not assignable to type 'Partial'. + Types of property 'a' are incompatible. + Type 'string' is not assignable to type 'number | undefined'. +tests/cases/conformance/types/mapped/mappedTypeErrors.ts(131,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 (21 errors) ==== +==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (24 errors) ==== interface Shape { name: string; @@ -223,4 +232,21 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(125,14): error TS2345: ~~~~~~~ !!! error TS2345: Argument of type '{ c: boolean; }' is not assignable to parameter of type 'Pick'. !!! error TS2345: Object literal may only specify known properties, and 'c' does not exist in type 'Pick'. - \ No newline at end of file + + type T2 = { a?: number, [key: string]: any }; + + let x1: T2 = { a: 'no' }; // Error + ~~ +!!! error TS2322: Type '{ a: string; }' is not assignable to type 'T2'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type 'string' is not assignable to type 'number | undefined'. + let x2: Partial = { a: 'no' }; // Error + ~~ +!!! error TS2322: Type '{ a: string; }' is not assignable to type 'Partial'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type 'string' is not assignable to type 'number | undefined'. + let x3: { [P in keyof T2]: T2[P]} = { a: 'no' }; // Error + ~~ +!!! error TS2322: Type '{ a: string; }' is not assignable to type '{ [x: string]: any; a?: number | undefined; }'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type 'string' is not assignable to type 'number | undefined'. \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeErrors.js b/tests/baselines/reference/mappedTypeErrors.js index e1b458565bb..83dc235c705 100644 --- a/tests/baselines/reference/mappedTypeErrors.js +++ b/tests/baselines/reference/mappedTypeErrors.js @@ -124,7 +124,12 @@ c.setState({ }); c.setState(foo); c.setState({ a: undefined }); // Error c.setState({ c: true }); // Error - + +type T2 = { a?: number, [key: string]: any }; + +let x1: T2 = { a: 'no' }; // Error +let x2: Partial = { a: 'no' }; // Error +let x3: { [P in keyof T2]: T2[P]} = { a: 'no' }; // Error //// [mappedTypeErrors.js] function f1(x) { @@ -196,6 +201,9 @@ c.setState({}); c.setState(foo); c.setState({ a: undefined }); // Error c.setState({ c: true }); // Error +var x1 = { a: 'no' }; // Error +var x2 = { a: 'no' }; // Error +var x3 = { a: 'no' }; // Error //// [mappedTypeErrors.d.ts] @@ -251,3 +259,12 @@ declare class C { setState(props: Pick): void; } declare let c: C; +declare type T2 = { + a?: number; + [key: string]: any; +}; +declare let x1: T2; +declare let x2: Partial; +declare let x3: { + [P in keyof T2]: T2[P]; +}; diff --git a/tests/baselines/reference/mappedTypeModifiers.js b/tests/baselines/reference/mappedTypeModifiers.js index 6291fb9c42b..a247d18f2d4 100644 --- a/tests/baselines/reference/mappedTypeModifiers.js +++ b/tests/baselines/reference/mappedTypeModifiers.js @@ -74,7 +74,30 @@ var b04: Readonly; var b04: Partial>; var b04: Readonly>; var b04: { [P in keyof BPR]: BPR[P] } -var b04: Pick; +var b04: Pick; + +type Foo = { prop: number, [x: string]: number }; + +function f1(x: Partial) { + x.prop; // ok + (x["other"] || 0).toFixed(); +} + +function f2(x: Readonly) { + x.prop; // ok + x["other"].toFixed(); +} + +function f3(x: Boxified) { + x.prop; // ok + x["other"].x.toFixed(); +} + +function f4(x: { [P in keyof Foo]: Foo[P] }) { + x.prop; // ok + x["other"].toFixed(); +} + //// [mappedTypeModifiers.js] var v00; @@ -131,3 +154,19 @@ var b04; var b04; var b04; var b04; +function f1(x) { + x.prop; // ok + (x["other"] || 0).toFixed(); +} +function f2(x) { + x.prop; // ok + x["other"].toFixed(); +} +function f3(x) { + x.prop; // ok + x["other"].x.toFixed(); +} +function f4(x) { + x.prop; // ok + x["other"].toFixed(); +} diff --git a/tests/baselines/reference/mappedTypeModifiers.symbols b/tests/baselines/reference/mappedTypeModifiers.symbols index 5138055d08b..b2b8faed4cf 100644 --- a/tests/baselines/reference/mappedTypeModifiers.symbols +++ b/tests/baselines/reference/mappedTypeModifiers.symbols @@ -353,3 +353,80 @@ var b04: Pick; >BPR : Symbol(BPR, Decl(mappedTypeModifiers.ts, 42, 67)) >BPR : Symbol(BPR, Decl(mappedTypeModifiers.ts, 42, 67)) +type Foo = { prop: number, [x: string]: number }; +>Foo : Symbol(Foo, Decl(mappedTypeModifiers.ts, 75, 30)) +>prop : Symbol(prop, Decl(mappedTypeModifiers.ts, 77, 12)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 77, 28)) + +function f1(x: Partial) { +>f1 : Symbol(f1, Decl(mappedTypeModifiers.ts, 77, 49)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 79, 12)) +>Partial : Symbol(Partial, Decl(lib.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(mappedTypeModifiers.ts, 75, 30)) + + x.prop; // ok +>x.prop : Symbol(prop) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 79, 12)) +>prop : Symbol(prop) + + (x["other"] || 0).toFixed(); +>(x["other"] || 0).toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 79, 12)) +>toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +} + +function f2(x: Readonly) { +>f2 : Symbol(f2, Decl(mappedTypeModifiers.ts, 82, 1)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 84, 12)) +>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(mappedTypeModifiers.ts, 75, 30)) + + x.prop; // ok +>x.prop : Symbol(prop) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 84, 12)) +>prop : Symbol(prop) + + x["other"].toFixed(); +>x["other"].toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 84, 12)) +>toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +} + +function f3(x: Boxified) { +>f3 : Symbol(f3, Decl(mappedTypeModifiers.ts, 87, 1)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 89, 12)) +>Boxified : Symbol(Boxified, Decl(mappedTypeModifiers.ts, 36, 28)) +>Foo : Symbol(Foo, Decl(mappedTypeModifiers.ts, 75, 30)) + + x.prop; // ok +>x.prop : Symbol(prop) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 89, 12)) +>prop : Symbol(prop) + + x["other"].x.toFixed(); +>x["other"].x.toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +>x["other"].x : Symbol(x, Decl(mappedTypeModifiers.ts, 38, 38)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 89, 12)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 38, 38)) +>toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +} + +function f4(x: { [P in keyof Foo]: Foo[P] }) { +>f4 : Symbol(f4, Decl(mappedTypeModifiers.ts, 92, 1)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 94, 12)) +>P : Symbol(P, Decl(mappedTypeModifiers.ts, 94, 18)) +>Foo : Symbol(Foo, Decl(mappedTypeModifiers.ts, 75, 30)) +>Foo : Symbol(Foo, Decl(mappedTypeModifiers.ts, 75, 30)) +>P : Symbol(P, Decl(mappedTypeModifiers.ts, 94, 18)) + + x.prop; // ok +>x.prop : Symbol(prop) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 94, 12)) +>prop : Symbol(prop) + + x["other"].toFixed(); +>x["other"].toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(mappedTypeModifiers.ts, 94, 12)) +>toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/mappedTypeModifiers.types b/tests/baselines/reference/mappedTypeModifiers.types index 7a30e227bd6..7771de023f1 100644 --- a/tests/baselines/reference/mappedTypeModifiers.types +++ b/tests/baselines/reference/mappedTypeModifiers.types @@ -353,3 +353,95 @@ var b04: Pick; >BPR : BPR >BPR : BPR +type Foo = { prop: number, [x: string]: number }; +>Foo : Foo +>prop : number +>x : string + +function f1(x: Partial) { +>f1 : (x: Partial) => void +>x : Partial +>Partial : Partial +>Foo : Foo + + x.prop; // ok +>x.prop : number | undefined +>x : Partial +>prop : number | undefined + + (x["other"] || 0).toFixed(); +>(x["other"] || 0).toFixed() : string +>(x["other"] || 0).toFixed : (fractionDigits?: number | undefined) => string +>(x["other"] || 0) : number +>x["other"] || 0 : number +>x["other"] : number | undefined +>x : Partial +>"other" : "other" +>0 : 0 +>toFixed : (fractionDigits?: number | undefined) => string +} + +function f2(x: Readonly) { +>f2 : (x: Readonly) => void +>x : Readonly +>Readonly : Readonly +>Foo : Foo + + x.prop; // ok +>x.prop : number +>x : Readonly +>prop : number + + x["other"].toFixed(); +>x["other"].toFixed() : string +>x["other"].toFixed : (fractionDigits?: number | undefined) => string +>x["other"] : number +>x : Readonly +>"other" : "other" +>toFixed : (fractionDigits?: number | undefined) => string +} + +function f3(x: Boxified) { +>f3 : (x: Boxified) => void +>x : Boxified +>Boxified : Boxified +>Foo : Foo + + x.prop; // ok +>x.prop : { x: number; } +>x : Boxified +>prop : { x: number; } + + x["other"].x.toFixed(); +>x["other"].x.toFixed() : string +>x["other"].x.toFixed : (fractionDigits?: number | undefined) => string +>x["other"].x : number +>x["other"] : { x: number; } +>x : Boxified +>"other" : "other" +>x : number +>toFixed : (fractionDigits?: number | undefined) => string +} + +function f4(x: { [P in keyof Foo]: Foo[P] }) { +>f4 : (x: { [x: string]: number; prop: number; }) => void +>x : { [x: string]: number; prop: number; } +>P : P +>Foo : Foo +>Foo : Foo +>P : P + + x.prop; // ok +>x.prop : number +>x : { [x: string]: number; prop: number; } +>prop : number + + x["other"].toFixed(); +>x["other"].toFixed() : string +>x["other"].toFixed : (fractionDigits?: number | undefined) => string +>x["other"] : number +>x : { [x: string]: number; prop: number; } +>"other" : "other" +>toFixed : (fractionDigits?: number | undefined) => string +} + diff --git a/tests/cases/conformance/types/mapped/mappedTypeErrors.ts b/tests/cases/conformance/types/mapped/mappedTypeErrors.ts index 4be6b6b098d..458dbe9caa4 100644 --- a/tests/cases/conformance/types/mapped/mappedTypeErrors.ts +++ b/tests/cases/conformance/types/mapped/mappedTypeErrors.ts @@ -125,3 +125,9 @@ c.setState({ }); c.setState(foo); c.setState({ a: undefined }); // Error c.setState({ c: true }); // Error + +type T2 = { a?: number, [key: string]: any }; + +let x1: T2 = { a: 'no' }; // Error +let x2: Partial = { a: 'no' }; // Error +let x3: { [P in keyof T2]: T2[P]} = { a: 'no' }; // Error \ No newline at end of file diff --git a/tests/cases/conformance/types/mapped/mappedTypeModifiers.ts b/tests/cases/conformance/types/mapped/mappedTypeModifiers.ts index 1fe6872965c..a111d186d16 100644 --- a/tests/cases/conformance/types/mapped/mappedTypeModifiers.ts +++ b/tests/cases/conformance/types/mapped/mappedTypeModifiers.ts @@ -1,4 +1,5 @@ // @strictNullChecks: true +// @noimplicitany: true type T = { a: number, b: string }; type TP = { a?: number, b?: string }; @@ -74,4 +75,26 @@ var b04: Readonly; var b04: Partial>; var b04: Readonly>; var b04: { [P in keyof BPR]: BPR[P] } -var b04: Pick; \ No newline at end of file +var b04: Pick; + +type Foo = { prop: number, [x: string]: number }; + +function f1(x: Partial) { + x.prop; // ok + (x["other"] || 0).toFixed(); +} + +function f2(x: Readonly) { + x.prop; // ok + x["other"].toFixed(); +} + +function f3(x: Boxified) { + x.prop; // ok + x["other"].x.toFixed(); +} + +function f4(x: { [P in keyof Foo]: Foo[P] }) { + x.prop; // ok + x["other"].toFixed(); +}