Skip to content

Commit 8f5d57a

Browse files
feat: port additional validation checks to `Langium (#576)
Closes partially #543 ### Summary of Changes Show an error if: * A union type has no type arguments * A callable type has optional parameters * Positional type arguments occur after named type arguments * Positional arguments occur after named arguments * A parameter is variadic and optional --------- Co-authored-by: megalinter-bot <[email protected]>
1 parent 47ce782 commit 8f5d57a

File tree

22 files changed

+297
-27
lines changed

22 files changed

+297
-27
lines changed

src/language/grammar/safe-ds.langium

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,16 +1045,16 @@ terminal TEMPLATE_STRING_END returns string: TEMPLATE_EXPRESSION_END STRING_TEXT
10451045
terminal CALL_TYPE_ARGUMENT_LIST_START:
10461046
'<'
10471047
(?=
1048-
/\s*/
1049-
( '*' // Star projection as positional type argument
1050-
| 'in' // Contravariant type projection as positional type argument
1051-
| 'out' // Covariant type projection as positional type argument
1052-
| 'literal' // Invariant literal type as positional type argument
1053-
| 'union' // Invariant union type as positional type argument
1054-
| '>' // Empty type argument list
1055-
| ID /\s*/
1056-
( '=' // Named type argument
1057-
| ('.' /\s*/ ID /\s*/)* (',' | '>') // Invariant type projection as positional type argument
1048+
/[\s»«]*/
1049+
( '*' // Star projection as positional type argument
1050+
| 'in' // Contravariant type projection as positional type argument
1051+
| 'out' // Covariant type projection as positional type argument
1052+
| 'literal' // Invariant literal type as positional type argument
1053+
| 'union' // Invariant union type as positional type argument
1054+
| '>' // Empty type argument list
1055+
| ID /[\s»«]*/
1056+
( '=' // Named type argument
1057+
| ('.' /[\s»«]*/ ID /[\s»«]*/)* (',' | '>') // Invariant type projection as positional type argument
10581058
)
10591059
)
10601060
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { SdsArgumentList } from '../../generated/ast.js';
2+
import { ValidationAcceptor } from 'langium';
3+
4+
export const CODE_ARGUMENT_LIST_POSITIONAL_AFTER_NAMED = 'argument-list/positional-after-named';
5+
6+
export const argumentListMustNotHavePositionalArgumentsAfterNamedArguments = (
7+
node: SdsArgumentList,
8+
accept: ValidationAcceptor,
9+
): void => {
10+
let foundNamed = false;
11+
for (const argument of node.arguments) {
12+
if (argument.parameter) {
13+
foundNamed = true;
14+
} else if (foundNamed) {
15+
accept('error', 'After the first named argument all arguments must be named.', {
16+
node: argument,
17+
code: CODE_ARGUMENT_LIST_POSITIONAL_AFTER_NAMED,
18+
});
19+
}
20+
}
21+
};

src/language/validation/other/declarations/parameterLists.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export const parameterListMustNotHaveOptionalAndVariadicParameters = (
1111
) => {
1212
const hasOptional = node.parameters.find((p) => p.defaultValue);
1313
if (hasOptional) {
14-
const variadicParameters = node.parameters.filter((p) => p.variadic);
14+
const variadicRequiredParameters = node.parameters.filter((p) => p.variadic && !p.defaultValue);
1515

16-
for (const variadic of variadicParameters) {
16+
for (const variadic of variadicRequiredParameters) {
1717
accept('error', 'A callable with optional parameters must not have a variadic parameter.', {
1818
node: variadic,
1919
property: 'name',
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { SdsParameter } from '../../../generated/ast.js';
2+
import { ValidationAcceptor } from 'langium';
3+
4+
export const CODE_PARAMETER_VARIADIC_AND_OPTIONAL = 'parameter/variadic-and-optional';
5+
6+
export const parameterMustNotBeVariadicAndOptional = (node: SdsParameter, accept: ValidationAcceptor) => {
7+
if (node.variadic && node.defaultValue) {
8+
accept('error', 'Variadic parameters must not be optional.', {
9+
node,
10+
property: 'name',
11+
code: CODE_PARAMETER_VARIADIC_AND_OPTIONAL,
12+
});
13+
}
14+
};

src/language/validation/imports.ts renamed to src/language/validation/other/imports.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ValidationAcceptor } from 'langium';
2-
import { SdsImportAlias } from '../generated/ast.js';
3-
import { isWildcardImport } from '../ast/checks.js';
2+
import { SdsImportAlias } from '../../generated/ast.js';
3+
import { isWildcardImport } from '../../ast/checks.js';
44

55
export const CODE_IMPORT_WILDCARD_IMPORT_WITH_ALIAS = 'import/wildcard-import-with-alias';
66

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { SdsCallableType } from '../../../generated/ast.js';
2+
import { ValidationAcceptor } from 'langium';
3+
import { parametersOrEmpty } from '../../../ast/shortcuts.js';
4+
5+
export const CODE_CALLABLE_TYPE_NO_OPTIONAL_PARAMETERS = 'callable-type/no-optional-parameters';
6+
7+
export const callableTypeMustNotHaveOptionalParameters = (node: SdsCallableType, accept: ValidationAcceptor): void => {
8+
for (const parameter of parametersOrEmpty(node.parameterList)) {
9+
if (parameter.defaultValue && !parameter.variadic) {
10+
accept('error', 'A callable type must not have optional parameters.', {
11+
node: parameter,
12+
property: 'defaultValue',
13+
code: CODE_CALLABLE_TYPE_NO_OPTIONAL_PARAMETERS,
14+
});
15+
}
16+
}
17+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { SdsTypeArgumentList } from '../../../generated/ast.js';
2+
import { ValidationAcceptor } from 'langium';
3+
4+
export const CODE_TYPE_ARGUMENT_LIST_POSITIONAL_AFTER_NAMED = 'type-argument-list/positional-after-named';
5+
6+
export const typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments = (
7+
node: SdsTypeArgumentList,
8+
accept: ValidationAcceptor,
9+
): void => {
10+
let foundNamed = false;
11+
for (const typeArgument of node.typeArguments) {
12+
if (typeArgument.typeParameter) {
13+
foundNamed = true;
14+
} else if (foundNamed) {
15+
accept('error', 'After the first named type argument all type arguments must be named.', {
16+
node: typeArgument,
17+
code: CODE_TYPE_ARGUMENT_LIST_POSITIONAL_AFTER_NAMED,
18+
});
19+
}
20+
}
21+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { SdsUnionType } from '../../../generated/ast.js';
2+
import { ValidationAcceptor } from 'langium';
3+
import { typeArgumentsOrEmpty } from '../../../ast/shortcuts.js';
4+
import { isEmpty } from 'radash';
5+
6+
export const CODE_UNION_TYPE_MISSING_TYPE_ARGUMENTS = 'union-type/missing-type-arguments';
7+
8+
export const unionTypeMustHaveTypeArguments = (node: SdsUnionType, accept: ValidationAcceptor): void => {
9+
if (isEmpty(typeArgumentsOrEmpty(node.typeArgumentList))) {
10+
accept('error', 'A union type must have least one type argument.', {
11+
node,
12+
property: 'typeArgumentList',
13+
code: CODE_UNION_TYPE_MISSING_TYPE_ARGUMENTS,
14+
});
15+
}
16+
};

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ import {
3737
parameterListMustNotHaveRequiredParametersAfterOptionalParameters,
3838
parameterListVariadicParameterMustBeLast,
3939
} from './other/declarations/parameterLists.js';
40-
import { importAliasMustNotBeUsedForWildcardImports } from './imports.js';
40+
import { importAliasMustNotBeUsedForWildcardImports } from './other/imports.js';
41+
import { unionTypeMustHaveTypeArguments } from './other/types/unionTypes.js';
42+
import { callableTypeMustNotHaveOptionalParameters } from './other/types/callableTypes.js';
43+
import { typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/types/typeArgumentLists.js';
44+
import { argumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/argumentLists.js';
45+
import { parameterMustNotBeVariadicAndOptional } from './other/declarations/parameters.js';
4146

4247
/**
4348
* Register custom validation checks.
@@ -48,9 +53,10 @@ export const registerValidationChecks = function (services: SafeDsServices) {
4853
const checks: ValidationChecks<SafeDsAstType> = {
4954
SdsAssignment: [assignmentShouldHaveMoreThanWildcardsAsAssignees],
5055
SdsAnnotation: [annotationMustContainUniqueNames, annotationParameterListShouldNotBeEmpty],
56+
SdsArgumentList: [argumentListMustNotHavePositionalArgumentsAfterNamedArguments],
5157
SdsAttribute: [attributeMustHaveTypeHint],
5258
SdsBlockLambda: [blockLambdaMustContainUniqueNames],
53-
SdsCallableType: [callableTypeMustContainUniqueNames],
59+
SdsCallableType: [callableTypeMustContainUniqueNames, callableTypeMustNotHaveOptionalParameters],
5460
SdsClass: [classMustContainUniqueNames],
5561
SdsClassBody: [classBodyShouldNotBeEmpty],
5662
SdsConstraintList: [constraintListShouldNotBeEmpty],
@@ -62,7 +68,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
6268
SdsFunction: [functionMustContainUniqueNames, functionResultListShouldNotBeEmpty],
6369
SdsImportAlias: [importAliasMustNotBeUsedForWildcardImports],
6470
SdsModule: [moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage],
65-
SdsParameter: [parameterMustHaveTypeHint],
71+
SdsParameter: [parameterMustHaveTypeHint, parameterMustNotBeVariadicAndOptional],
6672
SdsParameterList: [
6773
parameterListMustNotHaveOptionalAndVariadicParameters,
6874
parameterListMustNotHaveRequiredParametersAfterOptionalParameters,
@@ -72,9 +78,10 @@ export const registerValidationChecks = function (services: SafeDsServices) {
7278
SdsResult: [resultMustHaveTypeHint],
7379
SdsSegment: [segmentMustContainUniqueNames, segmentResultListShouldNotBeEmpty],
7480
SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts],
81+
SdsTypeArgumentList: [typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments],
7582
SdsTypeParameterConstraint: [typeParameterConstraintLeftOperandMustBeOwnTypeParameter],
7683
SdsTypeParameterList: [typeParameterListShouldNotBeEmpty],
77-
SdsUnionType: [unionTypeShouldNotHaveASingularTypeArgument],
84+
SdsUnionType: [unionTypeMustHaveTypeArguments, unionTypeShouldNotHaveASingularTypeArgument],
7885
SdsYield: [yieldMustNotBeUsedInPipeline],
7986
};
8087
registry.register(checks, validator);

src/language/validation/value.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// TODO: implement value-related checks here, including:
2+
// - division by zero
3+
// - must be constant

0 commit comments

Comments
 (0)