Skip to content

Commit 2d2ccc6

Browse files
feat: error if lambda is used in wrong context (#647)
Closes #409 Closes partially #543 ### Summary of Changes Show an error if a lambda is used in the wrong context. They must be assigned to a type parameter, either as its default value or an argument. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
1 parent 097764d commit 2d2ccc6

File tree

10 files changed

+464
-10
lines changed

10 files changed

+464
-10
lines changed

src/language/grammar/safe-ds.langium

+2-2
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ interface SdsIndexedAccess extends SdsChainedExpression {
684684

685685
interface SdsMemberAccess extends SdsChainedExpression {
686686
isNullSafe: boolean
687-
member: SdsReference
687+
member?: SdsReference
688688
}
689689

690690
SdsChainedExpression returns SdsExpression:
@@ -874,8 +874,8 @@ interface SdsType extends SdsObject {}
874874
interface SdsNamedTypeDeclaration extends SdsDeclaration {}
875875

876876
interface SdsMemberType extends SdsType {
877-
member: SdsNamedType
878877
receiver: SdsType
878+
member?: SdsNamedType
879879
}
880880

881881
SdsType returns SdsType:

src/language/scoping/safe-ds-scope-provider.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
167167
if (isSdsNamedType(node)) {
168168
return node.declaration.ref;
169169
} else if (isSdsMemberType(node)) {
170-
return node.member.declaration.ref;
170+
return node.member?.declaration?.ref;
171171
} else {
172172
return undefined;
173173
}
@@ -233,7 +233,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
233233
if (isSdsReference(node)) {
234234
return node.target.ref;
235235
} else if (isSdsMemberAccess(node)) {
236-
return node.member.target.ref;
236+
return node.member?.target?.ref;
237237
} else {
238238
return undefined;
239239
}

src/language/validation/other/expressions/lambdas.ts

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
1-
import { SdsLambda } from '../../../generated/ast.js';
1+
import { isSdsArgument, isSdsParameter, isSdsParenthesizedExpression, SdsLambda } from '../../../generated/ast.js';
22
import { ValidationAcceptor } from 'langium';
33
import { parametersOrEmpty } from '../../../helpers/nodeProperties.js';
4+
import { SafeDsServices } from '../../../safe-ds-module.js';
45

6+
export const CODE_LAMBDA_CONTEXT = 'lambda/context';
57
export const CODE_LAMBDA_CONST_MODIFIER = 'lambda/const-modifier';
68

9+
export const lambdaMustBeAssignedToTypedParameter = (services: SafeDsServices) => {
10+
const nodeMapper = services.helpers.NodeMapper;
11+
12+
return (node: SdsLambda, accept: ValidationAcceptor): void => {
13+
let context = node.$container;
14+
while (isSdsParenthesizedExpression(context)) {
15+
context = context.$container;
16+
}
17+
18+
let contextIsValid = false;
19+
if (isSdsParameter(context)) {
20+
contextIsValid = context.type !== undefined;
21+
} else if (isSdsArgument(context)) {
22+
const parameter = nodeMapper.argumentToParameterOrUndefined(context);
23+
// If the resolution of the parameter failed, we already show another error nearby
24+
contextIsValid = parameter === undefined || parameter.type !== undefined;
25+
}
26+
27+
if (!contextIsValid) {
28+
accept('error', 'A lambda must be assigned to a typed parameter.', {
29+
node,
30+
code: CODE_LAMBDA_CONTEXT,
31+
});
32+
}
33+
};
34+
};
35+
736
export const lambdaParameterMustNotHaveConstModifier = (node: SdsLambda, accept: ValidationAcceptor): void => {
837
for (const parameter of parametersOrEmpty(node)) {
938
if (parameter.isConstant) {

src/language/validation/other/expressions/memberAccesses.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const memberAccessOfEnumVariantMustNotLackInstantiation = (
1212
node: SdsMemberAccess,
1313
accept: ValidationAcceptor,
1414
): void => {
15-
const declaration = node.member.target.ref;
15+
const declaration = node.member?.target?.ref;
1616
if (!isSdsEnumVariant(declaration)) {
1717
return;
1818
}

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ import {
8181
} from './builtins/experimental.js';
8282
import { placeholderShouldBeUsed, placeholdersMustNotBeAnAlias } from './other/declarations/placeholders.js';
8383
import { segmentParameterShouldBeUsed, segmentResultMustBeAssignedExactlyOnce } from './other/declarations/segments.js';
84-
import { lambdaParameterMustNotHaveConstModifier } from './other/expressions/lambdas.js';
84+
import {
85+
lambdaMustBeAssignedToTypedParameter,
86+
lambdaParameterMustNotHaveConstModifier,
87+
} from './other/expressions/lambdas.js';
8588
import { indexedAccessesShouldBeUsedWithCaution } from './experimentalLanguageFeatures.js';
8689
import { requiredParameterMustNotBeExpert } from './builtins/expert.js';
8790
import {
@@ -177,7 +180,11 @@ export const registerValidationChecks = function (services: SafeDsServices) {
177180
SdsImportedDeclaration: [importedDeclarationAliasShouldDifferFromDeclarationName],
178181
SdsIndexedAccess: [indexedAccessesShouldBeUsedWithCaution],
179182
SdsInfixOperation: [divisionDivisorMustNotBeZero(services), elvisOperatorShouldBeNeeded(services)],
180-
SdsLambda: [lambdaParametersMustNotBeAnnotated, lambdaParameterMustNotHaveConstModifier],
183+
SdsLambda: [
184+
lambdaMustBeAssignedToTypedParameter(services),
185+
lambdaParametersMustNotBeAnnotated,
186+
lambdaParameterMustNotHaveConstModifier,
187+
],
181188
SdsMemberAccess: [
182189
memberAccessMustBeNullSafeIfReceiverIsNullable(services),
183190
memberAccessNullSafetyShouldBeNeeded(services),

src/language/validation/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getContainerOfType, ValidationAcceptor } from 'langium';
1+
import { AstNode, getContainerOfType, ValidationAcceptor } from 'langium';
22
import {
33
isSdsAnnotation,
44
isSdsCallable,
@@ -31,7 +31,7 @@ export const callReceiverMustBeCallable = (services: SafeDsServices) => {
3131
const nodeMapper = services.helpers.NodeMapper;
3232

3333
return (node: SdsCall, accept: ValidationAcceptor): void => {
34-
let receiver = node.receiver;
34+
let receiver: AstNode | undefined = node.receiver;
3535
if (isSdsMemberAccess(receiver)) {
3636
receiver = receiver.member;
3737
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package tests.validation.other.expressions.lambdas.assignedToTypedParameter
2+
3+
/*
4+
* Lambdas passed as default values
5+
*/
6+
7+
@Repeatable
8+
annotation MyAnnotation(
9+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
10+
f: () -> () = »() {}«,
11+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
12+
g: () -> r: Int = »() -> 1«
13+
)
14+
15+
class MyClass(
16+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
17+
f: () -> () = »() {}«,
18+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
19+
g: () -> r: Int = ((»() -> 1«))
20+
)
21+
22+
enum MyEnum {
23+
MyEnumVariant(
24+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
25+
f: () -> () = »() {}«,
26+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
27+
g: () -> r: Int = ((»() -> 1«))
28+
)
29+
}
30+
31+
fun myFunction(
32+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
33+
f: () -> () = »() {}«,
34+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
35+
g: () -> r: Int = ((»() -> 1«))
36+
)
37+
38+
segment mySegment1(
39+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
40+
f: () -> () = »() {}«,
41+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
42+
g: () -> r: Int = ((»() -> 1«))
43+
) {}
44+
45+
segment mySegment2(
46+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
47+
f: (p: () -> () = »() {}«) -> (),
48+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
49+
g: (p: () -> (r: Int) = ((»() -> 1«))) -> (),
50+
) {
51+
(
52+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
53+
f: () -> () = »() {}«,
54+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
55+
g: () -> r: Int = ((»() -> 1«))
56+
) {};
57+
58+
(
59+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
60+
f: () -> () = »() {}«,
61+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
62+
g: () -> r: Int = ((»() -> 1«))
63+
) -> 1;
64+
}
65+
66+
/*
67+
* Lambdas passed as arguments
68+
*/
69+
70+
@MyAnnotation(
71+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
72+
»() {}«,
73+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
74+
((»() -> 1«))
75+
)
76+
@MyAnnotation(
77+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
78+
f = »() {}«,
79+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
80+
g = ((»() -> 1«))
81+
)
82+
segment lambdasPassedAsArguments(
83+
callableType: (p: () -> (), q: () -> (r: Int)) -> (),
84+
) {
85+
val blockLambda = (p: () -> (), q: () -> (r: Int)) {};
86+
val expressionLambda = (p: () -> (), q: () -> (r: Int)) -> 1;
87+
88+
MyAnnotation(
89+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
90+
»() {}«,
91+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
92+
((»() -> 1«))
93+
);
94+
MyAnnotation(
95+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
96+
f = »() {}«,
97+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
98+
g = ((»() -> 1«))
99+
);
100+
101+
MyClass(
102+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
103+
»() {}«,
104+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
105+
((»() -> 1«))
106+
);
107+
MyClass(
108+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
109+
f = »() {}«,
110+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
111+
g = ((»() -> 1«))
112+
);
113+
114+
MyEnum.MyEnumVariant(
115+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
116+
»() {}«,
117+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
118+
((»() -> 1«))
119+
);
120+
MyEnum.MyEnumVariant(
121+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
122+
f = »() {}«,
123+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
124+
g = ((»() -> 1«))
125+
);
126+
127+
myFunction(
128+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
129+
»() {}«,
130+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
131+
((»() -> 1«))
132+
);
133+
myFunction(
134+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
135+
f = »() {}«,
136+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
137+
g = ((»() -> 1«))
138+
);
139+
140+
mySegment1(
141+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
142+
»() {}«,
143+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
144+
((»() -> 1«))
145+
);
146+
mySegment1(
147+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
148+
f = »() {}«,
149+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
150+
g = ((»() -> 1«))
151+
);
152+
153+
callableType(
154+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
155+
»() {}«,
156+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
157+
((»() -> 1«))
158+
);
159+
callableType(
160+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
161+
p = »() {}«,
162+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
163+
q = ((»() -> 1«))
164+
);
165+
166+
blockLambda(
167+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
168+
»() {}«,
169+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
170+
((»() -> 1«))
171+
);
172+
blockLambda(
173+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
174+
p = »() {}«,
175+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
176+
q = ((»() -> 1«))
177+
);
178+
179+
expressionLambda(
180+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
181+
»() {}«,
182+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
183+
((»() -> 1«))
184+
);
185+
expressionLambda(
186+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
187+
p = »() {}«,
188+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
189+
q = ((»() -> 1«))
190+
);
191+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package tests.validation.other.expressions.lambdas.context.assignedToUnresolvedParameter
2+
3+
fun myFunction()
4+
5+
pipeline unresolvedParameter {
6+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
7+
myFunction(»() {}«);
8+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
9+
myFunction(»() -> 1«);
10+
11+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
12+
myFunction(unresolved = »() {}«);
13+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
14+
myFunction(unresolved = »() -> 1«);
15+
16+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
17+
unresolved(»() {}«);
18+
// $TEST$ no error "A lambda must be assigned to a typed parameter."
19+
unresolved(»() -> 1«);
20+
}

0 commit comments

Comments
 (0)