Skip to content

Commit 85dbc04

Browse files
authored
Merge pull request #28312 from Microsoft/genericRest
Generic object rest variables and parameters
2 parents b28e411 + f9dd445 commit 85dbc04

13 files changed

+418
-86
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,8 @@ namespace ts {
520520
let deferredGlobalTemplateStringsArrayType: ObjectType;
521521
let deferredGlobalImportMetaType: ObjectType;
522522
let deferredGlobalExtractSymbol: Symbol;
523+
let deferredGlobalExcludeSymbol: Symbol;
524+
let deferredGlobalPickSymbol: Symbol;
523525
let deferredGlobalBigIntType: ObjectType;
524526

525527
const allPotentiallyUnusedIdentifiers = createMap<PotentiallyUnusedIdentifier[]>(); // key is file name
@@ -4622,18 +4624,25 @@ namespace ts {
46224624
if (source.flags & TypeFlags.Never) {
46234625
return emptyObjectType;
46244626
}
4625-
46264627
if (source.flags & TypeFlags.Union) {
46274628
return mapType(source, t => getRestType(t, properties, symbol));
46284629
}
4629-
4630-
const members = createSymbolTable();
4631-
const names = createUnderscoreEscapedMap<true>();
4632-
for (const name of properties) {
4633-
names.set(getTextOfPropertyName(name), true);
4630+
const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName));
4631+
if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) {
4632+
if (omitKeyType.flags & TypeFlags.Never) {
4633+
return source;
4634+
}
4635+
const pickTypeAlias = getGlobalPickSymbol();
4636+
const excludeTypeAlias = getGlobalExcludeSymbol();
4637+
if (!pickTypeAlias || !excludeTypeAlias) {
4638+
return errorType;
4639+
}
4640+
const pickKeys = getTypeAliasInstantiation(excludeTypeAlias, [getIndexType(source), omitKeyType]);
4641+
return getTypeAliasInstantiation(pickTypeAlias, [source, pickKeys]);
46344642
}
4643+
const members = createSymbolTable();
46354644
for (const prop of getPropertiesOfType(source)) {
4636-
if (!names.has(prop.escapedName)
4645+
if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType)
46374646
&& !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected))
46384647
&& isSpreadableProperty(prop)) {
46394648
members.set(prop.escapedName, getSpreadSymbol(prop));
@@ -4669,7 +4678,7 @@ namespace ts {
46694678
let type: Type | undefined;
46704679
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
46714680
if (declaration.dotDotDotToken) {
4672-
if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType) || isGenericObjectType(parentType)) {
4681+
if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) {
46734682
error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types);
46744683
return errorType;
46754684
}
@@ -4684,12 +4693,8 @@ namespace ts {
46844693
else {
46854694
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
46864695
const name = declaration.propertyName || <Identifier>declaration.name;
4687-
const exprType = isComputedPropertyName(name)
4688-
? checkComputedPropertyName(name)
4689-
: isIdentifier(name)
4690-
? getLiteralType(unescapeLeadingUnderscores(name.escapedText))
4691-
: checkExpression(name);
4692-
const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(getApparentType(parentType), exprType, name), name);
4696+
const exprType = getLiteralTypeFromPropertyName(name);
4697+
const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(parentType, exprType, name), name);
46934698
type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name));
46944699
}
46954700
}
@@ -6825,7 +6830,7 @@ namespace ts {
68256830
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
68266831
// We have a { [P in keyof T]: X }
68276832
for (const prop of getPropertiesOfType(modifiersType)) {
6828-
addMemberForKeyType(getLiteralTypeFromPropertyName(prop, include));
6833+
addMemberForKeyType(getLiteralTypeFromProperty(prop, include));
68296834
}
68306835
if (modifiersType.flags & TypeFlags.Any || getIndexInfoOfType(modifiersType, IndexKind.String)) {
68316836
addMemberForKeyType(stringType);
@@ -8677,6 +8682,14 @@ namespace ts {
86778682
return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = getGlobalSymbol("Extract" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
86788683
}
86798684

8685+
function getGlobalExcludeSymbol(): Symbol {
8686+
return deferredGlobalExcludeSymbol || (deferredGlobalExcludeSymbol = getGlobalSymbol("Exclude" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
8687+
}
8688+
8689+
function getGlobalPickSymbol(): Symbol {
8690+
return deferredGlobalPickSymbol || (deferredGlobalPickSymbol = getGlobalSymbol("Pick" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
8691+
}
8692+
86808693
function getGlobalBigIntType(reportErrors: boolean) {
86818694
return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
86828695
}
@@ -9268,21 +9281,24 @@ namespace ts {
92689281
type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false));
92699282
}
92709283

9284+
function getLiteralTypeFromPropertyName(name: PropertyName) {
9285+
return isIdentifier(name) ? getLiteralType(unescapeLeadingUnderscores(name.escapedText)) :
9286+
getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name));
9287+
}
9288+
92719289
function getBigIntLiteralType(node: BigIntLiteral): LiteralType {
92729290
return getLiteralType({
92739291
negative: false,
92749292
base10Value: parsePseudoBigInt(node.text)
92759293
});
92769294
}
92779295

9278-
function getLiteralTypeFromPropertyName(prop: Symbol, include: TypeFlags) {
9296+
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags) {
92799297
if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
92809298
let type = getLateBoundSymbol(prop).nameType;
92819299
if (!type && !isKnownSymbol(prop)) {
9282-
const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration);
9283-
type = name && isNumericLiteral(name) ? getLiteralType(+name.text) :
9284-
name && name.kind === SyntaxKind.ComputedPropertyName && isNumericLiteral(name.expression) ? getLiteralType(+name.expression.text) :
9285-
getLiteralType(symbolName(prop));
9300+
const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration) as PropertyName;
9301+
type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop));
92869302
}
92879303
if (type && type.flags & include) {
92889304
return type;
@@ -9291,8 +9307,8 @@ namespace ts {
92919307
return neverType;
92929308
}
92939309

9294-
function getLiteralTypeFromPropertyNames(type: Type, include: TypeFlags) {
9295-
return getUnionType(map(getPropertiesOfType(type), t => getLiteralTypeFromPropertyName(t, include)));
9310+
function getLiteralTypeFromProperties(type: Type, include: TypeFlags) {
9311+
return getUnionType(map(getPropertiesOfType(type), t => getLiteralTypeFromProperty(t, include)));
92969312
}
92979313

92989314
function getNonEnumNumberIndexInfo(type: Type) {
@@ -9307,10 +9323,10 @@ namespace ts {
93079323
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
93089324
type === wildcardType ? wildcardType :
93099325
type.flags & TypeFlags.Any ? keyofConstraintType :
9310-
stringsOnly ? getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral) :
9311-
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromPropertyNames(type, TypeFlags.UniqueESSymbol)]) :
9312-
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
9313-
getLiteralTypeFromPropertyNames(type, TypeFlags.StringOrNumberLiteralOrUnique);
9326+
stringsOnly ? getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
9327+
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
9328+
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
9329+
getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique);
93149330
}
93159331

93169332
function getExtractStringType(type: Type) {
@@ -9563,7 +9579,7 @@ namespace ts {
95639579
// object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in
95649580
// an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]'
95659581
// has always been resolved eagerly using the constraint type of 'this' at the given location.
9566-
if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) {
9582+
if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType) && isGenericObjectType(objectType)) {
95679583
if (objectType.flags & TypeFlags.AnyOrUnknown) {
95689584
return objectType;
95699585
}
@@ -11013,7 +11029,7 @@ namespace ts {
1101311029
if (!length(node.properties)) return;
1101411030
for (const prop of node.properties) {
1101511031
if (isSpreadAssignment(prop)) continue;
11016-
const type = getLiteralTypeFromPropertyName(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique);
11032+
const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique);
1101711033
if (!type || (type.flags & TypeFlags.Never)) {
1101811034
continue;
1101911035
}
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts(7,5): error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
2-
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts(8,5): error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
1+
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts(6,5): error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
2+
Index signatures are incompatible.
3+
Type 'string | number' is not assignable to type 'boolean'.
4+
Type 'string' is not assignable to type 'boolean'.
35

46

5-
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts (2 errors) ====
7+
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts (1 errors) ====
68
interface I {
79
[s: string]: boolean;
810
[s: number]: boolean;
911
}
1012

1113
var o: I = {
14+
~
15+
!!! error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
16+
!!! error TS2322: Index signatures are incompatible.
17+
!!! error TS2322: Type 'string | number' is not assignable to type 'boolean'.
18+
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
1219
[""+"foo"]: "",
13-
~~~~~~~~~~
14-
!!! error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
15-
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts:2:5: The expected type comes from this index signature.
1620
[""+"bar"]: 0
17-
~~~~~~~~~~
18-
!!! error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
19-
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts:2:5: The expected type comes from this index signature.
2021
}
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts(7,5): error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
2-
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts(8,5): error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
1+
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts(6,5): error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
2+
Index signatures are incompatible.
3+
Type 'string | number' is not assignable to type 'boolean'.
4+
Type 'string' is not assignable to type 'boolean'.
35

46

5-
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts (2 errors) ====
7+
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts (1 errors) ====
68
interface I {
79
[s: string]: boolean;
810
[s: number]: boolean;
911
}
1012

1113
var o: I = {
14+
~
15+
!!! error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
16+
!!! error TS2322: Index signatures are incompatible.
17+
!!! error TS2322: Type 'string | number' is not assignable to type 'boolean'.
18+
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
1219
[""+"foo"]: "",
13-
~~~~~~~~~~
14-
!!! error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
15-
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts:2:5: The expected type comes from this index signature.
1620
[""+"bar"]: 0
17-
~~~~~~~~~~
18-
!!! error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
19-
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts:2:5: The expected type comes from this index signature.
2021
}

tests/baselines/reference/definiteAssignmentOfDestructuredVariable.types

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ class C<T extends Options> {
1818
>method : () => void
1919

2020
let { a, b } = this.foo;
21-
>a : { [P in keyof T]: T[P]; }["a"]
22-
>b : { [P in keyof T]: T[P]; }["b"]
21+
>a : T["a"]
22+
>b : T["b"]
2323
>this.foo : { [P in keyof T]: T[P]; }
2424
>this : this
2525
>foo : { [P in keyof T]: T[P]; }
2626

2727
!(a && b);
2828
>!(a && b) : false
29-
>(a && b) : { [P in keyof T]: T[P]; }["b"]
30-
>a && b : { [P in keyof T]: T[P]; }["b"]
31-
>a : { [P in keyof T]: T[P]; }["a"]
32-
>b : { [P in keyof T]: T[P]; }["b"]
29+
>(a && b) : T["b"]
30+
>a && b : T["b"]
31+
>a : T["a"]
32+
>b : T["b"]
3333

3434
a;
35-
>a : { [P in keyof T]: T[P]; }["a"]
35+
>a : T["a"]
3636
}
3737
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//// [genericObjectRest.ts]
2+
const a = 'a';
3+
4+
function f1<T extends { a: string, b: number }>(obj: T) {
5+
let { ...r0 } = obj;
6+
let { a: a1, ...r1 } = obj;
7+
let { a: a2, b: b2, ...r2 } = obj;
8+
let { 'a': a3, ...r3 } = obj;
9+
let { ['a']: a4, ...r4 } = obj;
10+
let { [a]: a5, ...r5 } = obj;
11+
}
12+
13+
const sa = Symbol();
14+
const sb = Symbol();
15+
16+
function f2<T extends { [sa]: string, [sb]: number }>(obj: T) {
17+
let { [sa]: a1, [sb]: b1, ...r1 } = obj;
18+
}
19+
20+
function f3<T, K1 extends keyof T, K2 extends keyof T>(obj: T, k1: K1, k2: K2) {
21+
let { [k1]: a1, [k2]: a2, ...r1 } = obj;
22+
}
23+
24+
type Item = { a: string, b: number, c: boolean };
25+
26+
function f4<K1 extends keyof Item, K2 extends keyof Item>(obj: Item, k1: K1, k2: K2) {
27+
let { [k1]: a1, [k2]: a2, ...r1 } = obj;
28+
}
29+
30+
31+
//// [genericObjectRest.js]
32+
"use strict";
33+
var __rest = (this && this.__rest) || function (s, e) {
34+
var t = {};
35+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
36+
t[p] = s[p];
37+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
38+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
39+
t[p[i]] = s[p[i]];
40+
return t;
41+
};
42+
const a = 'a';
43+
function f1(obj) {
44+
let r0 = __rest(obj, []);
45+
let { a: a1 } = obj, r1 = __rest(obj, ["a"]);
46+
let { a: a2, b: b2 } = obj, r2 = __rest(obj, ["a", "b"]);
47+
let { 'a': a3 } = obj, r3 = __rest(obj, ['a']);
48+
let { ['a']: a4 } = obj, r4 = __rest(obj, ['a']);
49+
let _a = a, a5 = obj[_a], r5 = __rest(obj, [typeof _a === "symbol" ? _a : _a + ""]);
50+
}
51+
const sa = Symbol();
52+
const sb = Symbol();
53+
function f2(obj) {
54+
let _a = sa, a1 = obj[_a], _b = sb, b1 = obj[_b], r1 = __rest(obj, [typeof _a === "symbol" ? _a : _a + "", typeof _b === "symbol" ? _b : _b + ""]);
55+
}
56+
function f3(obj, k1, k2) {
57+
let _a = k1, a1 = obj[_a], _b = k2, a2 = obj[_b], r1 = __rest(obj, [typeof _a === "symbol" ? _a : _a + "", typeof _b === "symbol" ? _b : _b + ""]);
58+
}
59+
function f4(obj, k1, k2) {
60+
let _a = k1, a1 = obj[_a], _b = k2, a2 = obj[_b], r1 = __rest(obj, [typeof _a === "symbol" ? _a : _a + "", typeof _b === "symbol" ? _b : _b + ""]);
61+
}

0 commit comments

Comments
 (0)