Skip to content

Commit 6c52868

Browse files
authored
feat: show an error if a pure parameter does not have a callable type (#736)
Closes #729 ### Summary of Changes Only allow using the `@Pure` annotation on parameters with a callable type.
1 parent 168d098 commit 6c52868

File tree

4 files changed

+157
-105
lines changed

4 files changed

+157
-105
lines changed

packages/safe-ds-lang/src/language/builtins/safe-ds-annotations.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { EMPTY_STREAM, getContainerOfType, Stream, stream, URI } from 'langium';
2+
import { resourceNameToUri } from '../../helpers/resources.js';
13
import {
24
isSdsAnnotation,
35
isSdsEnum,
@@ -15,19 +17,18 @@ import {
1517
getParameters,
1618
hasAnnotationCallOf,
1719
} from '../helpers/nodeProperties.js';
18-
import { SafeDsModuleMembers } from './safe-ds-module-members.js';
19-
import { resourceNameToUri } from '../../helpers/resources.js';
20-
import { EMPTY_STREAM, getContainerOfType, Stream, stream, URI } from 'langium';
21-
import { SafeDsServices } from '../safe-ds-module.js';
2220
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
2321
import { EvaluatedEnumVariant, EvaluatedList, EvaluatedNode, StringConstant } from '../partialEvaluation/model.js';
2422
import { SafeDsPartialEvaluator } from '../partialEvaluation/safe-ds-partial-evaluator.js';
23+
import { SafeDsServices } from '../safe-ds-module.js';
2524
import { SafeDsEnums } from './safe-ds-enums.js';
25+
import { SafeDsModuleMembers } from './safe-ds-module-members.js';
2626

2727
const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub');
2828
const CODE_GENERATION_URI = resourceNameToUri('builtins/safeds/lang/codeGeneration.sdsstub');
2929
const IDE_INTEGRATION_URI = resourceNameToUri('builtins/safeds/lang/ideIntegration.sdsstub');
3030
const MATURITY_URI = resourceNameToUri('builtins/safeds/lang/maturity.sdsstub');
31+
const PURITY_URI = resourceNameToUri('builtins/safeds/lang/purity.sdsstub');
3132

3233
export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
3334
private readonly builtinEnums: SafeDsEnums;
@@ -75,6 +76,14 @@ export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
7576
}
7677
}
7778

79+
isPure(node: SdsFunction | SdsParameter | undefined): boolean {
80+
return hasAnnotationCallOf(node, this.Pure);
81+
}
82+
83+
private get Pure(): SdsAnnotation | undefined {
84+
return this.getAnnotation(PURITY_URI, 'Pure');
85+
}
86+
7887
get PythonCall(): SdsAnnotation | undefined {
7988
return this.getAnnotation(CODE_GENERATION_URI, 'PythonCall');
8089
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { ValidationAcceptor } from 'langium';
2+
import type { SdsParameter } from '../../generated/ast.js';
3+
import type { SafeDsServices } from '../../safe-ds-module.js';
4+
import { CallableType } from '../../typing/model.js';
5+
6+
export const CODE_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE = 'pure/parameter-must-have-callable-type';
7+
8+
export const pureParameterMustHaveCallableType = (services: SafeDsServices) => {
9+
const builtinAnnotations = services.builtins.Annotations;
10+
const typeComputer = services.types.TypeComputer;
11+
12+
return (node: SdsParameter, accept: ValidationAcceptor) => {
13+
// Don't show an error if no type is specified (yet) or if the parameter is not marked as pure
14+
if (!node.type || !builtinAnnotations.isPure(node)) {
15+
return;
16+
}
17+
18+
const type = typeComputer.computeType(node);
19+
if (!(type instanceof CallableType)) {
20+
accept('error', 'A pure parameter must have a callable type.', {
21+
node,
22+
property: 'name',
23+
code: CODE_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE,
24+
});
25+
}
26+
};
27+
};

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

+103-101
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
import { ValidationChecks } from 'langium';
22
import { SafeDsAstType } from '../generated/ast.js';
33
import type { SafeDsServices } from '../safe-ds-module.js';
4+
import {
5+
annotationCallAnnotationShouldNotBeDeprecated,
6+
argumentCorrespondingParameterShouldNotBeDeprecated,
7+
assigneeAssignedResultShouldNotBeDeprecated,
8+
namedTypeDeclarationShouldNotBeDeprecated,
9+
referenceTargetShouldNotBeDeprecated,
10+
requiredParameterMustNotBeDeprecated,
11+
} from './builtins/deprecated.js';
12+
import {
13+
annotationCallAnnotationShouldNotBeExperimental,
14+
argumentCorrespondingParameterShouldNotBeExperimental,
15+
assigneeAssignedResultShouldNotBeExperimental,
16+
namedTypeDeclarationShouldNotBeExperimental,
17+
referenceTargetShouldNotExperimental,
18+
} from './builtins/experimental.js';
19+
import { requiredParameterMustNotBeExpert } from './builtins/expert.js';
20+
import { pureParameterMustHaveCallableType } from './builtins/pure.js';
21+
import { pythonCallMustOnlyContainValidTemplateExpressions } from './builtins/pythonCall.js';
22+
import { pythonModuleShouldDifferFromSafeDsPackage } from './builtins/pythonModule.js';
23+
import {
24+
pythonNameMustNotBeSetIfPythonCallIsSet,
25+
pythonNameShouldDifferFromSafeDsName,
26+
} from './builtins/pythonName.js';
27+
import { singleUseAnnotationsMustNotBeRepeated } from './builtins/repeatable.js';
28+
import { annotationCallMustHaveCorrectTarget, targetShouldNotHaveDuplicateEntries } from './builtins/target.js';
29+
import {
30+
indexedAccessesShouldBeUsedWithCaution,
31+
literalTypesShouldBeUsedWithCaution,
32+
mapsShouldBeUsedWithCaution,
33+
unionTypesShouldBeUsedWithCaution,
34+
} from './experimentalLanguageFeatures.js';
35+
import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './inheritance.js';
436
import {
537
annotationMustContainUniqueNames,
638
blockLambdaMustContainUniqueNames,
@@ -18,6 +50,76 @@ import {
1850
schemaMustContainUniqueNames,
1951
segmentMustContainUniqueNames,
2052
} from './names.js';
53+
import {
54+
argumentListMustNotHavePositionalArgumentsAfterNamedArguments,
55+
argumentListMustNotHaveTooManyArguments,
56+
argumentListMustNotSetParameterMultipleTimes,
57+
argumentListMustSetAllRequiredParameters,
58+
} from './other/argumentLists.js';
59+
import {
60+
annotationCallArgumentsMustBeConstant,
61+
annotationCallMustNotLackArgumentList,
62+
callableTypeParametersMustNotBeAnnotated,
63+
callableTypeResultsMustNotBeAnnotated,
64+
lambdaParametersMustNotBeAnnotated,
65+
} from './other/declarations/annotationCalls.js';
66+
import { parameterListMustNotHaveRequiredParametersAfterOptionalParameters } from './other/declarations/parameterLists.js';
67+
import { constantParameterMustHaveConstantDefaultValue } from './other/declarations/parameters.js';
68+
import { placeholderShouldBeUsed, placeholdersMustNotBeAnAlias } from './other/declarations/placeholders.js';
69+
import {
70+
segmentParameterShouldBeUsed,
71+
segmentResultMustBeAssignedExactlyOnce,
72+
segmentShouldBeUsed,
73+
} from './other/declarations/segments.js';
74+
import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js';
75+
import { typeParameterMustHaveSufficientContext } from './other/declarations/typeParameters.js';
76+
import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expressions/calls.js';
77+
import { divisionDivisorMustNotBeZero } from './other/expressions/infixOperations.js';
78+
import {
79+
lambdaMustBeAssignedToTypedParameter,
80+
lambdaParameterMustNotHaveConstModifier,
81+
} from './other/expressions/lambdas.js';
82+
import {
83+
memberAccessMustBeNullSafeIfReceiverIsNullable,
84+
memberAccessOfEnumVariantMustNotLackInstantiation,
85+
} from './other/expressions/memberAccesses.js';
86+
import {
87+
referenceMustNotBeFunctionPointer,
88+
referenceMustNotBeStaticClassOrEnumReference,
89+
referenceTargetMustNotBeAnnotationPipelineOrSchema,
90+
} from './other/expressions/references.js';
91+
import { templateStringMustHaveExpressionBetweenTwoStringParts } from './other/expressions/templateStrings.js';
92+
import { importPackageMustExist, importPackageShouldNotBeEmpty } from './other/imports.js';
93+
import {
94+
moduleDeclarationsMustMatchFileKind,
95+
moduleWithDeclarationsMustStatePackage,
96+
pipelineFileMustNotBeInBuiltinPackage,
97+
} from './other/modules.js';
98+
import {
99+
assignmentAssigneeMustGetValue,
100+
assignmentShouldNotImplicitlyIgnoreResult,
101+
yieldMustNotBeUsedInPipeline,
102+
} from './other/statements/assignments.js';
103+
import {
104+
callableTypeMustNotHaveOptionalParameters,
105+
callableTypeParameterMustNotHaveConstModifier,
106+
} from './other/types/callableTypes.js';
107+
import {
108+
literalTypeMustHaveLiterals,
109+
literalTypeMustNotContainListLiteral,
110+
literalTypeMustNotContainMapLiteral,
111+
literalTypeShouldNotHaveDuplicateLiteral,
112+
} from './other/types/literalTypes.js';
113+
import {
114+
namedTypeMustNotHaveTooManyTypeArguments,
115+
namedTypeMustNotSetTypeParameterMultipleTimes,
116+
namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments,
117+
} from './other/types/namedTypes.js';
118+
import {
119+
unionTypeMustBeUsedInCorrectContext,
120+
unionTypeMustHaveTypes,
121+
unionTypeShouldNotHaveDuplicateTypes,
122+
} from './other/types/unionTypes.js';
21123
import {
22124
annotationCallArgumentListShouldBeNeeded,
23125
annotationParameterListShouldNotBeEmpty,
@@ -37,12 +139,6 @@ import {
37139
typeParameterListShouldNotBeEmpty,
38140
unionTypeShouldNotHaveASingularTypeArgument,
39141
} from './style.js';
40-
import { templateStringMustHaveExpressionBetweenTwoStringParts } from './other/expressions/templateStrings.js';
41-
import {
42-
assignmentAssigneeMustGetValue,
43-
assignmentShouldNotImplicitlyIgnoreResult,
44-
yieldMustNotBeUsedInPipeline,
45-
} from './other/statements/assignments.js';
46142
import {
47143
argumentTypeMustMatchParameterType,
48144
attributeMustHaveTypeHint,
@@ -57,101 +153,6 @@ import {
57153
resultMustHaveTypeHint,
58154
yieldTypeMustMatchResultType,
59155
} from './types.js';
60-
import {
61-
moduleDeclarationsMustMatchFileKind,
62-
moduleWithDeclarationsMustStatePackage,
63-
pipelineFileMustNotBeInBuiltinPackage,
64-
} from './other/modules.js';
65-
import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js';
66-
import { parameterListMustNotHaveRequiredParametersAfterOptionalParameters } from './other/declarations/parameterLists.js';
67-
import {
68-
unionTypeMustBeUsedInCorrectContext,
69-
unionTypeMustHaveTypes,
70-
unionTypeShouldNotHaveDuplicateTypes,
71-
} from './other/types/unionTypes.js';
72-
import {
73-
callableTypeMustNotHaveOptionalParameters,
74-
callableTypeParameterMustNotHaveConstModifier,
75-
} from './other/types/callableTypes.js';
76-
import {
77-
argumentListMustNotHavePositionalArgumentsAfterNamedArguments,
78-
argumentListMustNotHaveTooManyArguments,
79-
argumentListMustNotSetParameterMultipleTimes,
80-
argumentListMustSetAllRequiredParameters,
81-
} from './other/argumentLists.js';
82-
import {
83-
referenceMustNotBeFunctionPointer,
84-
referenceMustNotBeStaticClassOrEnumReference,
85-
referenceTargetMustNotBeAnnotationPipelineOrSchema,
86-
} from './other/expressions/references.js';
87-
import {
88-
annotationCallAnnotationShouldNotBeDeprecated,
89-
argumentCorrespondingParameterShouldNotBeDeprecated,
90-
assigneeAssignedResultShouldNotBeDeprecated,
91-
namedTypeDeclarationShouldNotBeDeprecated,
92-
referenceTargetShouldNotBeDeprecated,
93-
requiredParameterMustNotBeDeprecated,
94-
} from './builtins/deprecated.js';
95-
import {
96-
annotationCallAnnotationShouldNotBeExperimental,
97-
argumentCorrespondingParameterShouldNotBeExperimental,
98-
assigneeAssignedResultShouldNotBeExperimental,
99-
namedTypeDeclarationShouldNotBeExperimental,
100-
referenceTargetShouldNotExperimental,
101-
} from './builtins/experimental.js';
102-
import { placeholderShouldBeUsed, placeholdersMustNotBeAnAlias } from './other/declarations/placeholders.js';
103-
import {
104-
segmentParameterShouldBeUsed,
105-
segmentResultMustBeAssignedExactlyOnce,
106-
segmentShouldBeUsed,
107-
} from './other/declarations/segments.js';
108-
import {
109-
lambdaMustBeAssignedToTypedParameter,
110-
lambdaParameterMustNotHaveConstModifier,
111-
} from './other/expressions/lambdas.js';
112-
import {
113-
indexedAccessesShouldBeUsedWithCaution,
114-
literalTypesShouldBeUsedWithCaution,
115-
mapsShouldBeUsedWithCaution,
116-
unionTypesShouldBeUsedWithCaution,
117-
} from './experimentalLanguageFeatures.js';
118-
import { requiredParameterMustNotBeExpert } from './builtins/expert.js';
119-
import {
120-
annotationCallArgumentsMustBeConstant,
121-
annotationCallMustNotLackArgumentList,
122-
callableTypeParametersMustNotBeAnnotated,
123-
callableTypeResultsMustNotBeAnnotated,
124-
lambdaParametersMustNotBeAnnotated,
125-
} from './other/declarations/annotationCalls.js';
126-
import {
127-
memberAccessMustBeNullSafeIfReceiverIsNullable,
128-
memberAccessOfEnumVariantMustNotLackInstantiation,
129-
} from './other/expressions/memberAccesses.js';
130-
import { importPackageMustExist, importPackageShouldNotBeEmpty } from './other/imports.js';
131-
import { singleUseAnnotationsMustNotBeRepeated } from './builtins/repeatable.js';
132-
import {
133-
namedTypeMustNotHaveTooManyTypeArguments,
134-
namedTypeMustNotSetTypeParameterMultipleTimes,
135-
namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments,
136-
} from './other/types/namedTypes.js';
137-
import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './inheritance.js';
138-
import {
139-
pythonNameMustNotBeSetIfPythonCallIsSet,
140-
pythonNameShouldDifferFromSafeDsName,
141-
} from './builtins/pythonName.js';
142-
import { pythonModuleShouldDifferFromSafeDsPackage } from './builtins/pythonModule.js';
143-
import { divisionDivisorMustNotBeZero } from './other/expressions/infixOperations.js';
144-
import { constantParameterMustHaveConstantDefaultValue } from './other/declarations/parameters.js';
145-
import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expressions/calls.js';
146-
import {
147-
literalTypeMustHaveLiterals,
148-
literalTypeMustNotContainListLiteral,
149-
literalTypeMustNotContainMapLiteral,
150-
literalTypeShouldNotHaveDuplicateLiteral,
151-
} from './other/types/literalTypes.js';
152-
import { annotationCallMustHaveCorrectTarget, targetShouldNotHaveDuplicateEntries } from './builtins/target.js';
153-
import { pythonCallMustOnlyContainValidTemplateExpressions } from './builtins/pythonCall.js';
154-
import { typeParameterMustHaveSufficientContext } from './other/declarations/typeParameters.js';
155156

156157
/**
157158
* Register custom validation checks.
@@ -283,6 +284,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
283284
constantParameterMustHaveConstantDefaultValue(services),
284285
parameterMustHaveTypeHint,
285286
parameterDefaultValueTypeMustMatchParameterType(services),
287+
pureParameterMustHaveCallableType(services),
286288
requiredParameterMustNotBeDeprecated(services),
287289
requiredParameterMustNotBeExpert(services),
288290
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package tests.validation.builtins.pure.pureParameterMustHaveCallableType
2+
3+
// $TEST$ error "A pure parameter must have a callable type."
4+
// $TEST$ error "A pure parameter must have a callable type."
5+
// $TEST$ no error "A pure parameter must have a callable type."
6+
// $TEST$ no error "A pure parameter must have a callable type."
7+
// $TEST$ no error "A pure parameter must have a callable type."
8+
annotation MyAnnotation(
9+
@Pure »a«: Int,
10+
@Pure »b«: Unresolved,
11+
@Pure »c«: () -> (),
12+
@Pure »d«,
13+
»e«: Int,
14+
)

0 commit comments

Comments
 (0)