Merge pull request #12927 from Microsoft/mappedTypesPropsAndIndex
Preserve declared properties when mapping types with index signatures
This commit is contained in:
commit
8265e29038
8 changed files with 305 additions and 14 deletions
|
@ -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) {
|
||||
|
|
|
@ -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'.
|
|
@ -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];
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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, --, --))
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue