Skip to content

Commit 40dd8ba

Browse files
authored
Merge pull request #16330 from Microsoft/fix-js-infer-rest-args
Fix JS-inferred rest parameters
2 parents d450b8b + 4b19a94 commit 40dd8ba

8 files changed

+196
-23
lines changed

src/compiler/checker.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,17 @@ namespace ts {
28322832

28332833
function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext): ParameterDeclaration {
28342834
const parameterDeclaration = getDeclarationOfKind<ParameterDeclaration>(parameterSymbol, SyntaxKind.Parameter);
2835+
if (isTransientSymbol(parameterSymbol) && parameterSymbol.isRestParameter) {
2836+
// special-case synthetic rest parameters in JS files
2837+
return createParameter(
2838+
/*decorators*/ undefined,
2839+
/*modifiers*/ undefined,
2840+
parameterSymbol.isRestParameter ? createToken(SyntaxKind.DotDotDotToken) : undefined,
2841+
"args",
2842+
/*questionToken*/ undefined,
2843+
typeToTypeNodeHelper(anyArrayType, context),
2844+
/*initializer*/ undefined);
2845+
}
28352846
const modifiers = parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedClone);
28362847
const dotDotDotToken = isRestParameter(parameterDeclaration) ? createToken(SyntaxKind.DotDotDotToken) : undefined;
28372848
const name = parameterDeclaration.name ?
@@ -6391,8 +6402,17 @@ namespace ts {
63916402
const typePredicate = declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ?
63926403
createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) :
63936404
undefined;
6405+
// JS functions get a free rest parameter if they reference `arguments`
6406+
let hasRestLikeParameter = hasRestParameter(declaration);
6407+
if (!hasRestLikeParameter && isInJavaScriptFile(declaration) && !hasJSDocParameterTags(declaration) && containsArgumentsReference(declaration)) {
6408+
hasRestLikeParameter = true;
6409+
const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args");
6410+
syntheticArgsSymbol.type = anyArrayType;
6411+
syntheticArgsSymbol.isRestParameter = true;
6412+
parameters.push(syntheticArgsSymbol);
6413+
}
63946414

6395-
links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, hasRestParameter(declaration), hasLiteralTypes);
6415+
links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, hasRestLikeParameter, hasLiteralTypes);
63966416
}
63976417
return links.resolvedSignature;
63986418
}
@@ -6427,14 +6447,14 @@ namespace ts {
64276447
}
64286448
}
64296449

6430-
function containsArgumentsReference(declaration: FunctionLikeDeclaration): boolean {
6450+
function containsArgumentsReference(declaration: SignatureDeclaration): boolean {
64316451
const links = getNodeLinks(declaration);
64326452
if (links.containsArgumentsReference === undefined) {
64336453
if (links.flags & NodeCheckFlags.CaptureArguments) {
64346454
links.containsArgumentsReference = true;
64356455
}
64366456
else {
6437-
links.containsArgumentsReference = traverse(declaration.body);
6457+
links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body);
64386458
}
64396459
}
64406460
return links.containsArgumentsReference;
@@ -15501,21 +15521,6 @@ namespace ts {
1550115521
}
1550215522
}
1550315523

15504-
if (signatures.length === 1) {
15505-
const declaration = signatures[0].declaration;
15506-
if (declaration && isInJavaScriptFile(declaration) && !hasJSDocParameterTags(declaration)) {
15507-
if (containsArgumentsReference(<FunctionLikeDeclaration>declaration)) {
15508-
const signatureWithRest = cloneSignature(signatures[0]);
15509-
const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args");
15510-
syntheticArgsSymbol.type = anyArrayType;
15511-
syntheticArgsSymbol.isRestParameter = true;
15512-
signatureWithRest.parameters = concatenate(signatureWithRest.parameters, [syntheticArgsSymbol]);
15513-
signatureWithRest.hasRestParameter = true;
15514-
signatures = [signatureWithRest];
15515-
}
15516-
}
15517-
}
15518-
1551915524
const candidates = candidatesOutArray || [];
1552015525
// reorderCandidates fills up the candidates array directly
1552115526
reorderCandidates(signatures, candidates);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== tests/cases/compiler/main.js ===
2+
function allRest() { arguments; }
3+
>allRest : Symbol(allRest, Decl(main.js, 0, 0))
4+
>arguments : Symbol(arguments)
5+
6+
allRest();
7+
>allRest : Symbol(allRest, Decl(main.js, 0, 0))
8+
9+
allRest(1, 2, 3);
10+
>allRest : Symbol(allRest, Decl(main.js, 0, 0))
11+
12+
function someRest(x, y) { arguments; }
13+
>someRest : Symbol(someRest, Decl(main.js, 2, 17))
14+
>x : Symbol(x, Decl(main.js, 3, 18))
15+
>y : Symbol(y, Decl(main.js, 3, 20))
16+
>arguments : Symbol(arguments)
17+
18+
someRest(); // x and y are still optional because they are in a JS file
19+
>someRest : Symbol(someRest, Decl(main.js, 2, 17))
20+
21+
someRest(1, 2, 3);
22+
>someRest : Symbol(someRest, Decl(main.js, 2, 17))
23+
24+
/**
25+
* @param {number} x - a thing
26+
*/
27+
function jsdocced(x) { arguments; }
28+
>jsdocced : Symbol(jsdocced, Decl(main.js, 5, 18))
29+
>x : Symbol(x, Decl(main.js, 10, 18))
30+
>arguments : Symbol(arguments)
31+
32+
jsdocced(1);
33+
>jsdocced : Symbol(jsdocced, Decl(main.js, 5, 18))
34+
35+
function dontDoubleRest(x, ...y) { arguments; }
36+
>dontDoubleRest : Symbol(dontDoubleRest, Decl(main.js, 11, 12))
37+
>x : Symbol(x, Decl(main.js, 13, 24))
38+
>y : Symbol(y, Decl(main.js, 13, 26))
39+
>arguments : Symbol(arguments)
40+
41+
dontDoubleRest(1, 2, 3);
42+
>dontDoubleRest : Symbol(dontDoubleRest, Decl(main.js, 11, 12))
43+
44+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
=== tests/cases/compiler/main.js ===
2+
function allRest() { arguments; }
3+
>allRest : (...args: any[]) => void
4+
>arguments : IArguments
5+
6+
allRest();
7+
>allRest() : void
8+
>allRest : (...args: any[]) => void
9+
10+
allRest(1, 2, 3);
11+
>allRest(1, 2, 3) : void
12+
>allRest : (...args: any[]) => void
13+
>1 : 1
14+
>2 : 2
15+
>3 : 3
16+
17+
function someRest(x, y) { arguments; }
18+
>someRest : (x: any, y: any, ...args: any[]) => void
19+
>x : any
20+
>y : any
21+
>arguments : IArguments
22+
23+
someRest(); // x and y are still optional because they are in a JS file
24+
>someRest() : void
25+
>someRest : (x: any, y: any, ...args: any[]) => void
26+
27+
someRest(1, 2, 3);
28+
>someRest(1, 2, 3) : void
29+
>someRest : (x: any, y: any, ...args: any[]) => void
30+
>1 : 1
31+
>2 : 2
32+
>3 : 3
33+
34+
/**
35+
* @param {number} x - a thing
36+
*/
37+
function jsdocced(x) { arguments; }
38+
>jsdocced : (x: number) => void
39+
>x : number
40+
>arguments : IArguments
41+
42+
jsdocced(1);
43+
>jsdocced(1) : void
44+
>jsdocced : (x: number) => void
45+
>1 : 1
46+
47+
function dontDoubleRest(x, ...y) { arguments; }
48+
>dontDoubleRest : (x: any, ...y: any[]) => void
49+
>x : any
50+
>y : any[]
51+
>arguments : IArguments
52+
53+
dontDoubleRest(1, 2, 3);
54+
>dontDoubleRest(1, 2, 3) : void
55+
>dontDoubleRest : (x: any, ...y: any[]) => void
56+
>1 : 1
57+
>2 : 2
58+
>3 : 3
59+
60+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/compiler/foo.js ===
2+
// Test #16139
3+
function Foo() {
4+
>Foo : Symbol(Foo, Decl(foo.js, 0, 0))
5+
6+
arguments;
7+
>arguments : Symbol(arguments)
8+
9+
return new Foo();
10+
>Foo : Symbol(Foo, Decl(foo.js, 0, 0))
11+
}
12+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
=== tests/cases/compiler/foo.js ===
2+
// Test #16139
3+
function Foo() {
4+
>Foo : (...args: any[]) => any
5+
6+
arguments;
7+
>arguments : IArguments
8+
9+
return new Foo();
10+
>new Foo() : any
11+
>Foo : (...args: any[]) => any
12+
}
13+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @checkJs: true
2+
// @allowJs: true
3+
// @Filename: main.js
4+
// @noemit: true
5+
function allRest() { arguments; }
6+
allRest();
7+
allRest(1, 2, 3);
8+
function someRest(x, y) { arguments; }
9+
someRest(); // x and y are still optional because they are in a JS file
10+
someRest(1, 2, 3);
11+
12+
/**
13+
* @param {number} x - a thing
14+
*/
15+
function jsdocced(x) { arguments; }
16+
jsdocced(1);
17+
18+
function dontDoubleRest(x, ...y) { arguments; }
19+
dontDoubleRest(1, 2, 3);
20+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @Filename: foo.js
2+
// @noEmit: true
3+
// @allowJs: true
4+
// Test #16139
5+
function Foo() {
6+
arguments;
7+
return new Foo();
8+
}

tests/cases/fourslash/signatureHelpCallExpressionJs.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,25 @@
44
// @allowJs: true
55

66
// @Filename: main.js
7-
////function fnTest() { arguments; }
8-
////fnTest(/*1*/);
9-
////fnTest(1, 2, 3);
7+
////function allOptional() { arguments; }
8+
////allOptional(/*1*/);
9+
////allOptional(1, 2, 3);
10+
////function someOptional(x, y) { arguments; }
11+
////someOptional(/*2*/);
12+
////someOptional(1, 2, 3);
13+
////someOptional(); // no error here; x and y are optional in JS
1014

1115
goTo.marker('1');
1216
verify.signatureHelpCountIs(1);
1317
verify.currentSignatureParameterCountIs(1);
14-
verify.currentSignatureHelpIs('fnTest(...args: any[]): void');
18+
verify.currentSignatureHelpIs('allOptional(...args: any[]): void');
1519
verify.currentParameterHelpArgumentNameIs('args');
1620
verify.currentParameterSpanIs("...args: any[]");
17-
verify.numberOfErrorsInCurrentFile(0);
21+
22+
goTo.marker('2');
23+
verify.signatureHelpCountIs(1);
24+
verify.currentSignatureParameterCountIs(3);
25+
verify.currentSignatureHelpIs('someOptional(x: any, y: any, ...args: any[]): void');
26+
verify.currentParameterHelpArgumentNameIs('x');
27+
verify.currentParameterSpanIs("x: any");
28+
verify.numberOfErrorsInCurrentFile(0);

0 commit comments

Comments
 (0)