Skip to content

Commit

Permalink
feat: error if impure callable is passed to pure parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann committed Nov 22, 2023
1 parent 1b029a2 commit 14a8821
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 24 deletions.
63 changes: 62 additions & 1 deletion packages/safe-ds-lang/src/language/validation/purity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { stream, type ValidationAcceptor } from 'langium';
import { isSubset } from '../../helpers/collectionUtils.js';
import { isSdsCall, isSdsFunction, isSdsList, type SdsFunction } from '../generated/ast.js';
import { isSdsCall, isSdsFunction, isSdsList, SdsCall, type SdsFunction, SdsParameter } from '../generated/ast.js';
import { findFirstAnnotationCallOf, getArguments, getParameters } from '../helpers/nodeProperties.js';
import { StringConstant } from '../partialEvaluation/model.js';
import type { SafeDsServices } from '../safe-ds-module.js';
Expand All @@ -12,6 +12,7 @@ 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_SET_TO_IMPURE_CALLABLE = 'purity/pure-parameter-set-to-impure-callable';

export const functionPurityMustBeSpecified = (services: SafeDsServices) => {
const annotations = services.builtins.Annotations;
Expand Down Expand Up @@ -215,3 +216,63 @@ export const impurityReasonShouldNotBeSetMultipleTimes = (services: SafeDsServic
}
};
};

export const pureParameterDefaultValueMustBePure = (services: SafeDsServices) => {
const purityComputer = services.purity.PurityComputer;
const typeComputer = services.types.TypeComputer;

return (node: SdsParameter, accept: ValidationAcceptor) => {
if (!node.defaultValue) {
return;
}

const parameterType = typeComputer.computeType(node);
if (!(parameterType instanceof CallableType) || !purityComputer.isPureParameter(node)) {
return;
}

const defaultValueType = typeComputer.computeType(node.defaultValue);
if (!(defaultValueType instanceof CallableType)) {
return;
}

if (!purityComputer.isPureCallable(defaultValueType.callable)) {
accept('error', 'Cannot pass an impure callable to a pure parameter.', {
node: node.defaultValue,
code: CODE_PURITY_PURE_PARAMETER_SET_TO_IMPURE_CALLABLE,
});
}
};
};

export const callArgumentAssignedToPureParameterMustBePure = (services: SafeDsServices) => {
const nodeMapper = services.helpers.NodeMapper;
const purityComputer = services.purity.PurityComputer;
const typeComputer = services.types.TypeComputer;

return (node: SdsCall, accept: ValidationAcceptor) => {
for (const argument of getArguments(node)) {
const parameter = nodeMapper.argumentToParameter(argument);
if (!parameter) {
continue;
}

const parameterType = typeComputer.computeType(parameter);
if (!(parameterType instanceof CallableType) || !purityComputer.isPureParameter(parameter)) {
continue;
}

const argumentType = typeComputer.computeType(argument);
if (!(argumentType instanceof CallableType)) {
continue;
}

if (!purityComputer.isPureCallable(argumentType.callable)) {
accept('error', 'Cannot pass an impure callable to a pure parameter.', {
node: argument,
code: CODE_PURITY_PURE_PARAMETER_SET_TO_IMPURE_CALLABLE,
});
}
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,12 @@ import {
unionTypeShouldNotHaveDuplicateTypes,
} from './other/types/unionTypes.js';
import {
callArgumentAssignedToPureParameterMustBePure,
functionPurityMustBeSpecified,
impurityReasonParameterNameMustBelongToParameterOfCorrectType,
impurityReasonShouldNotBeSetMultipleTimes,
impurityReasonsOfOverridingMethodMustBeSubsetOfOverriddenMethod,
pureParameterDefaultValueMustBePure,
} from './purity.js';
import {
annotationCallArgumentListShouldBeNeeded,
Expand Down Expand Up @@ -223,6 +225,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsBlockLambda: [blockLambdaMustContainUniqueNames],
SdsCall: [
callArgumentListShouldBeNeeded(services),
callArgumentAssignedToPureParameterMustBePure(services),
callArgumentMustBeConstantIfParameterIsConstant(services),
callMustNotBeRecursive(services),
callReceiverMustBeCallable(services),
Expand Down Expand Up @@ -317,6 +320,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
constantParameterMustHaveTypeThatCanBeEvaluatedToConstant(services),
parameterMustHaveTypeHint,
parameterDefaultValueTypeMustMatchParameterType(services),
pureParameterDefaultValueMustBePure(services),
requiredParameterMustNotBeDeprecated(services),
requiredParameterMustNotBeExpert(services),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,98 @@ enum MyEnum {
)
}

@Pure
fun myFunction1(
f: () -> (),
other: Int = 1,
)

@Impure([
ImpurityReason.PotentiallyImpureParameterCall("f"),
ImpurityReason.PotentiallyImpureParameterCall("g"),
])
fun myFunction2(
fun myFunction(
f: () -> (),
g: () -> (),
other: Int = 1,
)

segment mySegment1(
f: () -> (),
other: Int = 1,
) {}

segment mySegment2(
myCallableType: (
f: () -> (),
other: Int,
) -> ()
) {
mySegment2(pureFunction);
}

segment mySegment2(
f: () -> (),
other: Int = 1,
) {
f();
}

pipeline myPipeline {
(
val myBlockLambda = (
f: () -> (),
other: Int = 1,
) {};

(
val myExpressionLambda = (
f: () -> (),
other: Int = 1,
) -> 1;

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
MyAnnotation(»pureFunction«, »pureFunction«);
// $TEST$ error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
MyAnnotation(»impureFunction«, »impureFunction«);

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
MyClass(»pureFunction«, »pureFunction«);
// $TEST$ error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
MyClass(»impureFunction«, »impureFunction«);

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
MyEnum.MyEnumVariant(»pureFunction«, »pureFunction«);
// $TEST$ error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
MyEnum.MyEnumVariant(»impureFunction«, »impureFunction«);

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myFunction(»pureFunction«, »pureFunction«, »pureFunction«);
// $TEST$ error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myFunction(»impureFunction«, »impureFunction«, »impureFunction«);

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
mySegment1(»pureFunction«, »pureFunction«);
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
mySegment1(»impureFunction«, »impureFunction«);

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myCallableType(»pureFunction«, »pureFunction«);
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myCallableType(»impureFunction«, »impureFunction«);

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myBlockLambda(»pureFunction«, »pureFunction«);
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myBlockLambda(»impureFunction«, »impureFunction«);

// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myExpressionLambda(»pureFunction«, »pureFunction«);
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
myExpressionLambda(»impureFunction«, »impureFunction«);
}

// Argument does not have callable type
pipeline myPipeline {
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
MyClass(»1«, »1«);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ fun impureFunction()
annotation MyAnnotation(
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
f: () -> () = »pureFunction«,
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
// $TEST$ error "Cannot pass an impure callable to a pure parameter."
g: () -> () = »impureFunction«,
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
other: Int = »impureFunction«,
)

class MyClass(
class MyClass1(
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
f: () -> () = »pureFunction«,
// $TEST$ error "Cannot pass an impure callable to a pure parameter."
Expand Down Expand Up @@ -101,3 +101,9 @@ pipeline myPipeline {
other: Int = »impureFunction«,
) -> 1;
}

// Default value does not have callable type
class MyClass2(
// $TEST$ no error "Cannot pass an impure callable to a pure parameter."
f: () -> () = »1«,
)

0 comments on commit 14a8821

Please sign in to comment.