diff --git a/packages/safe-ds-lang/src/language/builtins/safe-ds-annotations.ts b/packages/safe-ds-lang/src/language/builtins/safe-ds-annotations.ts index b219e6d71..255686623 100644 --- a/packages/safe-ds-lang/src/language/builtins/safe-ds-annotations.ts +++ b/packages/safe-ds-lang/src/language/builtins/safe-ds-annotations.ts @@ -85,7 +85,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { return this.getAnnotation(PURITY_URI, 'Impure'); } - callsPure(node: SdsFunction | SdsParameter | undefined): boolean { + callsPure(node: SdsFunction | undefined): boolean { return hasAnnotationCallOf(node, this.Pure); } diff --git a/packages/safe-ds-lang/src/language/validation/purity.ts b/packages/safe-ds-lang/src/language/validation/purity.ts index 4a3d78c14..0bcbd46a3 100644 --- a/packages/safe-ds-lang/src/language/validation/purity.ts +++ b/packages/safe-ds-lang/src/language/validation/purity.ts @@ -1,8 +1,8 @@ import { stream, type ValidationAcceptor } from 'langium'; import { isSubset } from '../../helpers/collectionUtils.js'; -import { isSdsCall, isSdsFunction, isSdsList, type SdsFunction, type SdsParameter } from '../generated/ast.js'; +import { isSdsCall, isSdsFunction, isSdsList, type SdsFunction } from '../generated/ast.js'; import { findFirstAnnotationCallOf, getArguments, getParameters } from '../helpers/nodeProperties.js'; -import { EvaluatedEnumVariant, StringConstant } from '../partialEvaluation/model.js'; +import { StringConstant } from '../partialEvaluation/model.js'; import type { SafeDsServices } from '../safe-ds-module.js'; import { CallableType } from '../typing/model.js'; @@ -12,66 +12,6 @@ export const CODE_PURITY_IMPURITY_REASONS_OF_OVERRIDING_METHOD = 'purity/impurit export const CODE_PURITY_INVALID_PARAMETER_NAME = 'purity/invalid-parameter-name'; export const CODE_PURITY_MUST_BE_SPECIFIED = 'purity/must-be-specified'; export const CODE_PURITY_POTENTIALLY_IMPURE_PARAMETER_NOT_CALLABLE = 'purity/potentially-impure-parameter-not-callable'; -export const CODE_PURITY_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE = 'purity/pure-parameter-must-have-callable-type'; - -export const callableParameterPurityMustBeSpecified = (services: SafeDsServices) => { - const builtinAnnotations = services.builtins.Annotations; - const possibleImpurityReasons = services.builtins.ImpurityReasons; - const typeComputer = services.types.TypeComputer; - - return (node: SdsFunction, accept: ValidationAcceptor) => { - const potentiallyImpureParameterCall = possibleImpurityReasons.PotentiallyImpureParameterCall; - if (!potentiallyImpureParameterCall) { - return; - } - - const parameterNameParameter = getParameters(potentiallyImpureParameterCall).find( - (it) => it.name === 'parameterName', - )!; - const impurityReasons = builtinAnnotations.streamImpurityReasons(node).toArray(); - - for (const parameter of getParameters(node)) { - const parameterType = typeComputer.computeType(parameter); - if (!(parameterType instanceof CallableType)) { - continue; - } - - const expectedImpurityReason = new EvaluatedEnumVariant( - possibleImpurityReasons.PotentiallyImpureParameterCall, - new Map([[parameterNameParameter, new StringConstant(parameter.name)]]), - ); - - if ( - builtinAnnotations.callsPure(parameter) && - impurityReasons.some((it) => it.equals(expectedImpurityReason)) - ) { - accept( - 'error', - "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive.", - { - node: parameter, - property: 'name', - code: CODE_PURITY_IMPURE_AND_PURE, - }, - ); - } else if ( - !builtinAnnotations.callsPure(node) && - !builtinAnnotations.callsPure(parameter) && - !impurityReasons.some((it) => it.equals(expectedImpurityReason)) - ) { - accept( - 'error', - "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function.", - { - node: parameter, - property: 'name', - code: CODE_PURITY_MUST_BE_SPECIFIED, - }, - ); - } - } - }; -}; export const functionPurityMustBeSpecified = (services: SafeDsServices) => { const annotations = services.builtins.Annotations; @@ -275,24 +215,3 @@ export const impurityReasonShouldNotBeSetMultipleTimes = (services: SafeDsServic } }; }; - -export const pureParameterMustHaveCallableType = (services: SafeDsServices) => { - const builtinAnnotations = services.builtins.Annotations; - const typeComputer = services.types.TypeComputer; - - return (node: SdsParameter, accept: ValidationAcceptor) => { - // Don't show an error if no type is specified (yet) or if the parameter is not marked as pure - if (!node.type || !builtinAnnotations.callsPure(node)) { - return; - } - - const type = typeComputer.computeType(node); - if (!(type instanceof CallableType)) { - accept('error', 'A pure parameter must have a callable type.', { - node, - property: 'name', - code: CODE_PURITY_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE, - }); - } - }; -}; diff --git a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts index 967f9152d..7a5da40a1 100644 --- a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts +++ b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts @@ -135,12 +135,10 @@ import { unionTypeShouldNotHaveDuplicateTypes, } from './other/types/unionTypes.js'; import { - callableParameterPurityMustBeSpecified, functionPurityMustBeSpecified, impurityReasonParameterNameMustBelongToParameterOfCorrectType, impurityReasonShouldNotBeSetMultipleTimes, impurityReasonsOfOverridingMethodMustBeSubsetOfOverriddenMethod, - pureParameterMustHaveCallableType, } from './purity.js'; import { annotationCallArgumentListShouldBeNeeded, @@ -157,7 +155,6 @@ import { importedDeclarationAliasShouldDifferFromDeclarationName, memberAccessNullSafetyShouldBeNeeded, namedTypeTypeArgumentListShouldBeNeeded, - pureAnnotationCallOnParameterShouldBeNeeded, segmentResultListShouldNotBeEmpty, typeParameterListShouldNotBeEmpty, unionTypeShouldNotHaveASingularTypeArgument, @@ -256,14 +253,12 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsEnumVariant: [enumVariantMustContainUniqueNames, enumVariantParameterListShouldNotBeEmpty], SdsExpressionLambda: [expressionLambdaMustContainUniqueNames], SdsFunction: [ - callableParameterPurityMustBeSpecified(services), functionMustContainUniqueNames, functionResultListShouldNotBeEmpty, functionPurityMustBeSpecified(services), impurityReasonsOfOverridingMethodMustBeSubsetOfOverriddenMethod(services), impurityReasonParameterNameMustBelongToParameterOfCorrectType(services), impurityReasonShouldNotBeSetMultipleTimes(services), - pureAnnotationCallOnParameterShouldBeNeeded(services), pythonCallMustOnlyContainValidTemplateExpressions(services), pythonNameMustNotBeSetIfPythonCallIsSet(services), ], @@ -320,7 +315,6 @@ export const registerValidationChecks = function (services: SafeDsServices) { constantParameterMustHaveTypeThatCanBeEvaluatedToConstant(services), parameterMustHaveTypeHint, parameterDefaultValueTypeMustMatchParameterType(services), - pureParameterMustHaveCallableType(services), requiredParameterMustNotBeDeprecated(services), requiredParameterMustNotBeExpert(services), ], diff --git a/packages/safe-ds-lang/src/language/validation/style.ts b/packages/safe-ds-lang/src/language/validation/style.ts index 6abce31ce..dbd7a4a14 100644 --- a/packages/safe-ds-lang/src/language/validation/style.ts +++ b/packages/safe-ds-lang/src/language/validation/style.ts @@ -20,10 +20,10 @@ import { SdsTypeParameterList, SdsUnionType, } from '../generated/ast.js'; -import { findFirstAnnotationCallOf, getParameters, getTypeParameters, Parameter } from '../helpers/nodeProperties.js'; +import { getParameters, getTypeParameters, Parameter } from '../helpers/nodeProperties.js'; import { NullConstant } from '../partialEvaluation/model.js'; import { SafeDsServices } from '../safe-ds-module.js'; -import { CallableType, UnknownType } from '../typing/model.js'; +import { UnknownType } from '../typing/model.js'; export const CODE_STYLE_UNNECESSARY_ASSIGNMENT = 'style/unnecessary-assignment'; export const CODE_STYLE_UNNECESSARY_ARGUMENT_LIST = 'style/unnecessary-argument-list'; @@ -33,7 +33,6 @@ export const CODE_STYLE_UNNECESSARY_CONSTRAINT_LIST = 'style/unnecessary-constra export const CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR = 'style/unnecessary-elvis-operator'; export const CODE_STYLE_UNNECESSARY_IMPORT_ALIAS = 'style/unnecessary-import-alias'; export const CODE_STYLE_UNNECESSARY_PARAMETER_LIST = 'style/unnecessary-parameter-list'; -export const CODE_STYLE_UNNECESSARY_PURE_ANNOTATION_CALL = 'style/unnecessary-pure-annotation-call'; export const CODE_STYLE_UNNECESSARY_RESULT_LIST = 'style/unnecessary-result-list'; export const CODE_STYLE_UNNECESSARY_SAFE_ACCESS = 'style/unnecessary-safe-access'; export const CODE_STYLE_UNNECESSARY_TYPE_ARGUMENT_LIST = 'style/unnecessary-type-argument-list'; @@ -252,40 +251,6 @@ export const enumVariantParameterListShouldNotBeEmpty = (node: SdsEnumVariant, a } }; -// ----------------------------------------------------------------------------- -// Unnecessary pure annotation calls -// ----------------------------------------------------------------------------- - -export const pureAnnotationCallOnParameterShouldBeNeeded = (services: SafeDsServices) => { - const builtinAnnotations = services.builtins.Annotations; - const typeComputer = services.types.TypeComputer; - - return (node: SdsFunction, accept: ValidationAcceptor) => { - if (!builtinAnnotations.callsPure(node) || builtinAnnotations.callsImpure(node)) { - return; - } - - for (const parameter of getParameters(node)) { - const parameterType = typeComputer.computeType(parameter); - if (!(parameterType instanceof CallableType)) { - continue; - } - - const pureAnnotationCall = findFirstAnnotationCallOf(parameter, builtinAnnotations.Pure); - if (pureAnnotationCall) { - accept( - 'info', - 'Callable parameters of a pure function are always pure, so this annotation call can be removed.', - { - node: pureAnnotationCall, - code: CODE_STYLE_UNNECESSARY_PURE_ANNOTATION_CALL, - }, - ); - } - } - }; -}; - // ----------------------------------------------------------------------------- // Unnecessary result lists // ----------------------------------------------------------------------------- diff --git a/packages/safe-ds-lang/src/resources/builtins/safeds/lang/purity.sdsstub b/packages/safe-ds-lang/src/resources/builtins/safeds/lang/purity.sdsstub index d4cbfee5a..2c5ab95f0 100644 --- a/packages/safe-ds-lang/src/resources/builtins/safeds/lang/purity.sdsstub +++ b/packages/safe-ds-lang/src/resources/builtins/safeds/lang/purity.sdsstub @@ -1,25 +1,21 @@ package safeds.lang /** - * **If called on a function:** Indicates that the function has no side effects and always returns the same results - * given the same arguments. + * Indicates that the function has no side effects and always returns the same results given the same arguments. * * Calls to such a function may be eliminated, if its results are not used. Moreover, the function can be memoized, i.e. * we can remember its results for a set of arguments. Finally, a pure function can be called at any time, allowing * reordering of calls or parallelization. - * - * **If called on a parameter:** Indicates that the parameter only accepts pure callables. */ @Experimental -@Target([AnnotationTarget.Function, AnnotationTarget.Parameter]) +@Target([AnnotationTarget.Function]) annotation Pure /** * Indicates that the function has side effects and/or does not always return the same results given the same arguments. * * @param allReasons - * A list of **all** reasons why the function is impure. If no specific {@link ImpurityReason} applies, include - * `ImpurityReason.Other`. + * A list of **all** reasons why the function is impure. If no specific {@link ImpurityReason} applies, include `ImpurityReason.Other`. */ @Experimental @Target([AnnotationTarget.Function]) diff --git a/packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/class.sdstest b/packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/class.sdstest deleted file mode 100644 index 27800aee3..000000000 --- a/packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/class.sdstest +++ /dev/null @@ -1,9 +0,0 @@ -package tests.validation.purity.callableParameterWithUnspecifiedPurity - -// $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - -class MyClass( - p1: Int, - @Pure p2: () -> (), - »p3«: () -> () -) diff --git a/packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/function.sdstest b/packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/function.sdstest deleted file mode 100644 index a1fdcb06b..000000000 --- a/packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/function.sdstest +++ /dev/null @@ -1,44 +0,0 @@ -package tests.validation.purity.callableParameterWithUnspecifiedPurity - -fun functionWithUnknownPurity( - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p1«: Int, - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - @Pure »p2«: () -> (), - // $TEST$ error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p3«: () -> () -) - -@Pure -fun pureFunction( - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p1«: Int, - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - @Pure »p2«: () -> (), - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p3«: () -> () -) - -@Impure([]) -fun impureFunctionWithoutReasons( - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p1«: Int, - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - @Pure »p2«: () -> (), - // $TEST$ error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p3«: () -> () -) - -@Impure([ - ImpurityReason.PotentiallyImpureParameterCall("p1"), - ImpurityReason.PotentiallyImpureParameterCall("p2"), - ImpurityReason.PotentiallyImpureParameterCall("p3") -]) -fun impureFunctionWithReasons( - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p1«: Int, - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - @Pure »p2«: () -> (), - // $TEST$ no error "The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function." - »p3«: () -> () -) diff --git a/packages/safe-ds-lang/tests/resources/validation/purity/pure and potentially impure callable parameter/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/purity/pure and potentially impure callable parameter/main.sdstest deleted file mode 100644 index a6bb4e4e1..000000000 --- a/packages/safe-ds-lang/tests/resources/validation/purity/pure and potentially impure callable parameter/main.sdstest +++ /dev/null @@ -1,45 +0,0 @@ -package tests.validation.purity.pureAndPotentiallyImpureCallableParameter - -fun functionWithUnknownPurity( - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p1«: Int, - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p2«: () -> (), - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - »p3«: () -> () -) - - -@Impure([]) -fun impureFunctionWithoutReasons( - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p1«: Int, - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p2«: () -> (), - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - »p3«: () -> () -) - -@Impure([ - ImpurityReason.PotentiallyImpureParameterCall("p1"), - ImpurityReason.PotentiallyImpureParameterCall("p2"), - ImpurityReason.PotentiallyImpureParameterCall("p3") -]) -fun impureFunctionWithReasons( - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p1«: Int, - // $TEST$ error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p2«: () -> (), - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - »p3«: () -> () -) - -@Pure -fun pureFunction( - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p1«: Int, - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - @Pure »p2«: () -> (), - // $TEST$ no error "'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive." - »p3«: () -> () -) diff --git a/packages/safe-ds-lang/tests/resources/validation/purity/pure parameter must have callable type/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/purity/pure parameter must have callable type/main.sdstest deleted file mode 100644 index 556c976ff..000000000 --- a/packages/safe-ds-lang/tests/resources/validation/purity/pure parameter must have callable type/main.sdstest +++ /dev/null @@ -1,14 +0,0 @@ -package tests.validation.purity.pureParameterMustHaveCallableType - -// $TEST$ error "A pure parameter must have a callable type." -// $TEST$ error "A pure parameter must have a callable type." -// $TEST$ no error "A pure parameter must have a callable type." -// $TEST$ no error "A pure parameter must have a callable type." -// $TEST$ no error "A pure parameter must have a callable type." -annotation MyAnnotation( - @Pure »a«: Int, - @Pure »b«: Unresolved, - @Pure »c«: () -> (), - @Pure »d«, - »e«: Int, -) diff --git a/packages/safe-ds-lang/tests/resources/validation/style/unnecessary pure annotation call on pure function parameter/info.sdstest b/packages/safe-ds-lang/tests/resources/validation/style/unnecessary pure annotation call on pure function parameter/info.sdstest deleted file mode 100644 index 17a4ff910..000000000 --- a/packages/safe-ds-lang/tests/resources/validation/style/unnecessary pure annotation call on pure function parameter/info.sdstest +++ /dev/null @@ -1,33 +0,0 @@ -package tests.validation.style.unnecessaryPureAnnotationCallOnPureFunctionParameter - -fun functionWithUnknownPurity( - // $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p1: Int, - // $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p2: () -> (), -) - -@Pure -fun pureFunction( - // $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p1: Int, - // $TEST$ info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p2: () -> (), -) - -@Impure([]) -fun impureFunction( - // $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p1: Int, - // $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p2: () -> (), -) - -@Pure -@Impure([]) -fun pureAndImpureFunction( - // $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p1: Int, - // $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - »@Pure« p2: () -> (), -) diff --git a/packages/safe-ds-lang/tests/resources/validation/style/unnecessary pure annotation call on pure function parameter/no info.sdstest b/packages/safe-ds-lang/tests/resources/validation/style/unnecessary pure annotation call on pure function parameter/no info.sdstest deleted file mode 100644 index 39fb60041..000000000 --- a/packages/safe-ds-lang/tests/resources/validation/style/unnecessary pure annotation call on pure function parameter/no info.sdstest +++ /dev/null @@ -1,20 +0,0 @@ -package tests.validation.style.unnecessaryPureAnnotationCallOnPureFunctionParameter - -// $TEST$ no info "Callable parameters of a pure function are always pure, so this annotation call can be removed." - -fun functionWithUnknownPurity( - p1: Int, - p2: () -> (), -) - -@Pure -fun pureFunction( - p1: Int, - p2: () -> () -) - -@Impure([]) -fun impureFunction( - p1: Int, - p2: () -> () -)