Skip to content
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

Generic object rest variables and parameters #28312

Merged
merged 14 commits into from
Nov 6, 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
72 changes: 44 additions & 28 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ namespace ts {
let deferredGlobalTemplateStringsArrayType: ObjectType;
let deferredGlobalImportMetaType: ObjectType;
let deferredGlobalExtractSymbol: Symbol;
let deferredGlobalExcludeSymbol: Symbol;
let deferredGlobalPickSymbol: Symbol;
let deferredGlobalBigIntType: ObjectType;

const allPotentiallyUnusedIdentifiers = createMap<PotentiallyUnusedIdentifier[]>(); // key is file name
Expand Down Expand Up @@ -4622,18 +4624,25 @@ namespace ts {
if (source.flags & TypeFlags.Never) {
return emptyObjectType;
}

if (source.flags & TypeFlags.Union) {
return mapType(source, t => getRestType(t, properties, symbol));
}

const members = createSymbolTable();
const names = createUnderscoreEscapedMap<true>();
for (const name of properties) {
names.set(getTextOfPropertyName(name), true);
const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName));
if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) {
if (omitKeyType.flags & TypeFlags.Never) {
return source;
}
const pickTypeAlias = getGlobalPickSymbol();
const excludeTypeAlias = getGlobalExcludeSymbol();
if (!pickTypeAlias || !excludeTypeAlias) {
return errorType;
}
const pickKeys = getTypeAliasInstantiation(excludeTypeAlias, [getIndexType(source), omitKeyType]);
return getTypeAliasInstantiation(pickTypeAlias, [source, pickKeys]);
}
const members = createSymbolTable();
for (const prop of getPropertiesOfType(source)) {
if (!names.has(prop.escapedName)
if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType)
&& !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected))
&& isSpreadableProperty(prop)) {
members.set(prop.escapedName, getSpreadSymbol(prop));
Expand Down Expand Up @@ -4669,7 +4678,7 @@ namespace ts {
let type: Type | undefined;
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
if (declaration.dotDotDotToken) {
if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType) || isGenericObjectType(parentType)) {
if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) {
error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types);
return errorType;
}
Expand All @@ -4684,12 +4693,8 @@ namespace ts {
else {
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
const name = declaration.propertyName || <Identifier>declaration.name;
const exprType = isComputedPropertyName(name)
? checkComputedPropertyName(name)
: isIdentifier(name)
? getLiteralType(unescapeLeadingUnderscores(name.escapedText))
: checkExpression(name);
const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(getApparentType(parentType), exprType, name), name);
const exprType = getLiteralTypeFromPropertyName(name);
const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(parentType, exprType, name), name);
type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name));
}
}
Expand Down Expand Up @@ -6825,7 +6830,7 @@ namespace ts {
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
// We have a { [P in keyof T]: X }
for (const prop of getPropertiesOfType(modifiersType)) {
addMemberForKeyType(getLiteralTypeFromPropertyName(prop, include));
addMemberForKeyType(getLiteralTypeFromProperty(prop, include));
}
if (modifiersType.flags & TypeFlags.Any || getIndexInfoOfType(modifiersType, IndexKind.String)) {
addMemberForKeyType(stringType);
Expand Down Expand Up @@ -8677,6 +8682,14 @@ namespace ts {
return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = getGlobalSymbol("Extract" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
}

function getGlobalExcludeSymbol(): Symbol {
return deferredGlobalExcludeSymbol || (deferredGlobalExcludeSymbol = getGlobalSymbol("Exclude" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
}

function getGlobalPickSymbol(): Symbol {
return deferredGlobalPickSymbol || (deferredGlobalPickSymbol = getGlobalSymbol("Pick" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
}

function getGlobalBigIntType(reportErrors: boolean) {
return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}
Expand Down Expand Up @@ -9268,21 +9281,24 @@ namespace ts {
type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false));
}

function getLiteralTypeFromPropertyName(name: PropertyName) {
return isIdentifier(name) ? getLiteralType(unescapeLeadingUnderscores(name.escapedText)) :
getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name));
}

function getBigIntLiteralType(node: BigIntLiteral): LiteralType {
return getLiteralType({
negative: false,
base10Value: parsePseudoBigInt(node.text)
});
}

function getLiteralTypeFromPropertyName(prop: Symbol, include: TypeFlags) {
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags) {
if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
let type = getLateBoundSymbol(prop).nameType;
if (!type && !isKnownSymbol(prop)) {
const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration);
type = name && isNumericLiteral(name) ? getLiteralType(+name.text) :
name && name.kind === SyntaxKind.ComputedPropertyName && isNumericLiteral(name.expression) ? getLiteralType(+name.expression.text) :
getLiteralType(symbolName(prop));
const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration) as PropertyName;
type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop));
}
if (type && type.flags & include) {
return type;
Expand All @@ -9291,8 +9307,8 @@ namespace ts {
return neverType;
}

function getLiteralTypeFromPropertyNames(type: Type, include: TypeFlags) {
return getUnionType(map(getPropertiesOfType(type), t => getLiteralTypeFromPropertyName(t, include)));
function getLiteralTypeFromProperties(type: Type, include: TypeFlags) {
return getUnionType(map(getPropertiesOfType(type), t => getLiteralTypeFromProperty(t, include)));
}

function getNonEnumNumberIndexInfo(type: Type) {
Expand All @@ -9307,10 +9323,10 @@ namespace ts {
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Any ? keyofConstraintType :
stringsOnly ? getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral) :
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromPropertyNames(type, TypeFlags.UniqueESSymbol)]) :
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
getLiteralTypeFromPropertyNames(type, TypeFlags.StringOrNumberLiteralOrUnique);
stringsOnly ? getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique);
}

function getExtractStringType(type: Type) {
Expand Down Expand Up @@ -9563,7 +9579,7 @@ namespace ts {
// object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in
// an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]'
// has always been resolved eagerly using the constraint type of 'this' at the given location.
if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) {
if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType) && isGenericObjectType(objectType)) {
if (objectType.flags & TypeFlags.AnyOrUnknown) {
return objectType;
}
Expand Down Expand Up @@ -11013,7 +11029,7 @@ namespace ts {
if (!length(node.properties)) return;
for (const prop of node.properties) {
if (isSpreadAssignment(prop)) continue;
const type = getLiteralTypeFromPropertyName(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique);
const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique);
if (!type || (type.flags & TypeFlags.Never)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
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'.
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'.
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts(6,5): error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
Index signatures are incompatible.
Type 'string | number' is not assignable to type 'boolean'.
Type 'string' is not assignable to type 'boolean'.


==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts (2 errors) ====
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts (1 errors) ====
interface I {
[s: string]: boolean;
[s: number]: boolean;
}

var o: I = {
~
!!! error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea what's going on here? I'm not sure why we stopped issuing the per-property error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed it was the change in looking up the type of property names.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because we previously treated all computed properties as having known (unit type) names, when here they actually have type string. Since they now have type string we generate a string index signature, and the elaboration logic stops trying to drill down to the properties.

!!! error TS2322: Index signatures are incompatible.
!!! error TS2322: Type 'string | number' is not assignable to type 'boolean'.
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
[""+"foo"]: "",
~~~~~~~~~~
!!! error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts:2:5: The expected type comes from this index signature.
[""+"bar"]: 0
~~~~~~~~~~
!!! error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES5.ts:2:5: The expected type comes from this index signature.
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
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'.
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'.
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts(6,5): error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
Index signatures are incompatible.
Type 'string | number' is not assignable to type 'boolean'.
Type 'string' is not assignable to type 'boolean'.


==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts (2 errors) ====
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts (1 errors) ====
interface I {
[s: string]: boolean;
[s: number]: boolean;
}

var o: I = {
~
!!! error TS2322: Type '{ [x: string]: string | number; }' is not assignable to type 'I'.
!!! error TS2322: Index signatures are incompatible.
!!! error TS2322: Type 'string | number' is not assignable to type 'boolean'.
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
[""+"foo"]: "",
~~~~~~~~~~
!!! error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts:2:5: The expected type comes from this index signature.
[""+"bar"]: 0
~~~~~~~~~~
!!! error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType8_ES6.ts:2:5: The expected type comes from this index signature.
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ class C<T extends Options> {
>method : () => void

let { a, b } = this.foo;
>a : { [P in keyof T]: T[P]; }["a"]
>b : { [P in keyof T]: T[P]; }["b"]
>a : T["a"]
>b : T["b"]
>this.foo : { [P in keyof T]: T[P]; }
>this : this
>foo : { [P in keyof T]: T[P]; }

!(a && b);
>!(a && b) : false
>(a && b) : { [P in keyof T]: T[P]; }["b"]
>a && b : { [P in keyof T]: T[P]; }["b"]
>a : { [P in keyof T]: T[P]; }["a"]
>b : { [P in keyof T]: T[P]; }["b"]
>(a && b) : T["b"]
>a && b : T["b"]
>a : T["a"]
>b : T["b"]

a;
>a : { [P in keyof T]: T[P]; }["a"]
>a : T["a"]
}
}
61 changes: 61 additions & 0 deletions tests/baselines/reference/genericObjectRest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//// [genericObjectRest.ts]
const a = 'a';

function f1<T extends { a: string, b: number }>(obj: T) {
let { ...r0 } = obj;
let { a: a1, ...r1 } = obj;
let { a: a2, b: b2, ...r2 } = obj;
let { 'a': a3, ...r3 } = obj;
let { ['a']: a4, ...r4 } = obj;
let { [a]: a5, ...r5 } = obj;
}

const sa = Symbol();
const sb = Symbol();

function f2<T extends { [sa]: string, [sb]: number }>(obj: T) {
let { [sa]: a1, [sb]: b1, ...r1 } = obj;
}

function f3<T, K1 extends keyof T, K2 extends keyof T>(obj: T, k1: K1, k2: K2) {
let { [k1]: a1, [k2]: a2, ...r1 } = obj;
}

type Item = { a: string, b: number, c: boolean };

function f4<K1 extends keyof Item, K2 extends keyof Item>(obj: Item, k1: K1, k2: K2) {
let { [k1]: a1, [k2]: a2, ...r1 } = obj;
}


//// [genericObjectRest.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;
};
const a = 'a';
function f1(obj) {
let r0 = __rest(obj, []);
let { a: a1 } = obj, r1 = __rest(obj, ["a"]);
let { a: a2, b: b2 } = obj, r2 = __rest(obj, ["a", "b"]);
let { 'a': a3 } = obj, r3 = __rest(obj, ['a']);
let { ['a']: a4 } = obj, r4 = __rest(obj, ['a']);
let _a = a, a5 = obj[_a], r5 = __rest(obj, [typeof _a === "symbol" ? _a : _a + ""]);
}
const sa = Symbol();
const sb = Symbol();
function f2(obj) {
let _a = sa, a1 = obj[_a], _b = sb, b1 = obj[_b], r1 = __rest(obj, [typeof _a === "symbol" ? _a : _a + "", typeof _b === "symbol" ? _b : _b + ""]);
}
function f3(obj, k1, k2) {
let _a = k1, a1 = obj[_a], _b = k2, a2 = obj[_b], r1 = __rest(obj, [typeof _a === "symbol" ? _a : _a + "", typeof _b === "symbol" ? _b : _b + ""]);
}
function f4(obj, k1, k2) {
let _a = k1, a1 = obj[_a], _b = k2, a2 = obj[_b], r1 = __rest(obj, [typeof _a === "symbol" ? _a : _a + "", typeof _b === "symbol" ? _b : _b + ""]);
}
Loading