Merge pull request #12927 from Microsoft/mappedTypesPropsAndIndex

Preserve declared properties when mapping types with index signatures
This commit is contained in:
Anders Hejlsberg 2016-12-14 15:23:29 -08:00 committed by GitHub
commit 8265e29038
8 changed files with 305 additions and 14 deletions

View file

@ -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((<IndexType>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((<IndexType>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) {

View file

@ -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<Foo, "a" | "b">'.
Object literal may only specify known properties, and 'c' does not exist in type 'Pick<Foo, "a" | "b">'.
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<T2>'.
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<Foo, "a" | "b">'.
!!! error TS2345: Object literal may only specify known properties, and 'c' does not exist in type 'Pick<Foo, "a" | "b">'.
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<T2> = { a: 'no' }; // Error
~~
!!! error TS2322: Type '{ a: string; }' is not assignable to type 'Partial<T2>'.
!!! 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'.

View file

@ -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<T2> = { 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<T> {
setState<K extends keyof T>(props: Pick<T, K>): void;
}
declare let c: C<Foo>;
declare type T2 = {
a?: number;
[key: string]: any;
};
declare let x1: T2;
declare let x2: Partial<T2>;
declare let x3: {
[P in keyof T2]: T2[P];
};

View file

@ -74,7 +74,30 @@ var b04: Readonly<BP>;
var b04: Partial<Readonly<B>>;
var b04: Readonly<Partial<B>>;
var b04: { [P in keyof BPR]: BPR[P] }
var b04: Pick<BPR, keyof BPR>;
var b04: Pick<BPR, keyof BPR>;
type Foo = { prop: number, [x: string]: number };
function f1(x: Partial<Foo>) {
x.prop; // ok
(x["other"] || 0).toFixed();
}
function f2(x: Readonly<Foo>) {
x.prop; // ok
x["other"].toFixed();
}
function f3(x: Boxified<Foo>) {
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();
}

View file

@ -353,3 +353,80 @@ var b04: Pick<BPR, keyof BPR>;
>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<Foo>) {
>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<Foo>) {
>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<Foo>) {
>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, --, --))
}

View file

@ -353,3 +353,95 @@ var b04: Pick<BPR, keyof BPR>;
>BPR : BPR
>BPR : BPR
type Foo = { prop: number, [x: string]: number };
>Foo : Foo
>prop : number
>x : string
function f1(x: Partial<Foo>) {
>f1 : (x: Partial<Foo>) => void
>x : Partial<Foo>
>Partial : Partial<T>
>Foo : Foo
x.prop; // ok
>x.prop : number | undefined
>x : Partial<Foo>
>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<Foo>
>"other" : "other"
>0 : 0
>toFixed : (fractionDigits?: number | undefined) => string
}
function f2(x: Readonly<Foo>) {
>f2 : (x: Readonly<Foo>) => void
>x : Readonly<Foo>
>Readonly : Readonly<T>
>Foo : Foo
x.prop; // ok
>x.prop : number
>x : Readonly<Foo>
>prop : number
x["other"].toFixed();
>x["other"].toFixed() : string
>x["other"].toFixed : (fractionDigits?: number | undefined) => string
>x["other"] : number
>x : Readonly<Foo>
>"other" : "other"
>toFixed : (fractionDigits?: number | undefined) => string
}
function f3(x: Boxified<Foo>) {
>f3 : (x: Boxified<Foo>) => void
>x : Boxified<Foo>
>Boxified : Boxified<T>
>Foo : Foo
x.prop; // ok
>x.prop : { x: number; }
>x : Boxified<Foo>
>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<Foo>
>"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
}

View file

@ -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<T2> = { a: 'no' }; // Error
let x3: { [P in keyof T2]: T2[P]} = { a: 'no' }; // Error

View file

@ -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<BP>;
var b04: Partial<Readonly<B>>;
var b04: Readonly<Partial<B>>;
var b04: { [P in keyof BPR]: BPR[P] }
var b04: Pick<BPR, keyof BPR>;
var b04: Pick<BPR, keyof BPR>;
type Foo = { prop: number, [x: string]: number };
function f1(x: Partial<Foo>) {
x.prop; // ok
(x["other"] || 0).toFixed();
}
function f2(x: Readonly<Foo>) {
x.prop; // ok
x["other"].toFixed();
}
function f3(x: Boxified<Foo>) {
x.prop; // ok
x["other"].x.toFixed();
}
function f4(x: { [P in keyof Foo]: Foo[P] }) {
x.prop; // ok
x["other"].toFixed();
}