Skip to content

Commit

Permalink
Merge pull request #12276 from Microsoft/libMappedTypes
Browse files Browse the repository at this point in the history
Predefined mapped types in lib.d.ts
  • Loading branch information
ahejlsberg authored Nov 15, 2016
2 parents 4c24744 + 997c586 commit 0ba2348
Show file tree
Hide file tree
Showing 9 changed files with 446 additions and 351 deletions.
66 changes: 43 additions & 23 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4550,6 +4550,10 @@ namespace ts {
unknownType);
}

function getErasedTemplateTypeFromMappedType(type: MappedType) {
return instantiateType(getTemplateTypeFromMappedType(type), createUnaryTypeMapper(getTypeParameterFromMappedType(type), anyType));
}

function isGenericMappedType(type: Type) {
if (getObjectFlags(type) & ObjectFlags.Mapped) {
const constraintType = getConstraintTypeFromMappedType(<MappedType>type);
Expand Down Expand Up @@ -7190,29 +7194,18 @@ namespace ts {
return result;
}
}
if (isGenericMappedType(target)) {
// A type [P in S]: X is related to a type [P in T]: Y if T is related to S and X is related to Y.
if (isGenericMappedType(source)) {
if ((result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) &&
(result = isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors))) {
return result;
}
}
}
else {
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
const apparentSource = getApparentType(source);
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
// relates to X. Thus, we include intersection types on the source side here.
if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
// Report structural errors only if we haven't reported any errors yet
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive);
if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
errorInfo = saveErrorInfo;
return result;
}
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
const apparentSource = getApparentType(source);
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
// relates to X. Thus, we include intersection types on the source side here.
if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
// Report structural errors only if we haven't reported any errors yet
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive);
if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
}
Expand Down Expand Up @@ -7441,6 +7434,9 @@ namespace ts {
if (expandingFlags === 3) {
result = Ternary.Maybe;
}
else if (isGenericMappedType(source) || isGenericMappedType(target)) {
result = mappedTypeRelatedTo(source, target, reportErrors);
}
else {
result = propertiesRelatedTo(source, target, reportErrors);
if (result) {
Expand Down Expand Up @@ -7472,6 +7468,30 @@ namespace ts {
return result;
}

// A type [P in S]: X is related to a type [P in T]: Y if T is related to S and X is related to Y.
function mappedTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
if (isGenericMappedType(source) && isGenericMappedType(target)) {
let result: Ternary;
if (relation === identityRelation) {
const readonlyMatches = !(<MappedType>source).declaration.readonlyToken === !(<MappedType>target).declaration.readonlyToken;
const optionalMatches = !(<MappedType>source).declaration.questionToken === !(<MappedType>target).declaration.questionToken;
if (readonlyMatches && optionalMatches) {
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
return result & isRelatedTo(getErasedTemplateTypeFromMappedType(<MappedType>source), getErasedTemplateTypeFromMappedType(<MappedType>target), reportErrors);
}
}
}
else {
if (relation === comparableRelation || !(<MappedType>source).declaration.questionToken || (<MappedType>target).declaration.questionToken) {
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
return result & isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
}
}
}
}
return Ternary.False;
}

function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
return propertiesIdenticalTo(source, target);
Expand Down
30 changes: 29 additions & 1 deletion src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ interface ObjectConstructor {
* Prevents the modification of existing property attributes and values, and prevents the addition of new properties.
* @param o Object on which to lock the attributes.
*/
freeze<T>(o: T): T;
freeze<T>(o: T): Readonly<T>;

/**
* Prevents the addition of new properties to an object.
Expand Down Expand Up @@ -1343,6 +1343,34 @@ interface ArrayLike<T> {
readonly [n: number]: T;
}

/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};

/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

/**
* From T pick a set of properties K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}

/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends string | number, T> = {
[P in K]: T;
}

/**
* Represents a raw buffer of binary data, which is used to store data for the
* different typed arrays. ArrayBuffers cannot be read from or written to directly,
Expand Down
71 changes: 45 additions & 26 deletions tests/baselines/reference/mappedTypeErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -1,41 +1,29 @@
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(34,20): error TS2313: Type parameter 'P' has a circular constraint.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(35,20): error TS2322: Type 'Date' is not assignable to type 'string | number'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(20,20): error TS2313: Type parameter 'P' has a circular constraint.
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(36,19): error TS2344: Type 'Date' does not satisfy the constraint 'string | number'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(22,19): error TS2344: Type 'Date' does not satisfy the constraint 'string | number'.
Type 'Date' is not assignable to type 'number'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(39,24): error TS2344: Type '"foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(40,24): error TS2344: Type '"name" | "foo"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
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"'.
Type '"foo"' is not assignable to type '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(42,24): error TS2344: Type '"x" | "y"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(28,24): error TS2344: Type '"x" | "y"' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
Type '"x"' is not assignable to type '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(44,24): error TS2344: Type 'undefined' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(47,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(30,24): error TS2344: Type 'undefined' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(33,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
Type 'T' is not assignable to type '"visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(51,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(37,24): error TS2344: Type 'T' does not satisfy the constraint '"name" | "width" | "height" | "visible"'.
Type 'string | number' is not assignable to type '"name" | "width" | "height" | "visible"'.
Type 'string' is not assignable to type '"name" | "width" | "height" | "visible"'.
Type 'T' is not assignable to type '"visible"'.
Type 'string | number' is not assignable to type '"visible"'.
Type 'string' is not assignable to type '"visible"'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(59,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]?: T[P]; }'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(60,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]: T[P]; }'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(61,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]?: T[P]; }'.
tests/cases/conformance/types/mapped/mappedTypeErrors.ts(66,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]: T[P][]; }'.


==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (9 errors) ====

type Partial<T> = {
[P in keyof T]?: T[P];
};

type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}

type Record<K extends string | number, T> = {
[_ in K]: T;
}
==== tests/cases/conformance/types/mapped/mappedTypeErrors.ts (13 errors) ====

interface Shape {
name: string;
Expand All @@ -53,6 +41,8 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(51,24): error TS2344: T
y: number;
}

// Constraint checking

type T00 = { [P in P]: string }; // Error
~
!!! error TS2313: Type parameter 'P' has a circular constraint.
Expand Down Expand Up @@ -107,4 +97,33 @@ tests/cases/conformance/types/mapped/mappedTypeErrors.ts(51,24): error TS2344: T

function f4<T extends keyof Named>(x: T) {
let y: Pick<Shape, T>;
}

// Type identity checking

function f10<T>() {
type K = keyof T;
var x: { [P in keyof T]: T[P] };
var x: { [Q in keyof T]: T[Q] };
var x: { [R in K]: T[R] };
}

function f11<T>() {
var x: { [P in keyof T]: T[P] };
var x: { [P in keyof T]?: T[P] }; // Error
~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]?: T[P]; }'.
var x: { readonly [P in keyof T]: T[P] }; // Error
~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]: T[P]; }'.
var x: { readonly [P in keyof T]?: T[P] }; // Error
~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ readonly [P in keyof T]?: T[P]; }'.
}

function f12<T>() {
var x: { [P in keyof T]: T[P] };
var x: { [P in keyof T]: T[P][] }; // Error
~
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type '{ [P in keyof T]: T[P]; }', but here has type '{ [P in keyof T]: T[P][]; }'.
}
70 changes: 42 additions & 28 deletions tests/baselines/reference/mappedTypeErrors.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
//// [mappedTypeErrors.ts]

type Partial<T> = {
[P in keyof T]?: T[P];
};

type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}

type Record<K extends string | number, T> = {
[_ in K]: T;
}

interface Shape {
name: string;
width: number;
Expand All @@ -32,6 +16,8 @@ interface Point {
y: number;
}

// Constraint checking

type T00 = { [P in P]: string }; // Error
type T01 = { [P in Date]: number }; // Error
type T02 = Record<Date, number>; // Error
Expand All @@ -58,6 +44,27 @@ function f3<T extends keyof Shape>(x: T) {

function f4<T extends keyof Named>(x: T) {
let y: Pick<Shape, T>;
}

// Type identity checking

function f10<T>() {
type K = keyof T;
var x: { [P in keyof T]: T[P] };
var x: { [Q in keyof T]: T[Q] };
var x: { [R in K]: T[R] };
}

function f11<T>() {
var x: { [P in keyof T]: T[P] };
var x: { [P in keyof T]?: T[P] }; // Error
var x: { readonly [P in keyof T]: T[P] }; // Error
var x: { readonly [P in keyof T]?: T[P] }; // Error
}

function f12<T>() {
var x: { [P in keyof T]: T[P] };
var x: { [P in keyof T]: T[P][] }; // Error
}

//// [mappedTypeErrors.js]
Expand All @@ -73,21 +80,25 @@ function f3(x) {
function f4(x) {
var y;
}
// Type identity checking
function f10() {
var x;
var x;
var x;
}
function f11() {
var x;
var x; // Error
var x; // Error
var x; // Error
}
function f12() {
var x;
var x; // Error
}


//// [mappedTypeErrors.d.ts]
declare type Partial<T> = {
[P in keyof T]?: T[P];
};
declare type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
declare type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
declare type Record<K extends string | number, T> = {
[_ in K]: T;
};
interface Shape {
name: string;
width: number;
Expand Down Expand Up @@ -119,3 +130,6 @@ declare function f1<T>(x: T): void;
declare function f2<T extends string | number>(x: T): void;
declare function f3<T extends keyof Shape>(x: T): void;
declare function f4<T extends keyof Named>(x: T): void;
declare function f10<T>(): void;
declare function f11<T>(): void;
declare function f12<T>(): void;
Loading

0 comments on commit 0ba2348

Please sign in to comment.