Skip to content

Improve constraints for non-homomorphic mapped types #29121

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
Dec 31, 2018
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
38 changes: 34 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7084,6 +7084,39 @@ namespace ts {
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
}

// Return the lower bound of the key type in a mapped type. Intuitively, the lower
// bound includes those keys that are known to always be present, for example because
// because of constraints on type parameters (e.g. 'keyof T' for a constrained T).
function getLowerBoundOfKeyType(type: Type): Type {
if (type.flags & (TypeFlags.Any | TypeFlags.Primitive)) {
return type;
}
if (type.flags & TypeFlags.Index) {
return getIndexType(getApparentType((<IndexType>type).type));
}
if (type.flags & TypeFlags.Conditional) {
return getLowerBoundOfConditionalType(<ConditionalType>type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
}
return neverType;
}

function getLowerBoundOfConditionalType(type: ConditionalType) {
if (type.root.isDistributive) {
const constraint = getLowerBoundOfKeyType(type.checkType);
if (constraint !== type.checkType) {
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
return getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
}
}
return type;
}

/** Resolve the members of a mapped type { [P in K]: T } */
function resolveMappedTypeMembers(type: MappedType) {
const members: SymbolTable = createSymbolTable();
Expand Down Expand Up @@ -7112,10 +7145,7 @@ namespace ts {
}
}
else {
// If the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X.
// Then iterate over the constituents of the key type.
const iterationType = constraintType.flags & TypeFlags.Index ? getIndexType(getApparentType((<IndexType>constraintType).type)) : constraintType;
forEachType(iterationType, addMemberForKeyType);
forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
}
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);

Expand Down
70 changes: 70 additions & 0 deletions tests/baselines/reference/mappedTypeConstraints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//// [mappedTypeConstraints.ts]
function f0<T extends { a: string, b: string }>(obj: Pick<T, Extract<keyof T, 'b'>>) {
obj.b;
}

function f1<T extends { a: string, b: string }>(obj: Pick<T, Exclude<keyof T, 'a'>>) {
obj.b;
}

function f2<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T | U, keyof (T | U)>) {
obj.b;
}

function f3<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T & U, keyof (T & U)>) {
obj.a;
obj.b;
obj.c;
}

function f4<T extends { a: string, b: string }>(obj: Record<Exclude<keyof T, 'b'> | 'c', string>) {
obj.a;
obj.c;
}

// Repro from #28821

type TargetProps = {
foo: string,
bar: string
};

const modifier = <T extends TargetProps>(targetProps: T) => {
let {bar, ...rest} = targetProps;
rest.foo;
};


//// [mappedTypeConstraints.js]
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
};
function f0(obj) {
obj.b;
}
function f1(obj) {
obj.b;
}
function f2(obj) {
obj.b;
}
function f3(obj) {
obj.a;
obj.b;
obj.c;
}
function f4(obj) {
obj.a;
obj.c;
}
var modifier = function (targetProps) {
var bar = targetProps.bar, rest = __rest(targetProps, ["bar"]);
rest.foo;
};
140 changes: 140 additions & 0 deletions tests/baselines/reference/mappedTypeConstraints.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
=== tests/cases/conformance/types/mapped/mappedTypeConstraints.ts ===
function f0<T extends { a: string, b: string }>(obj: Pick<T, Extract<keyof T, 'b'>>) {
>f0 : Symbol(f0, Decl(mappedTypeConstraints.ts, 0, 0))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 0, 12))
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 0, 23))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 0, 34))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 0, 48))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 0, 12))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 0, 12))

obj.b;
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 0, 34))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 0, 48))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 0, 34))
}

function f1<T extends { a: string, b: string }>(obj: Pick<T, Exclude<keyof T, 'a'>>) {
>f1 : Symbol(f1, Decl(mappedTypeConstraints.ts, 2, 1))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 4, 12))
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 4, 23))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 4, 34))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 4, 48))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 4, 12))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 4, 12))

obj.b;
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 4, 34))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 4, 48))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 4, 34))
}

function f2<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T | U, keyof (T | U)>) {
>f2 : Symbol(f2, Decl(mappedTypeConstraints.ts, 6, 1))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 8, 12))
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 8, 23))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 34))
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 8, 47))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 59))
>c : Symbol(c, Decl(mappedTypeConstraints.ts, 8, 70))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 8, 84))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 8, 12))
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 8, 47))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 8, 12))
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 8, 47))

obj.b;
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 34), Decl(mappedTypeConstraints.ts, 8, 59))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 8, 84))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 34), Decl(mappedTypeConstraints.ts, 8, 59))
}

function f3<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T & U, keyof (T & U)>) {
>f3 : Symbol(f3, Decl(mappedTypeConstraints.ts, 10, 1))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 12, 12))
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 12, 23))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 34))
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 12, 47))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 59))
>c : Symbol(c, Decl(mappedTypeConstraints.ts, 12, 70))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 12, 12))
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 12, 47))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 12, 12))
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 12, 47))

obj.a;
>obj.a : Symbol(a, Decl(mappedTypeConstraints.ts, 12, 23))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 12, 23))

obj.b;
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 34), Decl(mappedTypeConstraints.ts, 12, 59))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 34), Decl(mappedTypeConstraints.ts, 12, 59))

obj.c;
>obj.c : Symbol(c, Decl(mappedTypeConstraints.ts, 12, 70))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
>c : Symbol(c, Decl(mappedTypeConstraints.ts, 12, 70))
}

function f4<T extends { a: string, b: string }>(obj: Record<Exclude<keyof T, 'b'> | 'c', string>) {
>f4 : Symbol(f4, Decl(mappedTypeConstraints.ts, 16, 1))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 18, 12))
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 18, 23))
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 18, 34))
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 18, 48))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 18, 12))

obj.a;
>obj.a : Symbol(a)
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 18, 48))
>a : Symbol(a)

obj.c;
>obj.c : Symbol(c)
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 18, 48))
>c : Symbol(c)
}

// Repro from #28821

type TargetProps = {
>TargetProps : Symbol(TargetProps, Decl(mappedTypeConstraints.ts, 21, 1))

foo: string,
>foo : Symbol(foo, Decl(mappedTypeConstraints.ts, 25, 20))

bar: string
>bar : Symbol(bar, Decl(mappedTypeConstraints.ts, 26, 16))

};

const modifier = <T extends TargetProps>(targetProps: T) => {
>modifier : Symbol(modifier, Decl(mappedTypeConstraints.ts, 30, 5))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 30, 18))
>TargetProps : Symbol(TargetProps, Decl(mappedTypeConstraints.ts, 21, 1))
>targetProps : Symbol(targetProps, Decl(mappedTypeConstraints.ts, 30, 41))
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 30, 18))

let {bar, ...rest} = targetProps;
>bar : Symbol(bar, Decl(mappedTypeConstraints.ts, 31, 9))
>rest : Symbol(rest, Decl(mappedTypeConstraints.ts, 31, 13))
>targetProps : Symbol(targetProps, Decl(mappedTypeConstraints.ts, 30, 41))

rest.foo;
>rest.foo : Symbol(foo, Decl(mappedTypeConstraints.ts, 25, 20))
>rest : Symbol(rest, Decl(mappedTypeConstraints.ts, 31, 13))
>foo : Symbol(foo, Decl(mappedTypeConstraints.ts, 25, 20))

};

110 changes: 110 additions & 0 deletions tests/baselines/reference/mappedTypeConstraints.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
=== tests/cases/conformance/types/mapped/mappedTypeConstraints.ts ===
function f0<T extends { a: string, b: string }>(obj: Pick<T, Extract<keyof T, 'b'>>) {
>f0 : <T extends { a: string; b: string; }>(obj: Pick<T, Extract<keyof T, "b">>) => void
>a : string
>b : string
>obj : Pick<T, Extract<keyof T, "b">>

obj.b;
>obj.b : T["b"]
>obj : Pick<T, Extract<keyof T, "b">>
>b : T["b"]
}

function f1<T extends { a: string, b: string }>(obj: Pick<T, Exclude<keyof T, 'a'>>) {
>f1 : <T extends { a: string; b: string; }>(obj: Pick<T, Exclude<keyof T, "a">>) => void
>a : string
>b : string
>obj : Pick<T, Exclude<keyof T, "a">>

obj.b;
>obj.b : T["b"]
>obj : Pick<T, Exclude<keyof T, "a">>
>b : T["b"]
}

function f2<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T | U, keyof (T | U)>) {
>f2 : <T extends { a: string; b: string; }, U extends { b: string; c: string; }>(obj: Pick<T | U, keyof T & keyof U>) => void
>a : string
>b : string
>b : string
>c : string
>obj : Pick<T | U, keyof T & keyof U>

obj.b;
>obj.b : (T | U)["b"]
>obj : Pick<T | U, keyof T & keyof U>
>b : (T | U)["b"]
}

function f3<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T & U, keyof (T & U)>) {
>f3 : <T extends { a: string; b: string; }, U extends { b: string; c: string; }>(obj: Pick<T & U, keyof T | keyof U>) => void
>a : string
>b : string
>b : string
>c : string
>obj : Pick<T & U, keyof T | keyof U>

obj.a;
>obj.a : (T & U)["a"]
>obj : Pick<T & U, keyof T | keyof U>
>a : (T & U)["a"]

obj.b;
>obj.b : (T & U)["b"]
>obj : Pick<T & U, keyof T | keyof U>
>b : (T & U)["b"]

obj.c;
>obj.c : (T & U)["c"]
>obj : Pick<T & U, keyof T | keyof U>
>c : (T & U)["c"]
}

function f4<T extends { a: string, b: string }>(obj: Record<Exclude<keyof T, 'b'> | 'c', string>) {
>f4 : <T extends { a: string; b: string; }>(obj: Record<"c" | Exclude<keyof T, "b">, string>) => void
>a : string
>b : string
>obj : Record<"c" | Exclude<keyof T, "b">, string>

obj.a;
>obj.a : string
>obj : Record<"c" | Exclude<keyof T, "b">, string>
>a : string

obj.c;
>obj.c : string
>obj : Record<"c" | Exclude<keyof T, "b">, string>
>c : string
}

// Repro from #28821

type TargetProps = {
>TargetProps : TargetProps

foo: string,
>foo : string

bar: string
>bar : string

};

const modifier = <T extends TargetProps>(targetProps: T) => {
>modifier : <T extends TargetProps>(targetProps: T) => void
><T extends TargetProps>(targetProps: T) => { let {bar, ...rest} = targetProps; rest.foo;} : <T extends TargetProps>(targetProps: T) => void
>targetProps : T

let {bar, ...rest} = targetProps;
>bar : string
>rest : Pick<T, Exclude<keyof T, "bar">>
>targetProps : T

rest.foo;
>rest.foo : T["foo"]
>rest : Pick<T, Exclude<keyof T, "bar">>
>foo : T["foo"]

};

Loading