Skip to content

Commit 75a9e5b

Browse files
authored
feat: streamline purity information (#779)
### Summary of Changes Remove the option to mark callable parameters as `@Pure`, since it could collide with the impurity reasons on the containing function in various ways. Now only the context decides, whether a callable parameter only accepts pure callables or may be potentially impure.
1 parent c15c70e commit 75a9e5b

File tree

11 files changed

+8
-299
lines changed

11 files changed

+8
-299
lines changed

packages/safe-ds-lang/src/language/builtins/safe-ds-annotations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
8585
return this.getAnnotation(PURITY_URI, 'Impure');
8686
}
8787

88-
callsPure(node: SdsFunction | SdsParameter | undefined): boolean {
88+
callsPure(node: SdsFunction | undefined): boolean {
8989
return hasAnnotationCallOf(node, this.Pure);
9090
}
9191

packages/safe-ds-lang/src/language/validation/purity.ts

+2-83
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { stream, type ValidationAcceptor } from 'langium';
22
import { isSubset } from '../../helpers/collectionUtils.js';
3-
import { isSdsCall, isSdsFunction, isSdsList, type SdsFunction, type SdsParameter } from '../generated/ast.js';
3+
import { isSdsCall, isSdsFunction, isSdsList, type SdsFunction } from '../generated/ast.js';
44
import { findFirstAnnotationCallOf, getArguments, getParameters } from '../helpers/nodeProperties.js';
5-
import { EvaluatedEnumVariant, StringConstant } from '../partialEvaluation/model.js';
5+
import { StringConstant } from '../partialEvaluation/model.js';
66
import type { SafeDsServices } from '../safe-ds-module.js';
77
import { CallableType } from '../typing/model.js';
88

@@ -12,66 +12,6 @@ export const CODE_PURITY_IMPURITY_REASONS_OF_OVERRIDING_METHOD = 'purity/impurit
1212
export const CODE_PURITY_INVALID_PARAMETER_NAME = 'purity/invalid-parameter-name';
1313
export const CODE_PURITY_MUST_BE_SPECIFIED = 'purity/must-be-specified';
1414
export const CODE_PURITY_POTENTIALLY_IMPURE_PARAMETER_NOT_CALLABLE = 'purity/potentially-impure-parameter-not-callable';
15-
export const CODE_PURITY_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE = 'purity/pure-parameter-must-have-callable-type';
16-
17-
export const callableParameterPurityMustBeSpecified = (services: SafeDsServices) => {
18-
const builtinAnnotations = services.builtins.Annotations;
19-
const possibleImpurityReasons = services.builtins.ImpurityReasons;
20-
const typeComputer = services.types.TypeComputer;
21-
22-
return (node: SdsFunction, accept: ValidationAcceptor) => {
23-
const potentiallyImpureParameterCall = possibleImpurityReasons.PotentiallyImpureParameterCall;
24-
if (!potentiallyImpureParameterCall) {
25-
return;
26-
}
27-
28-
const parameterNameParameter = getParameters(potentiallyImpureParameterCall).find(
29-
(it) => it.name === 'parameterName',
30-
)!;
31-
const impurityReasons = builtinAnnotations.streamImpurityReasons(node).toArray();
32-
33-
for (const parameter of getParameters(node)) {
34-
const parameterType = typeComputer.computeType(parameter);
35-
if (!(parameterType instanceof CallableType)) {
36-
continue;
37-
}
38-
39-
const expectedImpurityReason = new EvaluatedEnumVariant(
40-
possibleImpurityReasons.PotentiallyImpureParameterCall,
41-
new Map([[parameterNameParameter, new StringConstant(parameter.name)]]),
42-
);
43-
44-
if (
45-
builtinAnnotations.callsPure(parameter) &&
46-
impurityReasons.some((it) => it.equals(expectedImpurityReason))
47-
) {
48-
accept(
49-
'error',
50-
"'@Pure' and the impurity reason 'PotentiallyImpureParameterCall' on the containing function are mutually exclusive.",
51-
{
52-
node: parameter,
53-
property: 'name',
54-
code: CODE_PURITY_IMPURE_AND_PURE,
55-
},
56-
);
57-
} else if (
58-
!builtinAnnotations.callsPure(node) &&
59-
!builtinAnnotations.callsPure(parameter) &&
60-
!impurityReasons.some((it) => it.equals(expectedImpurityReason))
61-
) {
62-
accept(
63-
'error',
64-
"The purity of a callable parameter must be specified. Call the annotation '@Pure' or add the impurity reason 'PotentiallyImpureParameterCall' to the containing function.",
65-
{
66-
node: parameter,
67-
property: 'name',
68-
code: CODE_PURITY_MUST_BE_SPECIFIED,
69-
},
70-
);
71-
}
72-
}
73-
};
74-
};
7515

7616
export const functionPurityMustBeSpecified = (services: SafeDsServices) => {
7717
const annotations = services.builtins.Annotations;
@@ -275,24 +215,3 @@ export const impurityReasonShouldNotBeSetMultipleTimes = (services: SafeDsServic
275215
}
276216
};
277217
};
278-
279-
export const pureParameterMustHaveCallableType = (services: SafeDsServices) => {
280-
const builtinAnnotations = services.builtins.Annotations;
281-
const typeComputer = services.types.TypeComputer;
282-
283-
return (node: SdsParameter, accept: ValidationAcceptor) => {
284-
// Don't show an error if no type is specified (yet) or if the parameter is not marked as pure
285-
if (!node.type || !builtinAnnotations.callsPure(node)) {
286-
return;
287-
}
288-
289-
const type = typeComputer.computeType(node);
290-
if (!(type instanceof CallableType)) {
291-
accept('error', 'A pure parameter must have a callable type.', {
292-
node,
293-
property: 'name',
294-
code: CODE_PURITY_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE,
295-
});
296-
}
297-
};
298-
};

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

-6
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,10 @@ import {
135135
unionTypeShouldNotHaveDuplicateTypes,
136136
} from './other/types/unionTypes.js';
137137
import {
138-
callableParameterPurityMustBeSpecified,
139138
functionPurityMustBeSpecified,
140139
impurityReasonParameterNameMustBelongToParameterOfCorrectType,
141140
impurityReasonShouldNotBeSetMultipleTimes,
142141
impurityReasonsOfOverridingMethodMustBeSubsetOfOverriddenMethod,
143-
pureParameterMustHaveCallableType,
144142
} from './purity.js';
145143
import {
146144
annotationCallArgumentListShouldBeNeeded,
@@ -157,7 +155,6 @@ import {
157155
importedDeclarationAliasShouldDifferFromDeclarationName,
158156
memberAccessNullSafetyShouldBeNeeded,
159157
namedTypeTypeArgumentListShouldBeNeeded,
160-
pureAnnotationCallOnParameterShouldBeNeeded,
161158
segmentResultListShouldNotBeEmpty,
162159
typeParameterListShouldNotBeEmpty,
163160
unionTypeShouldNotHaveASingularTypeArgument,
@@ -256,14 +253,12 @@ export const registerValidationChecks = function (services: SafeDsServices) {
256253
SdsEnumVariant: [enumVariantMustContainUniqueNames, enumVariantParameterListShouldNotBeEmpty],
257254
SdsExpressionLambda: [expressionLambdaMustContainUniqueNames],
258255
SdsFunction: [
259-
callableParameterPurityMustBeSpecified(services),
260256
functionMustContainUniqueNames,
261257
functionResultListShouldNotBeEmpty,
262258
functionPurityMustBeSpecified(services),
263259
impurityReasonsOfOverridingMethodMustBeSubsetOfOverriddenMethod(services),
264260
impurityReasonParameterNameMustBelongToParameterOfCorrectType(services),
265261
impurityReasonShouldNotBeSetMultipleTimes(services),
266-
pureAnnotationCallOnParameterShouldBeNeeded(services),
267262
pythonCallMustOnlyContainValidTemplateExpressions(services),
268263
pythonNameMustNotBeSetIfPythonCallIsSet(services),
269264
],
@@ -320,7 +315,6 @@ export const registerValidationChecks = function (services: SafeDsServices) {
320315
constantParameterMustHaveTypeThatCanBeEvaluatedToConstant(services),
321316
parameterMustHaveTypeHint,
322317
parameterDefaultValueTypeMustMatchParameterType(services),
323-
pureParameterMustHaveCallableType(services),
324318
requiredParameterMustNotBeDeprecated(services),
325319
requiredParameterMustNotBeExpert(services),
326320
],

packages/safe-ds-lang/src/language/validation/style.ts

+2-37
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import {
2020
SdsTypeParameterList,
2121
SdsUnionType,
2222
} from '../generated/ast.js';
23-
import { findFirstAnnotationCallOf, getParameters, getTypeParameters, Parameter } from '../helpers/nodeProperties.js';
23+
import { getParameters, getTypeParameters, Parameter } from '../helpers/nodeProperties.js';
2424
import { NullConstant } from '../partialEvaluation/model.js';
2525
import { SafeDsServices } from '../safe-ds-module.js';
26-
import { CallableType, UnknownType } from '../typing/model.js';
26+
import { UnknownType } from '../typing/model.js';
2727

2828
export const CODE_STYLE_UNNECESSARY_ASSIGNMENT = 'style/unnecessary-assignment';
2929
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
3333
export const CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR = 'style/unnecessary-elvis-operator';
3434
export const CODE_STYLE_UNNECESSARY_IMPORT_ALIAS = 'style/unnecessary-import-alias';
3535
export const CODE_STYLE_UNNECESSARY_PARAMETER_LIST = 'style/unnecessary-parameter-list';
36-
export const CODE_STYLE_UNNECESSARY_PURE_ANNOTATION_CALL = 'style/unnecessary-pure-annotation-call';
3736
export const CODE_STYLE_UNNECESSARY_RESULT_LIST = 'style/unnecessary-result-list';
3837
export const CODE_STYLE_UNNECESSARY_SAFE_ACCESS = 'style/unnecessary-safe-access';
3938
export const CODE_STYLE_UNNECESSARY_TYPE_ARGUMENT_LIST = 'style/unnecessary-type-argument-list';
@@ -252,40 +251,6 @@ export const enumVariantParameterListShouldNotBeEmpty = (node: SdsEnumVariant, a
252251
}
253252
};
254253

255-
// -----------------------------------------------------------------------------
256-
// Unnecessary pure annotation calls
257-
// -----------------------------------------------------------------------------
258-
259-
export const pureAnnotationCallOnParameterShouldBeNeeded = (services: SafeDsServices) => {
260-
const builtinAnnotations = services.builtins.Annotations;
261-
const typeComputer = services.types.TypeComputer;
262-
263-
return (node: SdsFunction, accept: ValidationAcceptor) => {
264-
if (!builtinAnnotations.callsPure(node) || builtinAnnotations.callsImpure(node)) {
265-
return;
266-
}
267-
268-
for (const parameter of getParameters(node)) {
269-
const parameterType = typeComputer.computeType(parameter);
270-
if (!(parameterType instanceof CallableType)) {
271-
continue;
272-
}
273-
274-
const pureAnnotationCall = findFirstAnnotationCallOf(parameter, builtinAnnotations.Pure);
275-
if (pureAnnotationCall) {
276-
accept(
277-
'info',
278-
'Callable parameters of a pure function are always pure, so this annotation call can be removed.',
279-
{
280-
node: pureAnnotationCall,
281-
code: CODE_STYLE_UNNECESSARY_PURE_ANNOTATION_CALL,
282-
},
283-
);
284-
}
285-
}
286-
};
287-
};
288-
289254
// -----------------------------------------------------------------------------
290255
// Unnecessary result lists
291256
// -----------------------------------------------------------------------------

packages/safe-ds-lang/src/resources/builtins/safeds/lang/purity.sdsstub

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
package safeds.lang
22

33
/**
4-
* **If called on a function:** Indicates that the function has no side effects and always returns the same results
5-
* given the same arguments.
4+
* Indicates that the function has no side effects and always returns the same results given the same arguments.
65
*
76
* Calls to such a function may be eliminated, if its results are not used. Moreover, the function can be memoized, i.e.
87
* we can remember its results for a set of arguments. Finally, a pure function can be called at any time, allowing
98
* reordering of calls or parallelization.
10-
*
11-
* **If called on a parameter:** Indicates that the parameter only accepts pure callables.
129
*/
1310
@Experimental
14-
@Target([AnnotationTarget.Function, AnnotationTarget.Parameter])
11+
@Target([AnnotationTarget.Function])
1512
annotation Pure
1613

1714
/**
1815
* Indicates that the function has side effects and/or does not always return the same results given the same arguments.
1916
*
2017
* @param allReasons
21-
* A list of **all** reasons why the function is impure. If no specific {@link ImpurityReason} applies, include
22-
* `ImpurityReason.Other`.
18+
* A list of **all** reasons why the function is impure. If no specific {@link ImpurityReason} applies, include `ImpurityReason.Other`.
2319
*/
2420
@Experimental
2521
@Target([AnnotationTarget.Function])

packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/class.sdstest

-9
This file was deleted.

packages/safe-ds-lang/tests/resources/validation/purity/callable parameter with unspecified purity/function.sdstest

-44
This file was deleted.

packages/safe-ds-lang/tests/resources/validation/purity/pure and potentially impure callable parameter/main.sdstest

-45
This file was deleted.

packages/safe-ds-lang/tests/resources/validation/purity/pure parameter must have callable type/main.sdstest

-14
This file was deleted.

packages/safe-ds-lang/tests/resources/validation/style/unnecessary pure annotation call on pure function parameter/info.sdstest

-33
This file was deleted.

0 commit comments

Comments
 (0)