Skip to content

Commit

Permalink
feat: error if lambda is used in wrong context
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann committed Oct 17, 2023
1 parent 097764d commit 12d9826
Show file tree
Hide file tree
Showing 6 changed files with 457 additions and 3 deletions.
31 changes: 30 additions & 1 deletion src/language/validation/other/expressions/lambdas.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
import { SdsLambda } from '../../../generated/ast.js';
import { isSdsArgument, isSdsParameter, isSdsParenthesizedExpression, SdsLambda } from '../../../generated/ast.js';
import { ValidationAcceptor } from 'langium';
import { parametersOrEmpty } from '../../../helpers/nodeProperties.js';
import { SafeDsServices } from '../../../safe-ds-module.js';

export const CODE_LAMBDA_CONTEXT = 'lambda/context';
export const CODE_LAMBDA_CONST_MODIFIER = 'lambda/const-modifier';

export const lambdaMustBeAssignedToTypedParameter = (services: SafeDsServices) => {
const nodeMapper = services.helpers.NodeMapper;

return (node: SdsLambda, accept: ValidationAcceptor): void => {
let context = node.$container;
while (isSdsParenthesizedExpression(context)) {
context = context.$container;
}

let contextIsValid = false;
if (isSdsParameter(context)) {
contextIsValid = context.type !== undefined;
} else if (isSdsArgument(context)) {
const parameter = nodeMapper.argumentToParameterOrUndefined(context);
// If the resolution of the parameter failed, we already show another error nearby
contextIsValid = parameter === undefined || parameter.type !== undefined;
}

if (!contextIsValid) {
accept('error', 'A lambda must be assigned to a typed parameter.', {
node,
code: CODE_LAMBDA_CONTEXT,
});
}
};
};

export const lambdaParameterMustNotHaveConstModifier = (node: SdsLambda, accept: ValidationAcceptor): void => {
for (const parameter of parametersOrEmpty(node)) {
if (parameter.isConstant) {
Expand Down
11 changes: 9 additions & 2 deletions src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ import {
} from './builtins/experimental.js';
import { placeholderShouldBeUsed, placeholdersMustNotBeAnAlias } from './other/declarations/placeholders.js';
import { segmentParameterShouldBeUsed, segmentResultMustBeAssignedExactlyOnce } from './other/declarations/segments.js';
import { lambdaParameterMustNotHaveConstModifier } from './other/expressions/lambdas.js';
import {
lambdaMustBeAssignedToTypedParameter,
lambdaParameterMustNotHaveConstModifier
} from './other/expressions/lambdas.js';
import { indexedAccessesShouldBeUsedWithCaution } from './experimentalLanguageFeatures.js';
import { requiredParameterMustNotBeExpert } from './builtins/expert.js';
import {
Expand Down Expand Up @@ -177,7 +180,11 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsImportedDeclaration: [importedDeclarationAliasShouldDifferFromDeclarationName],
SdsIndexedAccess: [indexedAccessesShouldBeUsedWithCaution],
SdsInfixOperation: [divisionDivisorMustNotBeZero(services), elvisOperatorShouldBeNeeded(services)],
SdsLambda: [lambdaParametersMustNotBeAnnotated, lambdaParameterMustNotHaveConstModifier],
SdsLambda: [
lambdaMustBeAssignedToTypedParameter(services),
lambdaParametersMustNotBeAnnotated,
lambdaParameterMustNotHaveConstModifier
],
SdsMemberAccess: [
memberAccessMustBeNullSafeIfReceiverIsNullable(services),
memberAccessNullSafetyShouldBeNeeded(services),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package tests.validation.other.expressions.lambdas.assignedToTypedParameter

/*
* Lambdas passed as default values
*/

@Repeatable
annotation MyAnnotation(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: () -> () = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: () -> r: Int = »() -> 1«
)

class MyClass(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: () -> () = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: () -> r: Int = ((»() -> 1«))
)

enum MyEnum {
MyEnumVariant(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: () -> () = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: () -> r: Int = ((»() -> 1«))
)
}

fun myFunction(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: () -> () = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: () -> r: Int = ((»() -> 1«))
)

segment mySegment1(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: () -> () = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: () -> r: Int = ((»() -> 1«))
) {}

segment mySegment2(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: (p: () -> () = »() {}«) -> (),
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: (p: () -> (r: Int) = ((»() -> 1«))) -> (),
) {
(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: () -> () = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: () -> r: Int = ((»() -> 1«))
) {};

(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f: () -> () = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g: () -> r: Int = ((»() -> 1«))
) -> 1;
}

/*
* Lambdas passed as arguments
*/

@MyAnnotation(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
)
@MyAnnotation(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g = ((»() -> 1«))
)
segment lambdasPassedAsArguments(
callableType: (p: () -> (), q: () -> (r: Int)) -> (),
) {
val blockLambda = (p: () -> (), q: () -> (r: Int)) {};
val expressionLambda = (p: () -> (), q: () -> (r: Int)) -> 1;

MyAnnotation(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
MyAnnotation(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g = ((»() -> 1«))
);

MyClass(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
MyClass(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g = ((»() -> 1«))
);

MyEnum.MyEnumVariant(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
MyEnum.MyEnumVariant(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g = ((»() -> 1«))
);

myFunction(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
myFunction(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g = ((»() -> 1«))
);

mySegment1(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
mySegment1(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
f = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
g = ((»() -> 1«))
);

callableType(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
callableType(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
p = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
q = ((»() -> 1«))
);

blockLambda(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
blockLambda(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
p = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
q = ((»() -> 1«))
);

expressionLambda(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
»() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
((»() -> 1«))
);
expressionLambda(
// $TEST$ no error "A lambda must be assigned to a typed parameter."
p = »() {}«,
// $TEST$ no error "A lambda must be assigned to a typed parameter."
q = ((»() -> 1«))
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package tests.validation.other.expressions.lambdas.context.assignedToUnresolvedParameter

fun myFunction()

pipeline unresolvedParameter {
// $TEST$ no error "A lambda must be assigned to a typed parameter."
myFunction(»() {}«);
// $TEST$ no error "A lambda must be assigned to a typed parameter."
myFunction(»() -> 1«);

// $TEST$ no error "A lambda must be assigned to a typed parameter."
myFunction(unresolved = »() {}«);
// $TEST$ no error "A lambda must be assigned to a typed parameter."
myFunction(unresolved = »() -> 1«);

// $TEST$ no error "A lambda must be assigned to a typed parameter."
unresolved(»() {}«);
// $TEST$ no error "A lambda must be assigned to a typed parameter."
unresolved(»() -> 1«);
}
Loading

0 comments on commit 12d9826

Please sign in to comment.