diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fd8123a5571..9d9a6d2f1ec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6232,13 +6232,15 @@ namespace ts { const restParameter = sig.parameters[restIndex]; const restType = getTypeOfSymbol(restParameter); if (isTupleType(restType)) { - const elementTypes = (restType).typeArguments || emptyArray; - const minLength = ((restType).target).minLength; + const elementTypes = restType.typeArguments || emptyArray; + const minLength = restType.target.minLength; + const tupleRestIndex = restType.target.hasRestElement ? elementTypes.length - 1 : -1; const restParams = map(elementTypes, (t, i) => { const name = getParameterNameAtPosition(sig, restIndex + i); - const checkFlags = i >= minLength ? CheckFlags.OptionalParameter : 0; + const checkFlags = i === tupleRestIndex ? CheckFlags.RestParameter : + i >= minLength ? CheckFlags.OptionalParameter : 0; const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); - symbol.type = t; + symbol.type = i === tupleRestIndex ? createArrayType(t) : t; return symbol; }); return concatenate(sig.parameters.slice(0, restIndex), restParams); @@ -8372,7 +8374,7 @@ namespace ts { 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; + return n === restElement && getIndexTypeOfType(type, IndexKind.Number) || type; }); links.resolvedType = createTupleType(elementTypes, minLength, !!restElement); } @@ -8887,6 +8889,12 @@ namespace ts { } return getTypeOfSymbol(prop); } + if (isTupleType(objectType)) { + const restType = getRestTypeOfTupleType(objectType); + if (restType && isNumericLiteralName(propName) && +propName >= 0) { + return restType; + } + } } if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { if (isTypeAny(objectType)) { @@ -11343,6 +11351,33 @@ namespace ts { } } let result = Ternary.True; + 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) { + reportError(Diagnostics.Rest_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) { + reportError(Diagnostics.Property_0_is_incompatible_with_rest_element_type, "" + i); + } + return Ternary.False; + } + result &= related; + } + } + } const properties = getPropertiesOfObjectType(target); for (const targetProp of properties) { if (!(targetProp.flags & SymbolFlags.Prototype)) { @@ -11417,35 +11452,6 @@ 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; } @@ -12506,7 +12512,7 @@ namespace ts { } const minArgumentCount = getMinArgumentCount(source); const minLength = minArgumentCount < paramCount ? 0 : minArgumentCount - paramCount; - const rest = sourceHasRest ? createArrayType(getUnionType(types)) : createTupleType(types, minLength, /*hasRestElement*/ false, names); + const rest = createTupleType(types, minLength, sourceHasRest, names); callback(rest, targetRestTypeVariable); } } @@ -17865,14 +17871,12 @@ namespace ts { } } + function isSpreadArgument(arg: Expression | undefined) { + return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg).isSpread); + } + function getSpreadArgumentIndex(args: ReadonlyArray): number { - for (let i = 0; i < args.length; i++) { - const arg = args[i]; - if (arg && arg.kind === SyntaxKind.SpreadElement) { - return i; - } - } - return -1; + return findIndex(args, isSpreadArgument); } function hasCorrectArity(node: CallLikeExpression, args: ReadonlyArray, signature: Signature, signatureHelpTrailingComma = false) { @@ -18091,7 +18095,7 @@ namespace ts { function getSpreadArgumentType(node: CallLikeExpression, args: ReadonlyArray, index: number, argCount: number, restType: TypeParameter, context: InferenceContext | undefined) { if (index === argCount - 1) { const arg = getEffectiveArgument(node, args, index); - if (arg && arg.kind === SyntaxKind.SpreadElement) { + if (isSpreadArgument(arg)) { // We are inferring from a spread expression in the last argument position, i.e. both the parameter // and the argument are ...x forms. return checkExpressionWithContextualType((arg).expression, restType, context); @@ -18100,16 +18104,20 @@ namespace ts { const contextualType = getIndexTypeOfType(restType, IndexKind.Number) || anyType; const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index); const types = []; - let hasSpreadExpression = false; + let spreadIndex = -1; for (let i = index; i < argCount; i++) { let argType = getEffectiveArgumentType(node, i); if (!argType) { argType = checkExpressionWithContextualType(args[i], contextualType, context); - hasSpreadExpression = hasSpreadExpression || args[i].kind === SyntaxKind.SpreadElement; + if (spreadIndex < 0 && isSpreadArgument(args[i])) { + spreadIndex = i - index; + } } types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); } - return hasSpreadExpression ? createArrayType(getUnionType(types)) : createTupleType(types); + return spreadIndex < 0 ? + createTupleType(types) : + createTupleType(append(types.slice(0, spreadIndex), getUnionType(types.slice(spreadIndex))), spreadIndex, /*hasRestElement*/ true); } function checkTypeArguments(signature: Signature, typeArgumentNodes: ReadonlyArray, reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | false { @@ -18205,7 +18213,7 @@ namespace ts { const arg = getEffectiveArgument(node, args, i); // If the effective argument is 'undefined', then it is an argument that is present but is synthetic. if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) { - if (i === restIndex && (restType.flags & TypeFlags.TypeParameter || arg && arg.kind === SyntaxKind.SpreadElement && !isArrayType(restType))) { + if (i === restIndex && (restType.flags & TypeFlags.TypeParameter || isSpreadArgument(arg) && !isArrayType(restType))) { const spreadType = getSpreadArgumentType(node, args, i, argCount, restType, /*context*/ undefined); return checkTypeRelatedTo(spreadType, restType, relation, arg, headMessage); } @@ -18275,17 +18283,20 @@ namespace ts { else { const args = node.arguments || emptyArray; const length = args.length; - if (length && args[length - 1].kind === SyntaxKind.SpreadElement && getSpreadArgumentIndex(args) === length - 1) { + if (length && isSpreadArgument(args[length - 1]) && getSpreadArgumentIndex(args) === length - 1) { // We have a spread argument in the last position and no other spread arguments. If the type // of the argument is a tuple type, spread the tuple elements into the argument list. We can // call checkExpressionCached because spread expressions never have a contextual type. const spreadArgument = args[length - 1]; const type = checkExpressionCached(spreadArgument.expression); if (isTupleType(type)) { - const syntheticArgs = map((type).typeArguments || emptyArray, t => { + const typeArguments = (type).typeArguments || emptyArray; + const restIndex = type.target.hasRestElement ? typeArguments.length - 1 : -1; + const syntheticArgs = map(typeArguments, (t, i) => { const arg = createNode(SyntaxKind.SyntheticExpression, spreadArgument.pos, spreadArgument.end); arg.parent = spreadArgument; arg.type = t; + arg.isSpread = i === restIndex; return arg; }); return concatenate(args.slice(0, length - 1), syntheticArgs); @@ -19601,9 +19612,12 @@ namespace ts { if (signature.hasRestParameter) { const restType = getTypeOfSymbol(signature.parameters[paramCount]); if (isTupleType(restType)) { - const elementCount = ((restType).typeArguments || emptyArray).length; - if (pos - paramCount < elementCount) { - return (restType).typeArguments![pos - paramCount]; + if (pos - paramCount < getLengthOfTupleType(restType)) { + return restType.typeArguments![pos - paramCount]; + } + const tupleRestType = getRestTypeOfTupleType(restType); + if (tupleRestType) { + return tupleRestType; } } return getIndexTypeOfType(restType, IndexKind.Number) || anyType; @@ -19612,7 +19626,14 @@ namespace ts { } function getTypeOfRestParameter(signature: Signature) { - return signature.hasRestParameter ? getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]) : undefined; + if (signature.hasRestParameter) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (isTupleType(restType)) { + return getRestTypeOfTupleType(restType); + } + return restType; + } + return undefined; } function getParameterCount(signature: Signature) { @@ -19620,18 +19641,20 @@ namespace ts { if (signature.hasRestParameter) { const restType = getTypeOfSymbol(signature.parameters[length - 1]); if (isTupleType(restType)) { - return length + ((restType).typeArguments || emptyArray).length - 1; + return length + (restType.typeArguments || emptyArray).length - 1; } } return length; } function getMinArgumentCount(signature: Signature) { - const restType = getTypeOfRestParameter(signature); - if (restType && isTupleType(restType)) { - const minLength = ((restType).target).minLength; - if (minLength > 0) { - return signature.parameters.length - 1 + minLength; + if (signature.hasRestParameter) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (isTupleType(restType)) { + const minLength = restType.target.minLength; + if (minLength > 0) { + return signature.parameters.length - 1 + minLength; + } } } return signature.minArgumentCount; @@ -19648,7 +19671,11 @@ namespace ts { } function hasEffectiveRestParameter(signature: Signature) { - return signature.hasRestParameter && !isTupleType(getTypeOfSymbol(signature.parameters[signature.parameters.length - 1])); + if (signature.hasRestParameter) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + return !isTupleType(restType) || restType.target.hasRestElement; + } + return false; } function getTypeOfFirstParameterOfSignature(signature: Signature) { @@ -21928,6 +21955,27 @@ namespace ts { } function checkTupleType(node: TupleTypeNode) { + const elementTypes = node.elementTypes; + let seenOptionalElement = false; + for (let i = 0; i < elementTypes.length; i++) { + const e = elementTypes[i]; + if (e.kind === SyntaxKind.RestType) { + if (i !== elementTypes.length - 1) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type); + break; + } + if (!isArrayType(getTypeFromTypeNode(e))) { + error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); + } + } + else if (e.kind === SyntaxKind.OptionalType) { + seenOptionalElement = true; + } + else if (seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; + } + } checkGrammarForDisallowedTrailingComma(node.elementTypes); forEach(node.elementTypes, checkSourceElement); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b31e404a3df..53e9f0c30bf 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -843,6 +843,14 @@ "category": "Error", "code": 1255 }, + "A rest element must be last in a tuple type.": { + "category": "Error", + "code": 1256 + }, + "A required element cannot follow an optional element.": { + "category": "Error", + "code": 1257 + }, "'with' statements are not allowed in an async function block.": { "category": "Error", "code": 1300 @@ -2028,6 +2036,18 @@ "category": "Error", "code": 2571 }, + "Rest signatures are incompatible.": { + "category": "Error", + "code": 2572 + }, + "Property '{0}' is incompatible with rest element type.": { + "category": "Error", + "code": 2573 + }, + "A rest element type must be an array type.": { + "category": "Error", + "code": 2574 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6b0dc64c193..5b352ddde1e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1303,6 +1303,7 @@ namespace ts { export interface SyntheticExpression extends Expression { kind: SyntaxKind.SyntheticExpression; + isSpread: boolean; type: Type; }