Skip to content

Commit

Permalink
feat: port validation of parameter lists (#573)
Browse files Browse the repository at this point in the history
Closes partially #543.

### Summary of Changes

Show an error if a parameter list:
* has more parameters after a variadic one,
* has optional parameters and a variadic one, or
* has required parameters after optional ones.
  • Loading branch information
lars-reimann authored Sep 21, 2023
1 parent af13e28 commit bd73bc5
Show file tree
Hide file tree
Showing 6 changed files with 456 additions and 14 deletions.
15 changes: 1 addition & 14 deletions src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -391,19 +391,6 @@ SdsParameterList returns SdsParameterList:
')'
;

interface SdsLambdaParameterList extends SdsExpression, SdsParameterList {}

SdsLambdaParameterList returns SdsParameterList:
{SdsLambdaParameterList}
'('
(
parameters+=SdsParameter
(',' parameters+=SdsParameter)*
','?
)?
')'
;

interface SdsParameter extends SdsLocalVariable {
variadic: boolean
^type?: SdsType
Expand Down Expand Up @@ -522,7 +509,7 @@ interface SdsExpressionLambda extends SdsLambda {
}

SdsLambda returns SdsExpression:
SdsLambdaParameterList
SdsParameterList
(
{SdsBlockLambda.parameterList=current} body=SdsBlockLambdaBlock
| {SdsExpressionLambda.parameterList=current} '->' result=SdsExpression
Expand Down
59 changes: 59 additions & 0 deletions src/language/validation/other/declarations/parameterLists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { SdsParameterList } from '../../../generated/ast.js';
import { ValidationAcceptor } from 'langium';

export const CODE_PARAMETER_LIST_OPTIONAL_AND_VARIADIC = 'parameter-list/optional-and-variadic';
export const CODE_PARAMETER_LIST_REQUIRED_AFTER_OPTIONAL = 'parameter-list/required-after-optional';
export const CODE_PARAMETER_LIST_VARIADIC_NOT_LAST = 'parameter-list/variadic-not-last';

export const parameterListMustNotHaveOptionalAndVariadicParameters = (
node: SdsParameterList,
accept: ValidationAcceptor,
) => {
const hasOptional = node.parameters.find((p) => p.defaultValue);
if (hasOptional) {
const variadicParameters = node.parameters.filter((p) => p.variadic);

for (const variadic of variadicParameters) {
accept('error', 'A callable with optional parameters must not have a variadic parameter.', {
node: variadic,
property: 'name',
code: CODE_PARAMETER_LIST_OPTIONAL_AND_VARIADIC,
});
}
}
};

export const parameterListMustNotHaveRequiredParametersAfterOptionalParameters = (
node: SdsParameterList,
accept: ValidationAcceptor,
) => {
let foundOptional = false;
for (const parameter of node.parameters) {
if (parameter.defaultValue) {
foundOptional = true;
} else if (foundOptional && !parameter.variadic) {
accept('error', 'After the first optional parameter all parameters must be optional.', {
node: parameter,
property: 'name',
code: CODE_PARAMETER_LIST_REQUIRED_AFTER_OPTIONAL,
});
}
}
};

export const parameterListVariadicParameterMustBeLast = (node: SdsParameterList, accept: ValidationAcceptor) => {
let foundVariadic = false;
for (const parameter of node.parameters) {
if (foundVariadic) {
accept('error', 'After a variadic parameter no more parameters must be specified.', {
node: parameter,
property: 'name',
code: CODE_PARAMETER_LIST_VARIADIC_NOT_LAST,
});
}

if (parameter.variadic) {
foundVariadic = true;
}
}
};
10 changes: 10 additions & 0 deletions src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import { yieldMustNotBeUsedInPipeline } from './other/statements/assignments.js'
import { attributeMustHaveTypeHint, parameterMustHaveTypeHint, resultMustHaveTypeHint } from './types.js';
import { moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage } from './other/modules.js';
import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js';
import {
parameterListMustNotHaveOptionalAndVariadicParameters,
parameterListMustNotHaveRequiredParametersAfterOptionalParameters,
parameterListVariadicParameterMustBeLast,
} from './other/declarations/parameterLists.js';

/**
* Register custom validation checks.
Expand All @@ -38,6 +43,11 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsFunction: [functionResultListShouldNotBeEmpty],
SdsModule: [moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage],
SdsParameter: [parameterMustHaveTypeHint],
SdsParameterList: [
parameterListMustNotHaveOptionalAndVariadicParameters,
parameterListMustNotHaveRequiredParametersAfterOptionalParameters,
parameterListVariadicParameterMustBeLast,
],
SdsResult: [resultMustHaveTypeHint],
SdsSegment: [segmentResultListShouldNotBeEmpty],
SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package tests.validation.other.declarations.parameterLists.mustNotHaveRequiredAfterOptional

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
annotation MyAnnotation1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
annotation MyAnnotation2(»a«: Int, »b«: Int = 1)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
annotation MyAnnotation3(»a«: Int)


// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
class MyClass1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
class MyClass2(»a«: Int, »b«: Int = 1)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
class MyClass3(»a«: Int)


enum MyEnum {
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
MyEnumVariant1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
MyEnumVariant2(»a«: Int, »b«: Int = 1)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
MyEnumVariant3(»a«: Int)
}


// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
fun myFunction1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
fun myFunction2(»a«: Int, »b«: Int = 1)

// $TEST$ no error "After the first optional parameter all parameters must be optional."
fun myFunction3(»a«: Int)


// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
segment mySegment1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) {}

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
segment mySegment2(»a«: Int, »b«: Int = 1) {}

// $TEST$ no error "After the first optional parameter all parameters must be optional."
segment mySegment3(»a«: Int) {}


pipeline myPipeline {
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) {};

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
(»a«: Int, »b«: Int = 1) {};

// $TEST$ no error "After the first optional parameter all parameters must be optional."
(»a«: Int) {};


// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) -> 1;

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
(»a«: Int, »b«: Int = 1) -> 1;

// $TEST$ no error "After the first optional parameter all parameters must be optional."
(»a«: Int) -> 1;
}


fun myFunction4(
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
p1: (»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) -> (),

// $TEST$ no error "After the first optional parameter all parameters must be optional."
// $TEST$ no error "After the first optional parameter all parameters must be optional."
p2: (»a«: Int, »b«: Int = 1) -> (),

// $TEST$ no error "After the first optional parameter all parameters must be optional."
p3: (»a«: Int) -> (),
)
Loading

0 comments on commit bd73bc5

Please sign in to comment.