Skip to content

Merge master11232016 2 #12479

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 6 commits into from
Nov 24, 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
62 changes: 48 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6495,7 +6495,36 @@ namespace ts {
return result;
}

function instantiateMappedType(type: MappedType, mapper: TypeMapper): MappedType {
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
// Check if we have an isomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some
// type parameter T. If so, the mapped type is distributive over a union type and when T is instantiated
// to a union type A | B, we produce { [P in keyof A]: X } | { [P in keyof B]: X }. Furthermore, for
// isomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a
// union type A | undefined, we produce { [P in keyof A]: X } | undefined.
const constraintType = getConstraintTypeFromMappedType(type);
if (constraintType.flags & TypeFlags.Index) {
const typeParameter = (<IndexType>constraintType).type;
const mappedTypeParameter = mapper(typeParameter);
if (typeParameter !== mappedTypeParameter) {
return mapType(mappedTypeParameter, t => {
if (isMappableType(t)) {
const replacementMapper = createUnaryTypeMapper(typeParameter, t);
const combinedMapper = mapper.mappedTypes && mapper.mappedTypes.length === 1 ? replacementMapper : combineTypeMappers(replacementMapper, mapper);
combinedMapper.mappedTypes = mapper.mappedTypes;
return instantiateMappedObjectType(type, combinedMapper);
}
return t;
});
}
}
return instantiateMappedObjectType(type, mapper);
}

function isMappableType(type: Type) {
return type.flags & (TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.Intersection | TypeFlags.IndexedAccess);
}

function instantiateMappedObjectType(type: MappedType, mapper: TypeMapper): Type {
const result = <MappedType>createObjectType(ObjectFlags.Mapped | ObjectFlags.Instantiated, type.symbol);
result.declaration = type.declaration;
result.mapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper;
Expand Down Expand Up @@ -7537,25 +7566,30 @@ namespace ts {

// 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);
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);
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);
}
}
}
}
}
else if (relation !== identityRelation && isEmptyObjectType(resolveStructuredTypeMembers(<ObjectType>target))) {
return Ternary.True;
}
return Ternary.False;
}

Expand Down
35 changes: 10 additions & 25 deletions tests/baselines/reference/mappedTypes1.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@ type T47 = { [P in string | "a" | "b" | "0" | "1"]: void };
declare function f1<T1>(): { [P in keyof T1]: void };
declare function f2<T1 extends string>(): { [P in keyof T1]: void };
declare function f3<T1 extends number>(): { [P in keyof T1]: void };
declare function f4<T1 extends Number>(): { [P in keyof T1]: void };

let x1 = f1();
let x2 = f2();
let x3 = f3();
let x3 = f3();
let x4 = f4();

//// [mappedTypes1.js]
var x1 = f1();
var x2 = f2();
var x3 = f3();
var x4 = f4();


//// [mappedTypes1.d.ts]
Expand Down Expand Up @@ -128,31 +131,13 @@ declare function f2<T1 extends string>(): {
declare function f3<T1 extends number>(): {
[P in keyof T1]: void;
};
declare let x1: {};
declare let x2: {
toString: void;
charAt: void;
charCodeAt: void;
concat: void;
indexOf: void;
lastIndexOf: void;
localeCompare: void;
match: void;
replace: void;
search: void;
slice: void;
split: void;
substring: void;
toLowerCase: void;
toLocaleLowerCase: void;
toUpperCase: void;
toLocaleUpperCase: void;
trim: void;
length: void;
substr: void;
valueOf: void;
declare function f4<T1 extends Number>(): {
[P in keyof T1]: void;
};
declare let x3: {
declare let x1: {};
declare let x2: string;
declare let x3: number;
declare let x4: {
toString: void;
valueOf: void;
toFixed: void;
Expand Down
17 changes: 14 additions & 3 deletions tests/baselines/reference/mappedTypes1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,26 @@ declare function f3<T1 extends number>(): { [P in keyof T1]: void };
>P : Symbol(P, Decl(mappedTypes1.ts, 33, 45))
>T1 : Symbol(T1, Decl(mappedTypes1.ts, 33, 20))

declare function f4<T1 extends Number>(): { [P in keyof T1]: void };
>f4 : Symbol(f4, Decl(mappedTypes1.ts, 33, 68))
>T1 : Symbol(T1, Decl(mappedTypes1.ts, 34, 20))
>Number : Symbol(Number, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypes1.ts, 34, 45))
>T1 : Symbol(T1, Decl(mappedTypes1.ts, 34, 20))

let x1 = f1();
>x1 : Symbol(x1, Decl(mappedTypes1.ts, 35, 3))
>x1 : Symbol(x1, Decl(mappedTypes1.ts, 36, 3))
>f1 : Symbol(f1, Decl(mappedTypes1.ts, 29, 59))

let x2 = f2();
>x2 : Symbol(x2, Decl(mappedTypes1.ts, 36, 3))
>x2 : Symbol(x2, Decl(mappedTypes1.ts, 37, 3))
>f2 : Symbol(f2, Decl(mappedTypes1.ts, 31, 53))

let x3 = f3();
>x3 : Symbol(x3, Decl(mappedTypes1.ts, 37, 3))
>x3 : Symbol(x3, Decl(mappedTypes1.ts, 38, 3))
>f3 : Symbol(f3, Decl(mappedTypes1.ts, 32, 68))

let x4 = f4();
>x4 : Symbol(x4, Decl(mappedTypes1.ts, 39, 3))
>f4 : Symbol(f4, Decl(mappedTypes1.ts, 33, 68))

20 changes: 16 additions & 4 deletions tests/baselines/reference/mappedTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,30 @@ declare function f3<T1 extends number>(): { [P in keyof T1]: void };
>P : P
>T1 : T1

declare function f4<T1 extends Number>(): { [P in keyof T1]: void };
>f4 : <T1 extends Number>() => { [P in keyof T1]: void; }
>T1 : T1
>Number : Number
>P : P
>T1 : T1

let x1 = f1();
>x1 : {}
>f1() : {}
>f1 : <T1>() => { [P in keyof T1]: void; }

let x2 = f2();
>x2 : { toString: void; charAt: void; charCodeAt: void; concat: void; indexOf: void; lastIndexOf: void; localeCompare: void; match: void; replace: void; search: void; slice: void; split: void; substring: void; toLowerCase: void; toLocaleLowerCase: void; toUpperCase: void; toLocaleUpperCase: void; trim: void; length: void; substr: void; valueOf: void; }
>f2() : { toString: void; charAt: void; charCodeAt: void; concat: void; indexOf: void; lastIndexOf: void; localeCompare: void; match: void; replace: void; search: void; slice: void; split: void; substring: void; toLowerCase: void; toLocaleLowerCase: void; toUpperCase: void; toLocaleUpperCase: void; trim: void; length: void; substr: void; valueOf: void; }
>x2 : string
>f2() : string
>f2 : <T1 extends string>() => { [P in keyof T1]: void; }

let x3 = f3();
>x3 : { toString: void; valueOf: void; toFixed: void; toExponential: void; toPrecision: void; toLocaleString: void; }
>f3() : { toString: void; valueOf: void; toFixed: void; toExponential: void; toPrecision: void; toLocaleString: void; }
>x3 : number
>f3() : number
>f3 : <T1 extends number>() => { [P in keyof T1]: void; }

let x4 = f4();
>x4 : { toString: void; valueOf: void; toFixed: void; toExponential: void; toPrecision: void; toLocaleString: void; }
>f4() : { toString: void; valueOf: void; toFixed: void; toExponential: void; toPrecision: void; toLocaleString: void; }
>f4 : <T1 extends Number>() => { [P in keyof T1]: void; }

41 changes: 25 additions & 16 deletions tests/baselines/reference/mappedTypes2.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,30 @@ declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;
declare function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>;
declare function proxify<T>(obj: T): Proxify<T>;

interface Point {
x: number;
y: number;
}

interface Shape {
name: string;
width: number;
height: number;
visible: boolean;
location: Point;
}

interface PartialShape {
name?: string;
width?: number;
height?: number;
visible?: boolean;
location?: Point;
}

interface ReadonlyShape {
readonly name: string;
readonly width: number;
readonly height: number;
readonly visible: boolean;
readonly location: Point;
}

function f0(s1: Shape, s2: Shape) {
Expand All @@ -69,7 +74,7 @@ function f2(shape: Shape) {
}

function f3(shape: Shape) {
const x = pick(shape, "name", "visible"); // { name: string, visible: boolean }
const x = pick(shape, "name", "location"); // { name: string, location: Point }
}

function f4() {
Expand All @@ -80,13 +85,13 @@ function f4() {
function f5(shape: Shape) {
const p = proxify(shape);
let name = p.name.get();
p.visible.set(false);
p.width.set(42);
}

function f6(shape: DeepReadonly<Shape>) {
let name = shape.name; // DeepReadonly<string>
let length = name.length; // DeepReadonly<number>
let toString = length.toString; // DeepReadonly<(radix?: number) => string>
let name = shape.name; // string
let location = shape.location; // DeepReadonly<Point>
let x = location.x; // number
}

//// [mappedTypes2.js]
Expand Down Expand Up @@ -115,7 +120,7 @@ function f2(shape) {
var partial = {};
}
function f3(shape) {
var x = pick(shape, "name", "visible"); // { name: string, visible: boolean }
var x = pick(shape, "name", "location"); // { name: string, location: Point }
}
function f4() {
var rec = { foo: "hello", bar: "world", baz: "bye" };
Expand All @@ -124,12 +129,12 @@ function f4() {
function f5(shape) {
var p = proxify(shape);
var name = p.name.get();
p.visible.set(false);
p.width.set(42);
}
function f6(shape) {
var name = shape.name; // DeepReadonly<string>
var length = name.length; // DeepReadonly<number>
var toString = length.toString; // DeepReadonly<(radix?: number) => string>
var name = shape.name; // string
var location = shape.location; // DeepReadonly<Point>
var x = location.x; // number
}


Expand All @@ -150,23 +155,27 @@ declare function freeze<T>(obj: T): Readonly<T>;
declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;
declare function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>;
declare function proxify<T>(obj: T): Proxify<T>;
interface Point {
x: number;
y: number;
}
interface Shape {
name: string;
width: number;
height: number;
visible: boolean;
location: Point;
}
interface PartialShape {
name?: string;
width?: number;
height?: number;
visible?: boolean;
location?: Point;
}
interface ReadonlyShape {
readonly name: string;
readonly width: number;
readonly height: number;
readonly visible: boolean;
readonly location: Point;
}
declare function f0(s1: Shape, s2: Shape): void;
declare function f1(shape: Shape): void;
Expand Down
Loading