Skip to content

Commit

Permalink
feat: optional parameters must be passed by name
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann committed Jan 7, 2025
1 parent 790fc17 commit 297a0b9
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../../../helpers/nodeProperties.js';
import type { SafeDsServices } from '../../../safe-ds-module.js';

export const CODE_ANNOTATION_CALL_CONSTANT_ARGUMENT = 'annotation-call/constant-argument';
export const CODE_ANNOTATION_CALL_CONSTANT_ARGUMENT = 'annotation-call/non-constant-argument';
export const CODE_ANNOTATION_CALL_MISSING_ARGUMENT_LIST = 'annotation-call/missing-argument-list';
export const CODE_ANNOTATION_CALL_TARGET_PARAMETER = 'annotation-call/target-parameter';
export const CODE_ANNOTATION_CALL_TARGET_RESULT = 'annotation-call/target-result';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { SafeDsServices } from '../../../safe-ds-module.js';
import type { SdsArgument } from '../../../generated/ast.js';
import { ValidationAcceptor } from 'langium';
import { Argument, Parameter } from '../../../helpers/nodeProperties.js';

export const CODE_ARGUMENT_POSITIONAL = 'argument/positional';

export const argumentMustBeNamedIfParameterIsOptional = (services: SafeDsServices) => {
const nodeMapper = services.helpers.NodeMapper;

return (node: SdsArgument, accept: ValidationAcceptor) => {
const parameter = nodeMapper.argumentToParameter(node);
if (!Parameter.isOptional(parameter)) {
return;
}

if (!Argument.isNamed(node)) {
accept('error', 'Argument must be named if the parameter is optional.', {
node,
property: 'value',
code: CODE_ARGUMENT_POSITIONAL,
});
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type SdsCall } from '../../../generated/ast.js';
import { getArguments, Parameter } from '../../../helpers/nodeProperties.js';
import { SafeDsServices } from '../../../safe-ds-module.js';

export const CODE_CALL_CONSTANT_ARGUMENT = 'call/constant-argument';
export const CODE_CALL_CONSTANT_ARGUMENT = 'call/non-constant-argument';
export const CODE_CALL_INFINITE_RECURSION = 'call/infinite-recursion';

export const callArgumentMustBeConstantIfParameterIsConstant = (services: SafeDsServices) => {
Expand Down
20 changes: 11 additions & 9 deletions packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,22 +192,14 @@ import {
outputStatementMustOnlyBeUsedInPipeline,
} from './other/statements/outputStatements.js';
import { messageOfConstraintsMustOnlyReferenceConstantParameters } from './other/declarations/constraints.js';
import { argumentMustBeNamedIfParameterIsOptional } from './other/expressions/arguments.js';

/**
* Register custom validation checks.
*/
export const registerValidationChecks = function (services: SafeDsServices) {
const registry = services.validation.ValidationRegistry;
const checks: ValidationChecks<SafeDsAstType> = {
SdsAssignee: [
assigneeAssignedResultShouldNotBeDeprecated(services),
assigneeAssignedResultShouldNotBeExperimental(services),
],
SdsAssignment: [
assignmentAssigneeMustGetValue(services),
assignmentShouldNotImplicitlyIgnoreResult(services),
assignmentShouldHaveMoreThanWildcardsAsAssignees(services),
],
SdsAbstractCall: [
argumentListMustNotHaveTooManyArguments(services),
argumentListMustSetAllRequiredParameters(services),
Expand All @@ -230,11 +222,21 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsArgument: [
argumentCorrespondingParameterShouldNotBeDeprecated(services),
argumentCorrespondingParameterShouldNotBeExperimental(services),
argumentMustBeNamedIfParameterIsOptional(services),
],
SdsArgumentList: [
argumentListMustNotHavePositionalArgumentsAfterNamedArguments,
argumentListMustNotSetParameterMultipleTimes(services),
],
SdsAssignee: [
assigneeAssignedResultShouldNotBeDeprecated(services),
assigneeAssignedResultShouldNotBeExperimental(services),
],
SdsAssignment: [
assignmentAssigneeMustGetValue(services),
assignmentShouldNotImplicitlyIgnoreResult(services),
assignmentShouldHaveMoreThanWildcardsAsAssignees(services),
],
SdsAttribute: [attributeMustHaveTypeHint],
SdsBlockLambda: [blockLambdaMustContainUniqueNames],
SdsCall: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tests.validation.other.expressions.arguments.mustBeNamedIfParameterIsOptional

@Repeatable
annotation MyAnnotation(required: Int, optional: Int = 0)

// $TEST$ no error "Argument must be named if the parameter is optional."
// $TEST$ error "Argument must be named if the parameter is optional."
@MyAnnotation(»1«, »2«)
// $TEST$ no error "Argument must be named if the parameter is optional."
// $TEST$ no error "Argument must be named if the parameter is optional."
@MyAnnotation(»required = 1«, »optional = 2«)

// $TEST$ no error "Argument must be named if the parameter is optional."
@Unresolved(»1«)
// $TEST$ no error "Argument must be named if the parameter is optional."
@MyAnnotation(1, 2, »3«)
// $TEST$ no error "Argument must be named if the parameter is optional."
@MyAnnotation(unresolved = »1«)
pipeline testPipeline {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tests.validation.other.expressions.arguments.mustBeNamedIfParameterIsOptional

@Pure fun myFunction(required: Int, optional: Int = 0)

pipeline testPipeline {
// $TEST$ no error "Argument must be named if the parameter is optional."
// $TEST$ error "Argument must be named if the parameter is optional."
myFunction(»1«, »2«);
// $TEST$ no error "Argument must be named if the parameter is optional."
// $TEST$ no error "Argument must be named if the parameter is optional."
myFunction(»required = 1«, »optional = 2«);

// $TEST$ no error "Argument must be named if the parameter is optional."
unresolved(»1«);
// $TEST$ no error "Argument must be named if the parameter is optional."
myFunction(1, 2, »3«);
// $TEST$ no error "Argument must be named if the parameter is optional."
myFunction(unresolved = »1«);
}

0 comments on commit 297a0b9

Please sign in to comment.