Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6e151f8

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

File tree

11 files changed

+11397
-27
lines changed

11 files changed

+11397
-27
lines changed

‎src/compiler.ts‎

Lines changed: 44 additions & 11 deletions
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‎

Lines changed: 36 additions & 12 deletions
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‎

Lines changed: 14 additions & 0 deletions
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‎

Lines changed: 15 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /