Skip to content

Commit bd73bc5

Browse files
authored
feat: port validation of parameter lists (#573)
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.
1 parent af13e28 commit bd73bc5

File tree

6 files changed

+456
-14
lines changed

6 files changed

+456
-14
lines changed

src/language/grammar/safe-ds.langium

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -391,19 +391,6 @@ SdsParameterList returns SdsParameterList:
391391
')'
392392
;
393393

394-
interface SdsLambdaParameterList extends SdsExpression, SdsParameterList {}
395-
396-
SdsLambdaParameterList returns SdsParameterList:
397-
{SdsLambdaParameterList}
398-
'('
399-
(
400-
parameters+=SdsParameter
401-
(',' parameters+=SdsParameter)*
402-
','?
403-
)?
404-
')'
405-
;
406-
407394
interface SdsParameter extends SdsLocalVariable {
408395
variadic: boolean
409396
^type?: SdsType
@@ -522,7 +509,7 @@ interface SdsExpressionLambda extends SdsLambda {
522509
}
523510

524511
SdsLambda returns SdsExpression:
525-
SdsLambdaParameterList
512+
SdsParameterList
526513
(
527514
{SdsBlockLambda.parameterList=current} body=SdsBlockLambdaBlock
528515
| {SdsExpressionLambda.parameterList=current} '->' result=SdsExpression
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { SdsParameterList } from '../../../generated/ast.js';
2+
import { ValidationAcceptor } from 'langium';
3+
4+
export const CODE_PARAMETER_LIST_OPTIONAL_AND_VARIADIC = 'parameter-list/optional-and-variadic';
5+
export const CODE_PARAMETER_LIST_REQUIRED_AFTER_OPTIONAL = 'parameter-list/required-after-optional';
6+
export const CODE_PARAMETER_LIST_VARIADIC_NOT_LAST = 'parameter-list/variadic-not-last';
7+
8+
export const parameterListMustNotHaveOptionalAndVariadicParameters = (
9+
node: SdsParameterList,
10+
accept: ValidationAcceptor,
11+
) => {
12+
const hasOptional = node.parameters.find((p) => p.defaultValue);
13+
if (hasOptional) {
14+
const variadicParameters = node.parameters.filter((p) => p.variadic);
15+
16+
for (const variadic of variadicParameters) {
17+
accept('error', 'A callable with optional parameters must not have a variadic parameter.', {
18+
node: variadic,
19+
property: 'name',
20+
code: CODE_PARAMETER_LIST_OPTIONAL_AND_VARIADIC,
21+
});
22+
}
23+
}
24+
};
25+
26+
export const parameterListMustNotHaveRequiredParametersAfterOptionalParameters = (
27+
node: SdsParameterList,
28+
accept: ValidationAcceptor,
29+
) => {
30+
let foundOptional = false;
31+
for (const parameter of node.parameters) {
32+
if (parameter.defaultValue) {
33+
foundOptional = true;
34+
} else if (foundOptional && !parameter.variadic) {
35+
accept('error', 'After the first optional parameter all parameters must be optional.', {
36+
node: parameter,
37+
property: 'name',
38+
code: CODE_PARAMETER_LIST_REQUIRED_AFTER_OPTIONAL,
39+
});
40+
}
41+
}
42+
};
43+
44+
export const parameterListVariadicParameterMustBeLast = (node: SdsParameterList, accept: ValidationAcceptor) => {
45+
let foundVariadic = false;
46+
for (const parameter of node.parameters) {
47+
if (foundVariadic) {
48+
accept('error', 'After a variadic parameter no more parameters must be specified.', {
49+
node: parameter,
50+
property: 'name',
51+
code: CODE_PARAMETER_LIST_VARIADIC_NOT_LAST,
52+
});
53+
}
54+
55+
if (parameter.variadic) {
56+
foundVariadic = true;
57+
}
58+
}
59+
};

src/language/validation/safe-ds-validator.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import { yieldMustNotBeUsedInPipeline } from './other/statements/assignments.js'
1919
import { attributeMustHaveTypeHint, parameterMustHaveTypeHint, resultMustHaveTypeHint } from './types.js';
2020
import { moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage } from './other/modules.js';
2121
import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js';
22+
import {
23+
parameterListMustNotHaveOptionalAndVariadicParameters,
24+
parameterListMustNotHaveRequiredParametersAfterOptionalParameters,
25+
parameterListVariadicParameterMustBeLast,
26+
} from './other/declarations/parameterLists.js';
2227

2328
/**
2429
* Register custom validation checks.
@@ -38,6 +43,11 @@ export const registerValidationChecks = function (services: SafeDsServices) {
3843
SdsFunction: [functionResultListShouldNotBeEmpty],
3944
SdsModule: [moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage],
4045
SdsParameter: [parameterMustHaveTypeHint],
46+
SdsParameterList: [
47+
parameterListMustNotHaveOptionalAndVariadicParameters,
48+
parameterListMustNotHaveRequiredParametersAfterOptionalParameters,
49+
parameterListVariadicParameterMustBeLast,
50+
],
4151
SdsResult: [resultMustHaveTypeHint],
4252
SdsSegment: [segmentResultListShouldNotBeEmpty],
4353
SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package tests.validation.other.declarations.parameterLists.mustNotHaveRequiredAfterOptional
2+
3+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
4+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
5+
// $TEST$ error "After the first optional parameter all parameters must be optional."
6+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
7+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
8+
annotation MyAnnotation1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)
9+
10+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
11+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
12+
annotation MyAnnotation2(»a«: Int, »b«: Int = 1)
13+
14+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
15+
annotation MyAnnotation3(»a«: Int)
16+
17+
18+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
19+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
20+
// $TEST$ error "After the first optional parameter all parameters must be optional."
21+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
22+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
23+
class MyClass1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)
24+
25+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
26+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
27+
class MyClass2(»a«: Int, »b«: Int = 1)
28+
29+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
30+
class MyClass3(»a«: Int)
31+
32+
33+
enum MyEnum {
34+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
35+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
36+
// $TEST$ error "After the first optional parameter all parameters must be optional."
37+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
38+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
39+
MyEnumVariant1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)
40+
41+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
42+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
43+
MyEnumVariant2(»a«: Int, »b«: Int = 1)
44+
45+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
46+
MyEnumVariant3(»a«: Int)
47+
}
48+
49+
50+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
51+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
52+
// $TEST$ error "After the first optional parameter all parameters must be optional."
53+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
54+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
55+
fun myFunction1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int)
56+
57+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
58+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
59+
fun myFunction2(»a«: Int, »b«: Int = 1)
60+
61+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
62+
fun myFunction3(»a«: Int)
63+
64+
65+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
66+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
67+
// $TEST$ error "After the first optional parameter all parameters must be optional."
68+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
69+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
70+
segment mySegment1(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) {}
71+
72+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
73+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
74+
segment mySegment2(»a«: Int, »b«: Int = 1) {}
75+
76+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
77+
segment mySegment3(»a«: Int) {}
78+
79+
80+
pipeline myPipeline {
81+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
82+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
83+
// $TEST$ error "After the first optional parameter all parameters must be optional."
84+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
85+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
86+
(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) {};
87+
88+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
89+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
90+
(»a«: Int, »b«: Int = 1) {};
91+
92+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
93+
(»a«: Int) {};
94+
95+
96+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
97+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
98+
// $TEST$ error "After the first optional parameter all parameters must be optional."
99+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
100+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
101+
(»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) -> 1;
102+
103+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
104+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
105+
(»a«: Int, »b«: Int = 1) -> 1;
106+
107+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
108+
(»a«: Int) -> 1;
109+
}
110+
111+
112+
fun myFunction4(
113+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
114+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
115+
// $TEST$ error "After the first optional parameter all parameters must be optional."
116+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
117+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
118+
p1: (»a«: Int, »b«: Int = 1, »c«: Int, »d«: Int = 2, vararg »e«: Int) -> (),
119+
120+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
121+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
122+
p2: (»a«: Int, »b«: Int = 1) -> (),
123+
124+
// $TEST$ no error "After the first optional parameter all parameters must be optional."
125+
p3: (»a«: Int) -> (),
126+
)

0 commit comments

Comments
 (0)