Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: streamline purity information #779

Merged
merged 1 commit into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
return this.getAnnotation(PURITY_URI, 'Impure');
}

callsPure(node: SdsFunction | SdsParameter | undefined): boolean {
callsPure(node: SdsFunction | undefined): boolean {
return hasAnnotationCallOf(node, this.Pure);
}

Expand Down
85 changes: 2 additions & 83 deletions packages/safe-ds-lang/src/language/validation/purity.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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;
Expand Down Expand Up @@ -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,
});
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,10 @@ import {
unionTypeShouldNotHaveDuplicateTypes,
} from './other/types/unionTypes.js';
import {
callableParameterPurityMustBeSpecified,
functionPurityMustBeSpecified,
impurityReasonParameterNameMustBelongToParameterOfCorrectType,
impurityReasonShouldNotBeSetMultipleTimes,
impurityReasonsOfOverridingMethodMustBeSubsetOfOverriddenMethod,
pureParameterMustHaveCallableType,
} from './purity.js';
import {
annotationCallArgumentListShouldBeNeeded,
Expand All @@ -157,7 +155,6 @@ import {
importedDeclarationAliasShouldDifferFromDeclarationName,
memberAccessNullSafetyShouldBeNeeded,
namedTypeTypeArgumentListShouldBeNeeded,
pureAnnotationCallOnParameterShouldBeNeeded,
segmentResultListShouldNotBeEmpty,
typeParameterListShouldNotBeEmpty,
unionTypeShouldNotHaveASingularTypeArgument,
Expand Down Expand Up @@ -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),
],
Expand Down Expand Up @@ -320,7 +315,6 @@ export const registerValidationChecks = function (services: SafeDsServices) {
constantParameterMustHaveTypeThatCanBeEvaluatedToConstant(services),
parameterMustHaveTypeHint,
parameterDefaultValueTypeMustMatchParameterType(services),
pureParameterMustHaveCallableType(services),
requiredParameterMustNotBeDeprecated(services),
requiredParameterMustNotBeExpert(services),
],
Expand Down
39 changes: 2 additions & 37 deletions packages/safe-ds-lang/src/language/validation/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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
// -----------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -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])
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading