Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow trailing commas in function parameter and argument lists #8942

Merged
5 commits merged into from
Jun 7, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10321,8 +10321,8 @@ namespace ts {
return -1;
}

function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature) {
let adjustedArgCount: number; // Apparent number of arguments we will have in this call
function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature, signatureHelpTrailingComma = false) {
let argCount: number; // Apparent number of arguments we will have in this call
let typeArguments: NodeArray<TypeNode>; // Type arguments (undefined if none)
let callIsIncomplete: boolean; // In incomplete call we want to be lenient when we have too few arguments
let isDecorator: boolean;
Expand All @@ -10333,7 +10333,7 @@ namespace ts {

// Even if the call is incomplete, we'll have a missing expression as our last argument,
// so we can say the count is just the arg list length
adjustedArgCount = args.length;
argCount = args.length;
typeArguments = undefined;

if (tagExpression.template.kind === SyntaxKind.TemplateExpression) {
Expand All @@ -10356,7 +10356,7 @@ namespace ts {
else if (node.kind === SyntaxKind.Decorator) {
isDecorator = true;
typeArguments = undefined;
adjustedArgCount = getEffectiveArgumentCount(node, /*args*/ undefined, signature);
argCount = getEffectiveArgumentCount(node, /*args*/ undefined, signature);
}
else {
const callExpression = <CallExpression>node;
Expand All @@ -10367,8 +10367,7 @@ namespace ts {
return signature.minArgumentCount === 0;
}

// For IDE scenarios we may have an incomplete call, so a trailing comma is tantamount to adding another argument.
adjustedArgCount = callExpression.arguments.hasTrailingComma ? args.length + 1 : args.length;
argCount = signatureHelpTrailingComma ? args.length + 1 : args.length;

// If we are missing the close paren, the call is incomplete.
callIsIncomplete = (<CallExpression>callExpression).arguments.end === callExpression.end;
Expand All @@ -10392,12 +10391,12 @@ namespace ts {
}

// Too many arguments implies incorrect arity.
if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) {
if (!signature.hasRestParameter && argCount > signature.parameters.length) {
return false;
}

// If the call is incomplete, we should skip the lower bound check.
const hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount;
const hasEnoughArguments = argCount >= signature.minArgumentCount;
return callIsIncomplete || hasEnoughArguments;
}

Expand Down Expand Up @@ -10969,6 +10968,11 @@ namespace ts {
let resultOfFailedInference: InferenceContext;
let result: Signature;

// If we are in signature help, a trailing comma indicates that we intend to provide another argument,
// so we will only accept overloads with arity at least 1 higher than the current number of provided arguments.
const signatureHelpTrailingComma =
candidatesOutArray && node.kind === SyntaxKind.CallExpression && (<CallExpression>node).arguments.hasTrailingComma;

// Section 4.12.1:
// if the candidate list contains one or more signatures for which the type of each argument
// expression is a subtype of each corresponding parameter type, the return type of the first
Expand All @@ -10980,14 +10984,14 @@ namespace ts {
// is just important for choosing the best signature. So in the case where there is only one
// signature, the subtype pass is useless. So skipping it is an optimization.
if (candidates.length > 1) {
result = chooseOverload(candidates, subtypeRelation);
result = chooseOverload(candidates, subtypeRelation, signatureHelpTrailingComma);
}
if (!result) {
// Reinitialize these pointers for round two
candidateForArgumentError = undefined;
candidateForTypeArgumentError = undefined;
resultOfFailedInference = undefined;
result = chooseOverload(candidates, assignableRelation);
result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma);
}
if (result) {
return result;
Expand Down Expand Up @@ -11058,9 +11062,9 @@ namespace ts {
diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo));
}

function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>) {
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
for (const originalCandidate of candidates) {
if (!hasCorrectArity(node, args, originalCandidate)) {
if (!hasCorrectArity(node, args, originalCandidate, signatureHelpTrailingComma)) {
continue;
}

Expand Down Expand Up @@ -18027,10 +18031,6 @@ namespace ts {
}

function checkGrammarParameterList(parameters: NodeArray<ParameterDeclaration>) {
if (checkGrammarForDisallowedTrailingComma(parameters)) {
return true;
}

let seenOptionalParameter = false;
const parameterCount = parameters.length;

Expand Down Expand Up @@ -18149,8 +18149,7 @@ namespace ts {
}

function checkGrammarArguments(node: CallExpression, args: NodeArray<Expression>): boolean {
return checkGrammarForDisallowedTrailingComma(args) ||
checkGrammarForOmittedArgument(node, args);
return checkGrammarForOmittedArgument(node, args);
}

function checkGrammarHeritageClause(node: HeritageClause): boolean {
Expand Down
12 changes: 0 additions & 12 deletions tests/baselines/reference/ArrowFunction2.errors.txt

This file was deleted.

8 changes: 0 additions & 8 deletions tests/baselines/reference/ArrowFunction2.js

This file was deleted.

This file was deleted.

11 changes: 0 additions & 11 deletions tests/baselines/reference/parserErrorRecovery_ArgumentList5.js

This file was deleted.

This file was deleted.

This file was deleted.

8 changes: 0 additions & 8 deletions tests/baselines/reference/parserParameterList12.errors.txt

This file was deleted.

5 changes: 5 additions & 0 deletions tests/baselines/reference/parserParameterList12.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
=== tests/cases/conformance/parser/ecmascript5/ParameterLists/parserParameterList12.ts ===
function F(a,) {
>F : Symbol(F, Decl(parserParameterList12.ts, 0, 0))
>a : Symbol(a, Decl(parserParameterList12.ts, 0, 11))
}
5 changes: 5 additions & 0 deletions tests/baselines/reference/parserParameterList12.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
=== tests/cases/conformance/parser/ecmascript5/ParameterLists/parserParameterList12.ts ===
function F(a,) {
>F : (a: any) => void
>a : any
}
12 changes: 0 additions & 12 deletions tests/baselines/reference/parserX_ArrowFunction2.errors.txt

This file was deleted.

8 changes: 0 additions & 8 deletions tests/baselines/reference/parserX_ArrowFunction2.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
tests/cases/conformance/es7/trailingCommasInFunctionParametersAndArguments.ts(20,11): error TS1138: Parameter declaration expected.


==== tests/cases/conformance/es7/trailingCommasInFunctionParametersAndArguments.ts (1 errors) ====
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's weird, I wasn't expecting to see a .errors.txt and a .types. Usually they are mutually exclusive. Did the .types file manage to skip the broken getter/setter pair?

function f1(x,) {}

f1(1,);

function f2(...args,) {}

f2(...[],);

// Not confused by overloads
declare function f3(x, ): number;
declare function f3(x, y,): string;

<number>f3(1,);
<string>f3(1, 2,);

// Works for constructors too
class X {
constructor(a,) { }
// *Not* allowed in getter
get x(,) { return 0; }
~
!!! error TS1138: Parameter declaration expected.
set x(value,) { }
}
interface Y {
new(x,);
(x,);
}

new X(1,);

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//// [trailingCommasInFunctionParametersAndArguments.ts]
function f1(x,) {}

f1(1,);

function f2(...args,) {}

f2(...[],);

// Not confused by overloads
declare function f3(x, ): number;
declare function f3(x, y,): string;

<number>f3(1,);
<string>f3(1, 2,);

// Works for constructors too
class X {
constructor(a,) { }
// *Not* allowed in getter
get x(,) { return 0; }
set x(value,) { }
}
interface Y {
new(x,);
(x,);
}

new X(1,);


//// [trailingCommasInFunctionParametersAndArguments.js]
function f1(x) { }
f1(1);
function f2() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i - 0] = arguments[_i];
}
}
f2.apply(void 0, []);
f3(1);
f3(1, 2);
// Works for constructors too
var X = (function () {
function X(a) {
}
Object.defineProperty(X.prototype, "x", {
// *Not* allowed in getter
get: function () { return 0; },
set: function (value) { },
enumerable: true,
configurable: true
});
return X;
}());
new X(1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
=== tests/cases/conformance/es7/trailingCommasInFunctionParametersAndArguments.ts ===
function f1(x,) {}
>f1 : Symbol(f1, Decl(trailingCommasInFunctionParametersAndArguments.ts, 0, 0))
>x : Symbol(x, Decl(trailingCommasInFunctionParametersAndArguments.ts, 0, 12))

f1(1,);
>f1 : Symbol(f1, Decl(trailingCommasInFunctionParametersAndArguments.ts, 0, 0))

function f2(...args,) {}
>f2 : Symbol(f2, Decl(trailingCommasInFunctionParametersAndArguments.ts, 2, 7))
>args : Symbol(args, Decl(trailingCommasInFunctionParametersAndArguments.ts, 4, 12))

f2(...[],);
>f2 : Symbol(f2, Decl(trailingCommasInFunctionParametersAndArguments.ts, 2, 7))

// Not confused by overloads
declare function f3(x, ): number;
>f3 : Symbol(f3, Decl(trailingCommasInFunctionParametersAndArguments.ts, 6, 11), Decl(trailingCommasInFunctionParametersAndArguments.ts, 9, 33))
>x : Symbol(x, Decl(trailingCommasInFunctionParametersAndArguments.ts, 9, 20))

declare function f3(x, y,): string;
>f3 : Symbol(f3, Decl(trailingCommasInFunctionParametersAndArguments.ts, 6, 11), Decl(trailingCommasInFunctionParametersAndArguments.ts, 9, 33))
>x : Symbol(x, Decl(trailingCommasInFunctionParametersAndArguments.ts, 10, 20))
>y : Symbol(y, Decl(trailingCommasInFunctionParametersAndArguments.ts, 10, 22))

<number>f3(1,);
>f3 : Symbol(f3, Decl(trailingCommasInFunctionParametersAndArguments.ts, 6, 11), Decl(trailingCommasInFunctionParametersAndArguments.ts, 9, 33))

<string>f3(1, 2,);
>f3 : Symbol(f3, Decl(trailingCommasInFunctionParametersAndArguments.ts, 6, 11), Decl(trailingCommasInFunctionParametersAndArguments.ts, 9, 33))

// Works for constructors too
class X {
>X : Symbol(X, Decl(trailingCommasInFunctionParametersAndArguments.ts, 13, 18))

constructor(a,) { }
>a : Symbol(a, Decl(trailingCommasInFunctionParametersAndArguments.ts, 17, 16))
}
new X(1,);
>X : Symbol(X, Decl(trailingCommasInFunctionParametersAndArguments.ts, 13, 18))

Loading