Merge pull request #29121 from Microsoft/mappedTypeConstraints

Improve constraints for non-homomorphic mapped types
This commit is contained in:
Anders Hejlsberg 2018-12-30 17:44:41 -10:00 committed by GitHub
commit 8570a67572
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 390 additions and 4 deletions

View File

@ -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();
@ -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);

View File

@ -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;
};

View File

@ -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))
};

View File

@ -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"]
};

View File

@ -0,0 +1,36 @@
// @strict: true
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;
};