Skip to content

Improve generic mapped type relations #13448

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

Merged
merged 3 commits into from
Jan 12, 2017
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
35 changes: 15 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4662,10 +4662,6 @@ namespace ts {
return type.modifiersType;
}

function getErasedTemplateTypeFromMappedType(type: MappedType) {
return instantiateType(getTemplateTypeFromMappedType(type), createTypeEraser([getTypeParameterFromMappedType(type)]));
}

function isGenericMappedType(type: Type) {
if (getObjectFlags(type) & ObjectFlags.Mapped) {
const constraintType = getConstraintTypeFromMappedType(<MappedType>type);
Expand Down Expand Up @@ -7765,25 +7761,24 @@ 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.
// A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
// that S and T are contra-variant whereas X and Y are co-variant.
function mappedTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
if (isGenericMappedType(target)) {
if (isGenericMappedType(source)) {
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);
}
const sourceReadonly = !!(<MappedType>source).declaration.readonlyToken;
const sourceOptional = !!(<MappedType>source).declaration.questionToken;
const targetReadonly = !!(<MappedType>target).declaration.readonlyToken;
const targetOptional = !!(<MappedType>target).declaration.questionToken;
const modifiersRelated = relation === identityRelation ?
sourceReadonly === targetReadonly && sourceOptional === targetOptional :
relation === comparableRelation || !sourceOptional || targetOptional;
if (modifiersRelated) {
let result: Ternary;
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(<MappedType>source)], [getTypeParameterFromMappedType(<MappedType>target)]);
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(<MappedType>source), mapper), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
}
}
}
Expand Down
104 changes: 102 additions & 2 deletions tests/baselines/reference/mappedTypeRelationships.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,24 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(62,5): error TS2
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(67,5): error TS2542: Index signature in type 'Readonly<U>' only permits reading.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(71,5): error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(76,5): error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(126,5): error TS2322: Type 'Partial<U>' is not assignable to type 'Identity<U>'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(142,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
Type 'T[P]' is not assignable to type 'U[P]'.
Type 'T' is not assignable to type 'U'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(147,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
Type 'keyof U' is not assignable to type 'keyof T'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(152,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: T[P]; }'.
Type 'keyof T' is not assignable to type 'K'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(157,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
Type 'keyof U' is not assignable to type 'K'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(162,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
Type 'keyof T' is not assignable to type 'K'.
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(167,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in K]: U[P]; }'.
Type 'T[P]' is not assignable to type 'U[P]'.
Type 'T' is not assignable to type 'U'.


==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (20 errors) ====
==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (27 errors) ====

function f1<T>(x: T, k: keyof T) {
return x[k];
Expand Down Expand Up @@ -190,4 +205,89 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(76,5): error TS2
function f51<T extends ItemMap, K extends keyof T>(obj: T, key: K) {
let item: Item = obj[key];
return obj[key].name;
}
}

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

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

function f60<U>(x: T1<U>, y: T2<U>) {
x = y;
y = x;
}

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

function f61<U>(x: Identity<U>, y: Partial<U>) {
x = y; // Error
~
!!! error TS2322: Type 'Partial<U>' is not assignable to type 'Identity<U>'.
y = x;
}

function f62<U>(x: Identity<U>, y: Readonly<U>) {
x = y;
y = x;
}

function f70<T>(x: { [P in keyof T]: T[P] }, y: { [P in keyof T]: T[P] }) {
x = y;
y = x;
}

function f71<T, U extends T>(x: { [P in keyof T]: T[P] }, y: { [P in keyof T]: U[P] }) {
x = y;
y = x; // Error
~
!!! error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
!!! error TS2322: Type 'T[P]' is not assignable to type 'U[P]'.
!!! error TS2322: Type 'T' is not assignable to type 'U'.
}

function f72<T, U extends T>(x: { [P in keyof T]: T[P] }, y: { [P in keyof U]: U[P] }) {
x = y;
y = x; // Error
~
!!! error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
!!! error TS2322: Type 'keyof U' is not assignable to type 'keyof T'.
}

function f73<T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in keyof T]: T[P] }) {
x = y;
y = x; // Error
~
!!! error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: T[P]; }'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'K'.
}

function f74<T, U extends T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in keyof U]: U[P] }) {
x = y;
y = x; // Error
~
!!! error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'.
!!! error TS2322: Type 'keyof U' is not assignable to type 'K'.
}

function f75<T, U extends T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in keyof T]: U[P] }) {
x = y;
y = x; // Error
~
!!! error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'K'.
}

function f76<T, U extends T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in K]: U[P] }) {
x = y;
y = x; // Error
~
!!! error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in K]: U[P]; }'.
!!! error TS2322: Type 'T[P]' is not assignable to type 'U[P]'.
!!! error TS2322: Type 'T' is not assignable to type 'U'.
}

152 changes: 151 additions & 1 deletion tests/baselines/reference/mappedTypeRelationships.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,70 @@ function f50<T extends ItemMap>(obj: T, key: keyof T) {
function f51<T extends ItemMap, K extends keyof T>(obj: T, key: K) {
let item: Item = obj[key];
return obj[key].name;
}
}

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

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

function f60<U>(x: T1<U>, y: T2<U>) {
x = y;
y = x;
}

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

function f61<U>(x: Identity<U>, y: Partial<U>) {
x = y; // Error
y = x;
}

function f62<U>(x: Identity<U>, y: Readonly<U>) {
x = y;
y = x;
}

function f70<T>(x: { [P in keyof T]: T[P] }, y: { [P in keyof T]: T[P] }) {
x = y;
y = x;
}

function f71<T, U extends T>(x: { [P in keyof T]: T[P] }, y: { [P in keyof T]: U[P] }) {
x = y;
y = x; // Error
}

function f72<T, U extends T>(x: { [P in keyof T]: T[P] }, y: { [P in keyof U]: U[P] }) {
x = y;
y = x; // Error
}

function f73<T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in keyof T]: T[P] }) {
x = y;
y = x; // Error
}

function f74<T, U extends T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in keyof U]: U[P] }) {
x = y;
y = x; // Error
}

function f75<T, U extends T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in keyof T]: U[P] }) {
x = y;
y = x; // Error
}

function f76<T, U extends T, K extends keyof T>(x: { [P in K]: T[P] }, y: { [P in K]: U[P] }) {
x = y;
y = x; // Error
}


//// [mappedTypeRelationships.js]
function f1(x, k) {
Expand Down Expand Up @@ -185,6 +248,46 @@ function f51(obj, key) {
var item = obj[key];
return obj[key].name;
}
function f60(x, y) {
x = y;
y = x;
}
function f61(x, y) {
x = y; // Error
y = x;
}
function f62(x, y) {
x = y;
y = x;
}
function f70(x, y) {
x = y;
y = x;
}
function f71(x, y) {
x = y;
y = x; // Error
}
function f72(x, y) {
x = y;
y = x; // Error
}
function f73(x, y) {
x = y;
y = x; // Error
}
function f74(x, y) {
x = y;
y = x; // Error
}
function f75(x, y) {
x = y;
y = x; // Error
}
function f76(x, y) {
x = y;
y = x; // Error
}


//// [mappedTypeRelationships.d.ts]
Expand Down Expand Up @@ -214,3 +317,50 @@ declare type ItemMap = {
};
declare function f50<T extends ItemMap>(obj: T, key: keyof T): string;
declare function f51<T extends ItemMap, K extends keyof T>(obj: T, key: K): string;
declare type T1<T> = {
[P in keyof T]: T[P];
};
declare type T2<T> = {
[P in keyof T]: T[P];
};
declare function f60<U>(x: T1<U>, y: T2<U>): void;
declare type Identity<T> = {
[P in keyof T]: T[P];
};
declare function f61<U>(x: Identity<U>, y: Partial<U>): void;
declare function f62<U>(x: Identity<U>, y: Readonly<U>): void;
declare function f70<T>(x: {
[P in keyof T]: T[P];
}, y: {
[P in keyof T]: T[P];
}): void;
declare function f71<T, U extends T>(x: {
[P in keyof T]: T[P];
}, y: {
[P in keyof T]: U[P];
}): void;
declare function f72<T, U extends T>(x: {
[P in keyof T]: T[P];
}, y: {
[P in keyof U]: U[P];
}): void;
declare function f73<T, K extends keyof T>(x: {
[P in K]: T[P];
}, y: {
[P in keyof T]: T[P];
}): void;
declare function f74<T, U extends T, K extends keyof T>(x: {
[P in K]: T[P];
}, y: {
[P in keyof U]: U[P];
}): void;
declare function f75<T, U extends T, K extends keyof T>(x: {
[P in K]: T[P];
}, y: {
[P in keyof T]: U[P];
}): void;
declare function f76<T, U extends T, K extends keyof T>(x: {
[P in K]: T[P];
}, y: {
[P in K]: U[P];
}): void;
Loading