Skip to content

Commit 9b1522f

Browse files
authored
feat: error if callable type is used in wrong context (#763)
Closes #713 ### Summary of Changes Callables are only supposed to be used as arguments to provide a clean graphical view. Likewise, we now show an error if a callable type is used for anything but a parameter.
1 parent 8cb2120 commit 9b1522f

File tree

7 files changed

+151
-20
lines changed

7 files changed

+151
-20
lines changed

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

+25-18
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import {
44
isSdsMap,
55
isSdsTypeArgumentList,
66
isSdsUnionType,
7-
SdsConstraintList,
8-
SdsIndexedAccess,
9-
SdsLiteralType,
10-
SdsMap,
7+
type SdsConstraintList,
8+
type SdsIndexedAccess,
9+
type SdsLiteralType,
10+
type SdsMap,
1111
type SdsTypeArgumentList,
1212
type SdsTypeParameterList,
13-
SdsUnionType,
13+
type SdsUnionType,
1414
} from '../generated/ast.js';
1515

1616
export const CODE_EXPERIMENTAL_LANGUAGE_FEATURE = 'experimental/language-feature';
@@ -24,6 +24,7 @@ export const constraintListsShouldBeUsedWithCaution = (node: SdsConstraintList,
2424
};
2525

2626
export const indexedAccessesShouldBeUsedWithCaution = (node: SdsIndexedAccess, accept: ValidationAcceptor): void => {
27+
// There's already a warning on the container
2728
if (hasContainerOfType(node.$container, isSdsIndexedAccess)) {
2829
return;
2930
}
@@ -43,6 +44,7 @@ export const literalTypesShouldBeUsedWithCaution = (node: SdsLiteralType, accept
4344
};
4445

4546
export const mapsShouldBeUsedWithCaution = (node: SdsMap, accept: ValidationAcceptor): void => {
47+
// There's already a warning on the container
4648
if (hasContainerOfType(node.$container, isSdsMap)) {
4749
return;
4850
}
@@ -53,23 +55,15 @@ export const mapsShouldBeUsedWithCaution = (node: SdsMap, accept: ValidationAcce
5355
});
5456
};
5557

56-
export const unionTypesShouldBeUsedWithCaution = (node: SdsUnionType, accept: ValidationAcceptor): void => {
57-
if (hasContainerOfType(node.$container, isSdsUnionType)) {
58-
return;
59-
}
60-
61-
accept('warning', 'Union types are experimental and may change without prior notice.', {
62-
node,
63-
keyword: 'union',
64-
code: CODE_EXPERIMENTAL_LANGUAGE_FEATURE,
65-
});
66-
};
67-
6858
export const typeArgumentListsShouldBeUsedWithCaution = (
6959
node: SdsTypeArgumentList,
7060
accept: ValidationAcceptor,
7161
): void => {
72-
if (hasContainerOfType(node.$container, isSdsTypeArgumentList)) {
62+
// There's already a warning on the container
63+
if (
64+
hasContainerOfType(node.$container, isSdsTypeArgumentList) ||
65+
hasContainerOfType(node.$container, isSdsUnionType)
66+
) {
7367
return;
7468
}
7569

@@ -88,3 +82,16 @@ export const typeParameterListsShouldBeUsedWithCaution = (
8882
code: CODE_EXPERIMENTAL_LANGUAGE_FEATURE,
8983
});
9084
};
85+
86+
export const unionTypesShouldBeUsedWithCaution = (node: SdsUnionType, accept: ValidationAcceptor): void => {
87+
// There's already a warning on the container
88+
if (hasContainerOfType(node.$container, isSdsUnionType)) {
89+
return;
90+
}
91+
92+
accept('warning', 'Union types are experimental and may change without prior notice.', {
93+
node,
94+
keyword: 'union',
95+
code: CODE_EXPERIMENTAL_LANGUAGE_FEATURE,
96+
});
97+
};

packages/safe-ds-lang/src/language/validation/other/types/callableTypes.ts

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { ValidationAcceptor } from 'langium';
2-
import { SdsCallableType } from '../../../generated/ast.js';
1+
import { getContainerOfType, ValidationAcceptor } from 'langium';
2+
import { isSdsCallableType, isSdsParameter, SdsCallableType } from '../../../generated/ast.js';
33

44
import { getParameters, Parameter } from '../../../helpers/nodeProperties.js';
55

66
export const CODE_CALLABLE_TYPE_CONST_MODIFIER = 'callable-type/const-modifier';
7+
export const CODE_CALLABLE_TYPE_CONTEXT = 'callable-type/context';
78
export const CODE_CALLABLE_TYPE_NO_OPTIONAL_PARAMETERS = 'callable-type/no-optional-parameters';
89

910
export const callableTypeParameterMustNotHaveConstModifier = (
@@ -21,6 +22,32 @@ export const callableTypeParameterMustNotHaveConstModifier = (
2122
}
2223
};
2324

25+
export const callableTypeMustBeUsedInCorrectContext = (node: SdsCallableType, accept: ValidationAcceptor): void => {
26+
if (!contextIsCorrect(node)) {
27+
accept('error', 'Callable types must only be used for parameters.', {
28+
node,
29+
code: CODE_CALLABLE_TYPE_CONTEXT,
30+
});
31+
}
32+
};
33+
34+
const contextIsCorrect = (node: SdsCallableType): boolean => {
35+
if (isSdsParameter(node.$container)) {
36+
return true;
37+
}
38+
39+
// Check whether we already show an error on a containing callable type
40+
let containingCallableType = getContainerOfType(node.$container, isSdsCallableType);
41+
while (containingCallableType) {
42+
if (!isSdsParameter(containingCallableType.$container)) {
43+
return true; // Container already has wrong context
44+
}
45+
containingCallableType = getContainerOfType(containingCallableType.$container, isSdsCallableType);
46+
}
47+
48+
return false;
49+
};
50+
2451
export const callableTypeMustNotHaveOptionalParameters = (node: SdsCallableType, accept: ValidationAcceptor): void => {
2552
for (const parameter of getParameters(node)) {
2653
if (Parameter.isOptional(parameter)) {

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

+2
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ import {
112112
yieldMustNotBeUsedInPipeline,
113113
} from './other/statements/assignments.js';
114114
import {
115+
callableTypeMustBeUsedInCorrectContext,
115116
callableTypeMustNotHaveOptionalParameters,
116117
callableTypeParameterMustNotHaveConstModifier,
117118
} from './other/types/callableTypes.js';
@@ -217,6 +218,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
217218
callReceiverMustBeCallable(services),
218219
],
219220
SdsCallableType: [
221+
callableTypeMustBeUsedInCorrectContext,
220222
callableTypeMustContainUniqueNames,
221223
callableTypeMustNotHaveOptionalParameters,
222224
callableTypeParametersMustNotBeAnnotated,

packages/safe-ds-lang/tests/resources/validation/experimental language feature/type argument lists/main.sdstest

+3
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ class MyClass1(p: MyClass1»<>«)
66

77
// $TEST$ no warning "Type argument lists & type arguments are experimental and may change without prior notice."
88
class MyClass2<T>(p: MyClass2<MyClass1»<>«>)
9+
10+
// $TEST$ no warning "Type argument lists & type arguments are experimental and may change without prior notice."
11+
class MyClass2<T>(p: union»<>«)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package tests.validation.other.types.callableTypes.context
2+
3+
annotation MyAnnotation(
4+
// $TEST$ no error "Callable types must only be used for parameters."
5+
p: »() -> ()«,
6+
)
7+
8+
class MyClass<T>(
9+
// $TEST$ no error "Callable types must only be used for parameters."
10+
p: »() -> ()«,
11+
) {
12+
// $TEST$ error "Callable types must only be used for parameters."
13+
attr a: »() -> ()«
14+
}
15+
16+
enum MyEnum {
17+
MyEnumVariant<T>(
18+
// $TEST$ no error "Callable types must only be used for parameters."
19+
p: »() -> ()«,
20+
)
21+
}
22+
23+
fun myFunction(
24+
// $TEST$ no error "Callable types must only be used for parameters."
25+
p: »() -> ()«,
26+
) -> (
27+
// $TEST$ error "Callable types must only be used for parameters."
28+
r: »() -> ()«,
29+
)
30+
31+
segment mySegment1(
32+
// $TEST$ no error "Callable types must only be used for parameters."
33+
p: »() -> ()«,
34+
) -> (
35+
// $TEST$ error "Callable types must only be used for parameters."
36+
r: »() -> ()«,
37+
) {}
38+
39+
segment mySegment2(
40+
// $TEST$ no error "Callable types must only be used for parameters."
41+
// $TEST$ error "Callable types must only be used for parameters."
42+
c: (p: »() -> ()«) -> (r: »() -> ()«),
43+
) {
44+
// $TEST$ no error "Callable types must only be used for parameters."
45+
(
46+
p: »() -> ()«,
47+
) {};
48+
49+
// $TEST$ no error "Callable types must only be used for parameters."
50+
(
51+
p: »() -> ()«,
52+
) -> 1;
53+
}
54+
55+
segment mySegment3(
56+
// $TEST$ error "Callable types must only be used for parameters."
57+
p1: MyClass<»() -> ()«>,
58+
59+
// $TEST$ error "Callable types must only be used for parameters."
60+
p2: MyEnum.MyEnumVariant<»() -> ()«>,
61+
) {}
62+
63+
segment mySegment4(
64+
// $TEST$ error "Callable types must only be used for parameters."
65+
p1: »() -> ()«.MyClass
66+
) {}
67+
68+
segment mySegment5(
69+
// $TEST$ error "Callable types must only be used for parameters."
70+
p1: union<»() -> ()«>
71+
) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package tests.validation.other.types.callableTypes.context
2+
3+
/*
4+
* We already show an error for the outer callable type, if it's used in the wrong context.
5+
*/
6+
7+
class MyClass1 {
8+
// $TEST$ no error "Callable types must only be used for parameters."
9+
attr a: () -> (r: »() -> ()«)
10+
}
11+
12+
class MyClass2 {
13+
// $TEST$ no error "Callable types must only be used for parameters."
14+
// $TEST$ no error "Callable types must only be used for parameters."
15+
attr a: () -> (r: (p: »() -> ()«) -> (r: »() -> ()«))
16+
}

packages/safe-ds-lang/tests/resources/validation/other/types/union types/context/main.sdstest

+5
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,8 @@ segment mySegment3(
5959
// $TEST$ error "Union types must only be used for parameters of annotations, classes, and functions."
6060
p2: MyEnum.MyEnumVariant<»union<Int>«>,
6161
) {}
62+
63+
segment mySegment4(
64+
// $TEST$ error "Union types must only be used for parameters of annotations, classes, and functions."
65+
p1: »union<Int>«.MyClass
66+
) {}

0 commit comments

Comments
 (0)