diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index e7e6d1a24..aeabecd95 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -31,6 +31,14 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Expert'); } + isRepeatable(node: SdsAnnotation | undefined): boolean { + return this.hasAnnotationCallOf(node, this.Repeatable); + } + + private get Repeatable(): SdsAnnotation | undefined { + return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Repeatable'); + } + private hasAnnotationCallOf(node: SdsAnnotatedObject | undefined, expected: SdsAnnotation | undefined): boolean { return annotationCallsOrEmpty(node).some((it) => { const actual = it.annotation?.ref; diff --git a/src/language/validation/builtins/repeatable.ts b/src/language/validation/builtins/repeatable.ts new file mode 100644 index 000000000..a4cf518bf --- /dev/null +++ b/src/language/validation/builtins/repeatable.ts @@ -0,0 +1,22 @@ +import { ValidationAcceptor } from 'langium'; +import { SdsAnnotatedObject } from '../../generated/ast.js'; +import { SafeDsServices } from '../../safe-ds-module.js'; +import { annotationCallsOrEmpty } from '../../helpers/nodeProperties.js'; +import { duplicatesBy } from '../../helpers/collectionUtils.js'; + +export const CODE_ANNOTATION_NOT_REPEATABLE = 'annotation/not-repeatable'; + +export const singleUseAnnotationsMustNotBeRepeated = + (services: SafeDsServices) => (node: SdsAnnotatedObject, accept: ValidationAcceptor) => { + const callsOfSingleUseAnnotations = annotationCallsOrEmpty(node).filter((it) => { + const annotation = it.annotation?.ref; + return annotation && !services.builtins.Annotations.isRepeatable(annotation); + }); + + for (const duplicate of duplicatesBy(callsOfSingleUseAnnotations, (it) => it.annotation?.ref)) { + accept('error', `The annotation '${duplicate.annotation.$refText}' is not repeatable.`, { + node: duplicate, + code: CODE_ANNOTATION_NOT_REPEATABLE, + }); + } + }; diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index b4d527410..2cdf0ad7a 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -76,6 +76,7 @@ import { } from './other/declarations/annotationCalls.js'; import { memberAccessMustBeNullSafeIfReceiverIsNullable } from './other/expressions/memberAccesses.js'; import { importPackageMustExist, importPackageShouldNotBeEmpty } from './other/imports.js'; +import { singleUseAnnotationsMustNotBeRepeated } from './builtins/repeatable.js'; /** * Register custom validation checks. @@ -98,6 +99,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { annotationCallAnnotationShouldNotBeExperimental(services), annotationCallArgumentListShouldBeNeeded, ], + SdsAnnotatedObject: [singleUseAnnotationsMustNotBeRepeated(services)], SdsArgument: [ argumentCorrespondingParameterShouldNotBeDeprecated(services), argumentCorrespondingParameterShouldNotBeExperimental(services), diff --git a/tests/resources/validation/builtins/annotations/repeatable/main.sdstest b/tests/resources/validation/builtins/annotations/repeatable/main.sdstest new file mode 100644 index 000000000..eb642f8e8 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/repeatable/main.sdstest @@ -0,0 +1,24 @@ +package tests.validation.builtins.annotations.repeatable + +annotation SingleUse + +@Repeatable +annotation MultiUse + +// $TEST$ no error r"The annotation '\w*' is not repeatable\." +»@SingleUse« +// $TEST$ no error r"The annotation '\w*' is not repeatable\." +»@MultiUse« +// $TEST$ no error r"The annotation '\w*' is not repeatable\." +»@MultiUse« +// $TEST$ no error r"The annotation '\w*' is not repeatable\." +»@UnresolvedAnnotation« +// $TEST$ no error r"The annotation '\w*' is not repeatable\." +»@UnresolvedAnnotation« +class CorrectUse + +// $TEST$ no error r"The annotation '\w*' is not repeatable\." +»@SingleUse« +// $TEST$ error "The annotation 'SingleUse' is not repeatable." +»@SingleUse« +class IncorrectUse diff --git a/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest b/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest index dcd48f85f..3cee2dd40 100644 --- a/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest +++ b/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest @@ -58,6 +58,7 @@ segment mySegment() -> ( // $TEST$ error "No value is assigned to this assignee." »val k«, »val l« = unresolved(); + // $TEST$ error "No value is assigned to this assignee." »yield r1« = noResults(); // $TEST$ no error "No value is assigned to this assignee."