Skip to content

Commit 6e151f8

Browse files
feat: Support rest params in function calls (#2905)
1 parent cdd5e01 commit 6e151f8

11 files changed

+11397
-27
lines changed

src/compiler.ts

+44-11
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ import {
182182
findDecorator,
183183
isTypeOmitted,
184184
Source,
185-
TypeDeclaration
185+
TypeDeclaration,
186+
ParameterKind
186187
} from "./ast";
187188

188189
import {
@@ -6171,16 +6172,7 @@ export class Compiler extends DiagnosticEmitter {
61716172
return false;
61726173
}
61736174

6174-
// not yet implemented (TODO: maybe some sort of an unmanaged/lightweight array?)
61756175
let hasRest = signature.hasRest;
6176-
if (hasRest) {
6177-
this.error(
6178-
DiagnosticCode.Not_implemented_0,
6179-
reportNode.range, "Rest parameters"
6180-
);
6181-
return false;
6182-
}
6183-
61846176
let minimum = signature.requiredParameters;
61856177
let maximum = signature.parameterTypes.length;
61866178

@@ -6225,6 +6217,37 @@ export class Compiler extends DiagnosticEmitter {
62256217
}
62266218
}
62276219

6220+
private adjustArgumentsForRestParams(
6221+
argumentExpressions: Expression[],
6222+
signature: Signature,
6223+
reportNode: Node
6224+
) : Expression[] {
6225+
6226+
// if no rest args, return the original args
6227+
if (!signature.hasRest) {
6228+
return argumentExpressions;
6229+
}
6230+
6231+
// if there are fewer args than params, then the rest args were not provided
6232+
// so return the original args
6233+
const numArguments = argumentExpressions.length;
6234+
const numParams = signature.parameterTypes.length;
6235+
if (numArguments < numParams) {
6236+
return argumentExpressions;
6237+
}
6238+
6239+
// make an array literal expression from the rest args
6240+
let elements = argumentExpressions.slice(numParams - 1);
6241+
let range = new Range(elements[0].range.start, elements[elements.length - 1].range.end);
6242+
range.source = reportNode.range.source;
6243+
let arrExpr = new ArrayLiteralExpression(elements, range);
6244+
6245+
// return the original args, but replace the rest args with the array
6246+
const exprs = argumentExpressions.slice(0, numParams - 1);
6247+
exprs.push(arrExpr);
6248+
return exprs;
6249+
}
6250+
62286251
/** Compiles a direct call to a concrete function. */
62296252
compileCallDirect(
62306253
instance: Function,
@@ -6246,6 +6269,9 @@ export class Compiler extends DiagnosticEmitter {
62466269
}
62476270
if (instance.hasDecorator(DecoratorFlags.Unsafe)) this.checkUnsafe(reportNode);
62486271

6272+
argumentExpressions = this.adjustArgumentsForRestParams(argumentExpressions, signature, reportNode);
6273+
numArguments = argumentExpressions.length;
6274+
62496275
// handle call on `this` in constructors
62506276
let sourceFunction = this.currentFlow.sourceFunction;
62516277
if (sourceFunction.is(CommonFlags.Constructor) && reportNode.isAccessOnThis) {
@@ -6477,7 +6503,11 @@ export class Compiler extends DiagnosticEmitter {
64776503
let declaration = originalParameterDeclarations[minArguments + i];
64786504
let initializer = declaration.initializer;
64796505
let initExpr: ExpressionRef;
6480-
if (initializer) {
6506+
if (declaration.parameterKind === ParameterKind.Rest) {
6507+
const arrExpr = new ArrayLiteralExpression([], declaration.range.atEnd);
6508+
initExpr = this.compileArrayLiteral(arrExpr, type, Constraints.ConvExplicit);
6509+
initExpr = module.local_set(operandIndex, initExpr, type.isManaged);
6510+
} else if (initializer) {
64816511
initExpr = this.compileExpression(
64826512
initializer,
64836513
type,
@@ -6863,6 +6893,9 @@ export class Compiler extends DiagnosticEmitter {
68636893
return this.module.unreachable();
68646894
}
68656895

6896+
argumentExpressions = this.adjustArgumentsForRestParams(argumentExpressions, signature, reportNode);
6897+
numArguments = argumentExpressions.length;
6898+
68666899
let numArgumentsInclThis = thisArg ? numArguments + 1 : numArguments;
68676900
let operands = new Array<ExpressionRef>(numArgumentsInclThis);
68686901
let index = 0;

src/resolver.ts

+36-12
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,9 @@ export class Resolver extends DiagnosticEmitter {
738738
ctxFlow,
739739
reportMode,
740740
);
741-
741+
if (!resolvedTypeArguments) {
742+
return null;
743+
}
742744
return this.resolveFunction(
743745
prototype,
744746
resolvedTypeArguments,
@@ -778,16 +780,24 @@ export class Resolver extends DiagnosticEmitter {
778780
let numParameters = parameterNodes.length;
779781

780782
let argumentNodes: Expression[];
783+
let argumentsRange: Range;
781784
switch (node.kind) {
782-
case NodeKind.Call:
783-
argumentNodes = (<CallExpression>node).args;
785+
case NodeKind.Call: {
786+
const expr = node as CallExpression;
787+
argumentNodes = expr.args;
788+
argumentsRange = expr.argumentsRange;
784789
break;
785-
case NodeKind.New:
786-
argumentNodes = (<NewExpression>node).args;
790+
}
791+
case NodeKind.New: {
792+
const expr = node as NewExpression;
793+
argumentNodes = expr.args;
794+
argumentsRange = expr.argumentsRange;
787795
break;
788-
default:
796+
}
797+
default: {
789798
assert(false);
790799
return null;
800+
}
791801
}
792802

793803
let numArguments = argumentNodes.length;
@@ -802,16 +812,27 @@ export class Resolver extends DiagnosticEmitter {
802812
if (parameterNodes[i].parameterKind == ParameterKind.Optional) {
803813
continue;
804814
}
805-
// missing initializer -> too few arguments
806815
if (reportMode == ReportMode.Report) {
807-
this.error(
808-
DiagnosticCode.Expected_0_arguments_but_got_1,
809-
node.range, numParameters.toString(), numArguments.toString()
810-
);
816+
if (parameterNodes[i].parameterKind == ParameterKind.Rest) {
817+
// rest params are optional, but one element is needed for type inference
818+
this.error(
819+
DiagnosticCode.Type_argument_expected,
820+
argumentsRange.atEnd
821+
);
822+
} else {
823+
// missing initializer -> too few arguments
824+
this.error(
825+
DiagnosticCode.Expected_0_arguments_but_got_1,
826+
node.range, numParameters.toString(), numArguments.toString()
827+
);
828+
}
811829
}
812830
return null;
813831
}
814832
let typeNode = parameterNodes[i].type;
833+
if (parameterNodes[i].parameterKind == ParameterKind.Rest) {
834+
typeNode = (<NamedTypeNode> typeNode).typeArguments![0];
835+
}
815836
if (typeNode.hasGenericComponent(typeParameterNodes)) {
816837
let type = this.resolveExpression(argumentExpression, ctxFlow, Type.auto, ReportMode.Swallow);
817838
if (type) {
@@ -2921,10 +2942,13 @@ export class Resolver extends DiagnosticEmitter {
29212942
let numSignatureParameters = signatureParameters.length;
29222943
let parameterTypes = new Array<Type>(numSignatureParameters);
29232944
let requiredParameters = 0;
2945+
let hasRest = false;
29242946
for (let i = 0; i < numSignatureParameters; ++i) {
29252947
let parameterDeclaration = signatureParameters[i];
29262948
if (parameterDeclaration.parameterKind == ParameterKind.Default) {
29272949
requiredParameters = i + 1;
2950+
} else if (parameterDeclaration.parameterKind == ParameterKind.Rest) {
2951+
hasRest = true;
29282952
}
29292953
let typeNode = parameterDeclaration.type;
29302954
if (isTypeOmitted(typeNode)) {
@@ -2984,7 +3008,7 @@ export class Resolver extends DiagnosticEmitter {
29843008
returnType = type;
29853009
}
29863010

2987-
let signature = Signature.create(this.program, parameterTypes, returnType, thisType, requiredParameters);
3011+
let signature = Signature.create(this.program, parameterTypes, returnType, thisType, requiredParameters, hasRest);
29883012

29893013
let nameInclTypeParameters = prototype.name;
29903014
if (instanceKey.length) nameInclTypeParameters += `<${instanceKey}>`;

tests/compiler/call-rest-err.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"asc_flags": [
3+
],
4+
"stderr": [
5+
"TS2322: Type '~lib/string/String' is not assignable to type 'i32'.",
6+
"sum('a', 'b')",
7+
"TS2322: Type '~lib/string/String' is not assignable to type 'i32'.",
8+
"sum('a', 'b')",
9+
"TS2322: Type '~lib/string/String' is not assignable to type 'i32'.",
10+
"count(1, 'a')",
11+
"TS1140: Type argument expected.",
12+
"count()"
13+
]
14+
}

tests/compiler/call-rest-err.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function sum(...args: i32[]): i32 {
2+
let sum = 0;
3+
for (let i = 0, k = args.length; i < k; ++i) {
4+
sum += args[i];
5+
}
6+
return sum;
7+
}
8+
9+
function count<T>(...args: T[]): i32 {
10+
return args.length;
11+
}
12+
13+
sum('a', 'b'); // expect a type mismatch error on each argument
14+
count(1, 'a'); // expect a type mismatch error on the second argument
15+
count(); // expect type inference error

0 commit comments

Comments
 (0)