Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Predefined mapped types in lib.d.ts #12276

Merged
merged 4 commits into from
Nov 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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