From 565ab7a9710dd0d83b0b044e92ec3a564a9ff8d5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 21 Dec 2018 12:50:53 -0800 Subject: [PATCH 1/3] Properly compute lower bound of key type in a mapped type --- src/compiler/checker.ts | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 23c5568f2e5f4..ce35ddd522d27 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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((type).type)); + } + if (type.flags & TypeFlags.Conditional) { + return getLowerBoundOfConditionalType(type); + } + if (type.flags & TypeFlags.Union) { + return getUnionType(sameMap((type).types, getLowerBoundOfKeyType)); + } + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(sameMap((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((constraintType).type)) : constraintType; - forEachType(iterationType, addMemberForKeyType); + forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); } setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); From 7ad2661625633f932c76eaca6daca00b0835ce65 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 21 Dec 2018 12:51:01 -0800 Subject: [PATCH 2/3] Add tests --- .../types/mapped/mappedTypeConstraints.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/cases/conformance/types/mapped/mappedTypeConstraints.ts diff --git a/tests/cases/conformance/types/mapped/mappedTypeConstraints.ts b/tests/cases/conformance/types/mapped/mappedTypeConstraints.ts new file mode 100644 index 0000000000000..3710f2b9bef10 --- /dev/null +++ b/tests/cases/conformance/types/mapped/mappedTypeConstraints.ts @@ -0,0 +1,36 @@ +// @strict: true + +function f0(obj: Pick>) { + obj.b; +} + +function f1(obj: Pick>) { + obj.b; +} + +function f2(obj: Pick) { + obj.b; +} + +function f3(obj: Pick) { + obj.a; + obj.b; + obj.c; +} + +function f4(obj: Record | 'c', string>) { + obj.a; + obj.c; +} + +// Repro from #28821 + +type TargetProps = { + foo: string, + bar: string +}; + +const modifier = (targetProps: T) => { + let {bar, ...rest} = targetProps; + rest.foo; +}; From 194496f5ade09001e1973711d6abaa08a5c09599 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 21 Dec 2018 12:51:20 -0800 Subject: [PATCH 3/3] Accept new baselines --- .../reference/mappedTypeConstraints.js | 70 +++++++++ .../reference/mappedTypeConstraints.symbols | 140 ++++++++++++++++++ .../reference/mappedTypeConstraints.types | 110 ++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeConstraints.js create mode 100644 tests/baselines/reference/mappedTypeConstraints.symbols create mode 100644 tests/baselines/reference/mappedTypeConstraints.types diff --git a/tests/baselines/reference/mappedTypeConstraints.js b/tests/baselines/reference/mappedTypeConstraints.js new file mode 100644 index 0000000000000..0565af1c6ecd2 --- /dev/null +++ b/tests/baselines/reference/mappedTypeConstraints.js @@ -0,0 +1,70 @@ +//// [mappedTypeConstraints.ts] +function f0(obj: Pick>) { + obj.b; +} + +function f1(obj: Pick>) { + obj.b; +} + +function f2(obj: Pick) { + obj.b; +} + +function f3(obj: Pick) { + obj.a; + obj.b; + obj.c; +} + +function f4(obj: Record | 'c', string>) { + obj.a; + obj.c; +} + +// Repro from #28821 + +type TargetProps = { + foo: string, + bar: string +}; + +const modifier = (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; +}; diff --git a/tests/baselines/reference/mappedTypeConstraints.symbols b/tests/baselines/reference/mappedTypeConstraints.symbols new file mode 100644 index 0000000000000..3601ffc3de848 --- /dev/null +++ b/tests/baselines/reference/mappedTypeConstraints.symbols @@ -0,0 +1,140 @@ +=== tests/cases/conformance/types/mapped/mappedTypeConstraints.ts === +function f0(obj: Pick>) { +>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(obj: Pick>) { +>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(obj: Pick) { +>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(obj: Pick) { +>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(obj: Record | '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 = (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)) + +}; + diff --git a/tests/baselines/reference/mappedTypeConstraints.types b/tests/baselines/reference/mappedTypeConstraints.types new file mode 100644 index 0000000000000..3e42e2a2e9800 --- /dev/null +++ b/tests/baselines/reference/mappedTypeConstraints.types @@ -0,0 +1,110 @@ +=== tests/cases/conformance/types/mapped/mappedTypeConstraints.ts === +function f0(obj: Pick>) { +>f0 : (obj: Pick>) => void +>a : string +>b : string +>obj : Pick> + + obj.b; +>obj.b : T["b"] +>obj : Pick> +>b : T["b"] +} + +function f1(obj: Pick>) { +>f1 : (obj: Pick>) => void +>a : string +>b : string +>obj : Pick> + + obj.b; +>obj.b : T["b"] +>obj : Pick> +>b : T["b"] +} + +function f2(obj: Pick) { +>f2 : (obj: Pick) => void +>a : string +>b : string +>b : string +>c : string +>obj : Pick + + obj.b; +>obj.b : (T | U)["b"] +>obj : Pick +>b : (T | U)["b"] +} + +function f3(obj: Pick) { +>f3 : (obj: Pick) => void +>a : string +>b : string +>b : string +>c : string +>obj : Pick + + obj.a; +>obj.a : (T & U)["a"] +>obj : Pick +>a : (T & U)["a"] + + obj.b; +>obj.b : (T & U)["b"] +>obj : Pick +>b : (T & U)["b"] + + obj.c; +>obj.c : (T & U)["c"] +>obj : Pick +>c : (T & U)["c"] +} + +function f4(obj: Record | 'c', string>) { +>f4 : (obj: Record<"c" | Exclude, string>) => void +>a : string +>b : string +>obj : Record<"c" | Exclude, string> + + obj.a; +>obj.a : string +>obj : Record<"c" | Exclude, string> +>a : string + + obj.c; +>obj.c : string +>obj : Record<"c" | Exclude, string> +>c : string +} + +// Repro from #28821 + +type TargetProps = { +>TargetProps : TargetProps + + foo: string, +>foo : string + + bar: string +>bar : string + +}; + +const modifier = (targetProps: T) => { +>modifier : (targetProps: T) => void +>(targetProps: T) => { let {bar, ...rest} = targetProps; rest.foo;} : (targetProps: T) => void +>targetProps : T + + let {bar, ...rest} = targetProps; +>bar : string +>rest : Pick> +>targetProps : T + + rest.foo; +>rest.foo : T["foo"] +>rest : Pick> +>foo : T["foo"] + +}; +