From f1efd1d0430443191a0ee36087b8c3b5aec4c6ba Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 15 Jun 2018 11:43:16 -0700 Subject: [PATCH] Parsing and rudimentary checking of tuples with rest elements --- src/compiler/binder.ts | 1 + src/compiler/checker.ts | 89 ++++++++++++++++++++++++++------- src/compiler/emitter.ts | 5 +- src/compiler/factory.ts | 12 +++++ src/compiler/transformers/ts.ts | 1 + src/compiler/visitor.ts | 4 ++ src/parser/parser.ts | 9 +++- src/parser/types.ts | 7 +++ 8 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 3c8f91970bc..96805be23bf 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3602,6 +3602,7 @@ namespace ts { case SyntaxKind.ArrayType: case SyntaxKind.TupleType: case SyntaxKind.OptionalType: + case SyntaxKind.RestType: case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: case SyntaxKind.ConditionalType: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e13aefc4c03..c011c77f5ec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3400,9 +3400,12 @@ namespace ts { if (typeArguments.length > 0) { const arity = getTypeReferenceArity(type); const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); + const hasRestElement = (type.target).hasRestElement; if (tupleConstituentNodes && tupleConstituentNodes.length > 0) { for (let i = (type.target).minLength; i < arity; i++) { - tupleConstituentNodes[i] = createOptionalTypeNode(tupleConstituentNodes[i]); + tupleConstituentNodes[i] = hasRestElement && i === arity - 1 ? + createRestTypeNode(createArrayTypeNode(tupleConstituentNodes[i])) : + createOptionalTypeNode(tupleConstituentNodes[i]); } return createTupleTypeNode(tupleConstituentNodes); } @@ -4842,7 +4845,7 @@ namespace ts { } // If the pattern has at least one element, and no rest element, then it should imply a tuple type. const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); - let result = createTupleType(elementTypes); + let result = createTupleType(elementTypes); if (includePatternInType) { result = cloneTypeReference(result); result.pattern = pattern; @@ -8282,21 +8285,25 @@ namespace ts { // // Note that the generic type created by this function has no symbol associated with it. The same // is true for each of the synthesized type parameters. - function createTupleTypeOfArity(arity: number, minLength: number, associatedNames: __String[] | undefined): TupleType { + function createTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, associatedNames: __String[] | undefined): TupleType { let typeParameters: TypeParameter[] | undefined; const properties: Symbol[] = []; + const maxLength = hasRestElement ? arity - 1 : arity; if (arity) { typeParameters = new Array(arity); for (let i = 0; i < arity; i++) { - const property = createSymbol(SymbolFlags.Property | (i >= minLength ? SymbolFlags.Optional : 0), "" + i as __String); - property.type = typeParameters[i] = createType(TypeFlags.TypeParameter); - properties.push(property); + const typeParameter = typeParameters[i] = createType(TypeFlags.TypeParameter); + if (i < maxLength) { + const property = createSymbol(SymbolFlags.Property | (i >= minLength ? SymbolFlags.Optional : 0), "" + i as __String); + property.type = typeParameter; + properties.push(property); + } } } const literalTypes = []; - for (let i = minLength; i <= arity; i++) literalTypes.push(getLiteralType(i)); + for (let i = minLength; i <= maxLength; i++) literalTypes.push(getLiteralType(i)); const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String); - lengthSymbol.type = getUnionType(literalTypes); + lengthSymbol.type = hasRestElement ? numberType : getUnionType(literalTypes); properties.push(lengthSymbol); const type = createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference); type.typeParameters = typeParameters; @@ -8315,29 +8322,40 @@ namespace ts { type.declaredStringIndexInfo = undefined; type.declaredNumberIndexInfo = undefined; type.minLength = minLength; + type.hasRestElement = hasRestElement; type.associatedNames = associatedNames; return type; } - function getTupleTypeOfArity(arity: number, minLength: number, associatedNames?: __String[]): GenericType { - const key = arity + "," + minLength + (associatedNames && associatedNames.length ? "," + associatedNames.join(",") : ""); + function getTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, associatedNames?: __String[]): GenericType { + const key = arity + (hasRestElement ? "+" : ",") + minLength + (associatedNames && associatedNames.length ? "," + associatedNames.join(",") : ""); let type = tupleTypes.get(key); if (!type) { - tupleTypes.set(key, type = createTupleTypeOfArity(arity, minLength, associatedNames)); + tupleTypes.set(key, type = createTupleTypeOfArity(arity, minLength, hasRestElement, associatedNames)); } return type; } - function createTupleType(elementTypes: Type[], minLength = elementTypes.length, associatedNames?: __String[]) { - const tupleType = getTupleTypeOfArity(elementTypes.length, minLength, associatedNames); + function createTupleType(elementTypes: Type[], minLength = elementTypes.length, hasRestElement = false, associatedNames?: __String[]) { + const arity = elementTypes.length; + if (arity === 1 && hasRestElement) { + return createArrayType(elementTypes[0]); + } + const tupleType = getTupleTypeOfArity(arity, minLength, arity > 0 && hasRestElement, associatedNames); return elementTypes.length ? createTypeReference(tupleType, elementTypes) : tupleType; } function getTypeFromTupleTypeNode(node: TupleTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType) + 1; - links.resolvedType = createTupleType(map(node.elementTypes, getTypeFromTypeNode), minLength); + const lastElement = lastOrUndefined(node.elementTypes); + const restElement = lastElement && lastElement.kind === SyntaxKind.RestType ? lastElement : undefined; + const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType && n !== restElement) + 1; + const elementTypes = map(node.elementTypes, n => { + const type = getTypeFromTypeNode(n); + return n === restElement ? getIndexTypeOfType(type, IndexKind.Number) || errorType : type; + }); + links.resolvedType = createTupleType(elementTypes, minLength, !!restElement); } return links.resolvedType; } @@ -9548,9 +9566,10 @@ namespace ts { case SyntaxKind.JSDocOptionalType: return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); case SyntaxKind.ParenthesizedType: + case SyntaxKind.RestType: case SyntaxKind.JSDocNonNullableType: case SyntaxKind.JSDocTypeExpression: - return getTypeFromTypeNode((node).type); + return getTypeFromTypeNode((node).type); case SyntaxKind.JSDocVariadicType: return getTypeFromJSDocVariadicType(node as JSDocVariadicType); case SyntaxKind.FunctionType: @@ -11346,6 +11365,35 @@ namespace ts { } } } + if (isTupleType(target)) { + const targetRestType = getRestTypeOfTupleType(target); + if (targetRestType) { + if (!isTupleType(source)) { + return Ternary.False; + } + const sourceRestType = getRestTypeOfTupleType(source); + if (sourceRestType && !isRelatedTo(sourceRestType, targetRestType, reportErrors)) { + if (reportErrors) { + // !!! Rest element types are incompatible + reportError(Diagnostics.Index_signatures_are_incompatible); + } + return Ternary.False; + } + const targetCount = getTypeReferenceArity(target) - 1; + const sourceCount = getTypeReferenceArity(source) - (sourceRestType ? 1 : 0); + for (let i = targetCount; i < sourceCount; i++) { + const related = isRelatedTo((source).typeArguments![i], targetRestType, reportErrors); + if (!related) { + if (reportErrors) { + // !!! Property {0} is incompatible with rest element type + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, "" + i); + } + return Ternary.False; + } + result &= related; + } + } + } return result; } @@ -12028,6 +12076,10 @@ namespace ts { return !!(getObjectFlags(type) & ObjectFlags.Reference && (type).target.objectFlags & ObjectFlags.Tuple); } + function getRestTypeOfTupleType(type: TypeReference) { + return (type.target).hasRestElement ? type.typeArguments![type.target.typeParameters!.length - 1] : undefined; + } + function getFalsyFlagsOfTypes(types: Type[]): TypeFlags { let result: TypeFlags = 0; for (const t of types) { @@ -12400,7 +12452,7 @@ namespace ts { } const minArgumentCount = getMinArgumentCount(source); const minLength = minArgumentCount < paramCount ? 0 : minArgumentCount - paramCount; - const rest = sourceHasRest ? createArrayType(getUnionType(types)) : createTupleType(types, minLength, names); + const rest = sourceHasRest ? createArrayType(getUnionType(types)) : createTupleType(types, minLength, /*hasRestElement*/ false, names); callback(rest, targetRestTypeVariable); } } @@ -15899,7 +15951,7 @@ namespace ts { // If array literal is actually a destructuring pattern, mark it as an implied type. We do this such // that we get the same behavior for "var [x, y] = []" and "[x, y] = []". if (inDestructuringPattern && elementTypes.length) { - const type = cloneTypeReference(createTupleType(elementTypes)); + const type = cloneTypeReference(createTupleType(elementTypes)); type.pattern = node; return type; } @@ -25639,6 +25691,7 @@ namespace ts { return checkUnionOrIntersectionType(node); case SyntaxKind.ParenthesizedType: case SyntaxKind.OptionalType: + case SyntaxKind.RestType: return checkSourceElement((node).type); case SyntaxKind.TypeOperator: return checkTypeOperator(node); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index edf3c457f87..904972b52f3 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -678,8 +678,9 @@ namespace ts { return emitJSDocNonNullableType(node as JSDocNonNullableType); case SyntaxKind.JSDocOptionalType: return emitJSDocOptionalType(node as JSDocOptionalType); + case SyntaxKind.RestType: case SyntaxKind.JSDocVariadicType: - return emitJSDocVariadicType(node as JSDocVariadicType); + return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); // Binding patterns case SyntaxKind.ObjectBindingPattern: @@ -1287,7 +1288,7 @@ namespace ts { writePunctuation("]"); } - function emitJSDocVariadicType(node: JSDocVariadicType) { + function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { write("..."); emit(node.type); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index b861d1c18c3..95fe9d8a327 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -763,6 +763,18 @@ namespace ts { : node; } + export function createRestTypeNode(type: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.RestType) as RestTypeNode; + node.type = type; + return node; + } + + export function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { + return node.type !== type + ? updateNode(createRestTypeNode(type), node) + : node; + } + export function createUnionTypeNode(types: ReadonlyArray): UnionTypeNode { return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types); } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 218eb95b619..1cb2942a519 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -381,6 +381,7 @@ namespace ts { case SyntaxKind.ArrayType: case SyntaxKind.TupleType: case SyntaxKind.OptionalType: + case SyntaxKind.RestType: case SyntaxKind.TypeLiteral: case SyntaxKind.TypePredicate: case SyntaxKind.TypeParameter: diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 6c7ae614ac8..886d3be337a 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -379,6 +379,10 @@ namespace ts { return updateOptionalTypeNode((node), visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.RestType: + return updateRestTypeNode((node), + visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.UnionType: return updateUnionTypeNode(node, nodesVisitor((node).types, visitor, isTypeNode)); diff --git a/src/parser/parser.ts b/src/parser/parser.ts index fad37c233dc..460464b0465 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -446,12 +446,13 @@ namespace ts { return visitNode(cbNode, (node).tagName); case SyntaxKind.OptionalType: + case SyntaxKind.RestType: case SyntaxKind.JSDocTypeExpression: case SyntaxKind.JSDocNonNullableType: case SyntaxKind.JSDocNullableType: case SyntaxKind.JSDocOptionalType: case SyntaxKind.JSDocVariadicType: - return visitNode(cbNode, (node).type); + return visitNode(cbNode, (node).type); case SyntaxKind.JSDocFunctionType: return visitNodes(cbNode, cbNodes, (node).parameters) || visitNode(cbNode, (node).type); @@ -2775,6 +2776,12 @@ namespace ts { } function parseTupleElementType() { + const pos = getNodePos(); + if (parseOptional(SyntaxKind.DotDotDotToken)) { + const node = createNode(SyntaxKind.RestType, pos); + node.type = parseType(); + return finishNode(node); + } const type = parseType(); if (!(contextFlags & NodeFlags.JSDoc) && type.kind === SyntaxKind.JSDocNullableType && type.pos === (type).type.pos) { type.kind = SyntaxKind.OptionalType; diff --git a/src/parser/types.ts b/src/parser/types.ts index e6a3727e925..bae03d391ed 100644 --- a/src/parser/types.ts +++ b/src/parser/types.ts @@ -226,6 +226,7 @@ namespace ts { ArrayType, TupleType, OptionalType, + RestType, UnionType, IntersectionType, ConditionalType, @@ -1107,6 +1108,11 @@ namespace ts { type: TypeNode; } + export interface RestTypeNode extends TypeNode { + kind: SyntaxKind.RestType; + type: TypeNode; + } + export type UnionOrIntersectionTypeNode = UnionTypeNode | IntersectionTypeNode; export interface UnionTypeNode extends TypeNode { @@ -3854,6 +3860,7 @@ namespace ts { export interface TupleType extends GenericType { minLength: number; + hasRestElement: boolean; associatedNames?: __String[]; }