Skip to content

Commit

Permalink
feat: show an error if a pure parameter does not have a callable type (
Browse files Browse the repository at this point in the history
…#736)

Closes #729

### Summary of Changes

Only allow using the `@Pure` annotation on parameters with a callable
type.
  • Loading branch information
lars-reimann authored Nov 7, 2023
1 parent 168d098 commit 6c52868
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 105 deletions.
17 changes: 13 additions & 4 deletions packages/safe-ds-lang/src/language/builtins/safe-ds-annotations.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { EMPTY_STREAM, getContainerOfType, Stream, stream, URI } from 'langium';
import { resourceNameToUri } from '../../helpers/resources.js';
import {
isSdsAnnotation,
isSdsEnum,
Expand All @@ -15,19 +17,18 @@ import {
getParameters,
hasAnnotationCallOf,
} from '../helpers/nodeProperties.js';
import { SafeDsModuleMembers } from './safe-ds-module-members.js';
import { resourceNameToUri } from '../../helpers/resources.js';
import { EMPTY_STREAM, getContainerOfType, Stream, stream, URI } from 'langium';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import { EvaluatedEnumVariant, EvaluatedList, EvaluatedNode, StringConstant } from '../partialEvaluation/model.js';
import { SafeDsPartialEvaluator } from '../partialEvaluation/safe-ds-partial-evaluator.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsEnums } from './safe-ds-enums.js';
import { SafeDsModuleMembers } from './safe-ds-module-members.js';

const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub');
const CODE_GENERATION_URI = resourceNameToUri('builtins/safeds/lang/codeGeneration.sdsstub');
const IDE_INTEGRATION_URI = resourceNameToUri('builtins/safeds/lang/ideIntegration.sdsstub');
const MATURITY_URI = resourceNameToUri('builtins/safeds/lang/maturity.sdsstub');
const PURITY_URI = resourceNameToUri('builtins/safeds/lang/purity.sdsstub');

export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
private readonly builtinEnums: SafeDsEnums;
Expand Down Expand Up @@ -75,6 +76,14 @@ export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
}
}

isPure(node: SdsFunction | SdsParameter | undefined): boolean {
return hasAnnotationCallOf(node, this.Pure);
}

private get Pure(): SdsAnnotation | undefined {
return this.getAnnotation(PURITY_URI, 'Pure');
}

get PythonCall(): SdsAnnotation | undefined {
return this.getAnnotation(CODE_GENERATION_URI, 'PythonCall');
}
Expand Down
27 changes: 27 additions & 0 deletions packages/safe-ds-lang/src/language/validation/builtins/pure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ValidationAcceptor } from 'langium';
import type { SdsParameter } from '../../generated/ast.js';
import type { SafeDsServices } from '../../safe-ds-module.js';
import { CallableType } from '../../typing/model.js';

export const CODE_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE = 'pure/parameter-must-have-callable-type';

export const pureParameterMustHaveCallableType = (services: SafeDsServices) => {
const builtinAnnotations = services.builtins.Annotations;
const typeComputer = services.types.TypeComputer;

return (node: SdsParameter, accept: ValidationAcceptor) => {
// Don't show an error if no type is specified (yet) or if the parameter is not marked as pure
if (!node.type || !builtinAnnotations.isPure(node)) {
return;
}

const type = typeComputer.computeType(node);
if (!(type instanceof CallableType)) {
accept('error', 'A pure parameter must have a callable type.', {
node,
property: 'name',
code: CODE_PURE_PARAMETER_MUST_HAVE_CALLABLE_TYPE,
});
}
};
};
204 changes: 103 additions & 101 deletions packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
import { ValidationChecks } from 'langium';
import { SafeDsAstType } from '../generated/ast.js';
import type { SafeDsServices } from '../safe-ds-module.js';
import {
annotationCallAnnotationShouldNotBeDeprecated,
argumentCorrespondingParameterShouldNotBeDeprecated,
assigneeAssignedResultShouldNotBeDeprecated,
namedTypeDeclarationShouldNotBeDeprecated,
referenceTargetShouldNotBeDeprecated,
requiredParameterMustNotBeDeprecated,
} from './builtins/deprecated.js';
import {
annotationCallAnnotationShouldNotBeExperimental,
argumentCorrespondingParameterShouldNotBeExperimental,
assigneeAssignedResultShouldNotBeExperimental,
namedTypeDeclarationShouldNotBeExperimental,
referenceTargetShouldNotExperimental,
} from './builtins/experimental.js';
import { requiredParameterMustNotBeExpert } from './builtins/expert.js';
import { pureParameterMustHaveCallableType } from './builtins/pure.js';
import { pythonCallMustOnlyContainValidTemplateExpressions } from './builtins/pythonCall.js';
import { pythonModuleShouldDifferFromSafeDsPackage } from './builtins/pythonModule.js';
import {
pythonNameMustNotBeSetIfPythonCallIsSet,
pythonNameShouldDifferFromSafeDsName,
} from './builtins/pythonName.js';
import { singleUseAnnotationsMustNotBeRepeated } from './builtins/repeatable.js';
import { annotationCallMustHaveCorrectTarget, targetShouldNotHaveDuplicateEntries } from './builtins/target.js';
import {
indexedAccessesShouldBeUsedWithCaution,
literalTypesShouldBeUsedWithCaution,
mapsShouldBeUsedWithCaution,
unionTypesShouldBeUsedWithCaution,
} from './experimentalLanguageFeatures.js';
import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './inheritance.js';
import {
annotationMustContainUniqueNames,
blockLambdaMustContainUniqueNames,
Expand All @@ -18,6 +50,76 @@ import {
schemaMustContainUniqueNames,
segmentMustContainUniqueNames,
} from './names.js';
import {
argumentListMustNotHavePositionalArgumentsAfterNamedArguments,
argumentListMustNotHaveTooManyArguments,
argumentListMustNotSetParameterMultipleTimes,
argumentListMustSetAllRequiredParameters,
} from './other/argumentLists.js';
import {
annotationCallArgumentsMustBeConstant,
annotationCallMustNotLackArgumentList,
callableTypeParametersMustNotBeAnnotated,
callableTypeResultsMustNotBeAnnotated,
lambdaParametersMustNotBeAnnotated,
} from './other/declarations/annotationCalls.js';
import { parameterListMustNotHaveRequiredParametersAfterOptionalParameters } from './other/declarations/parameterLists.js';
import { constantParameterMustHaveConstantDefaultValue } from './other/declarations/parameters.js';
import { placeholderShouldBeUsed, placeholdersMustNotBeAnAlias } from './other/declarations/placeholders.js';
import {
segmentParameterShouldBeUsed,
segmentResultMustBeAssignedExactlyOnce,
segmentShouldBeUsed,
} from './other/declarations/segments.js';
import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js';
import { typeParameterMustHaveSufficientContext } from './other/declarations/typeParameters.js';
import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expressions/calls.js';
import { divisionDivisorMustNotBeZero } from './other/expressions/infixOperations.js';
import {
lambdaMustBeAssignedToTypedParameter,
lambdaParameterMustNotHaveConstModifier,
} from './other/expressions/lambdas.js';
import {
memberAccessMustBeNullSafeIfReceiverIsNullable,
memberAccessOfEnumVariantMustNotLackInstantiation,
} from './other/expressions/memberAccesses.js';
import {
referenceMustNotBeFunctionPointer,
referenceMustNotBeStaticClassOrEnumReference,
referenceTargetMustNotBeAnnotationPipelineOrSchema,
} from './other/expressions/references.js';
import { templateStringMustHaveExpressionBetweenTwoStringParts } from './other/expressions/templateStrings.js';
import { importPackageMustExist, importPackageShouldNotBeEmpty } from './other/imports.js';
import {
moduleDeclarationsMustMatchFileKind,
moduleWithDeclarationsMustStatePackage,
pipelineFileMustNotBeInBuiltinPackage,
} from './other/modules.js';
import {
assignmentAssigneeMustGetValue,
assignmentShouldNotImplicitlyIgnoreResult,
yieldMustNotBeUsedInPipeline,
} from './other/statements/assignments.js';
import {
callableTypeMustNotHaveOptionalParameters,
callableTypeParameterMustNotHaveConstModifier,
} from './other/types/callableTypes.js';
import {
literalTypeMustHaveLiterals,
literalTypeMustNotContainListLiteral,
literalTypeMustNotContainMapLiteral,
literalTypeShouldNotHaveDuplicateLiteral,
} from './other/types/literalTypes.js';
import {
namedTypeMustNotHaveTooManyTypeArguments,
namedTypeMustNotSetTypeParameterMultipleTimes,
namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments,
} from './other/types/namedTypes.js';
import {
unionTypeMustBeUsedInCorrectContext,
unionTypeMustHaveTypes,
unionTypeShouldNotHaveDuplicateTypes,
} from './other/types/unionTypes.js';
import {
annotationCallArgumentListShouldBeNeeded,
annotationParameterListShouldNotBeEmpty,
Expand All @@ -37,12 +139,6 @@ import {
typeParameterListShouldNotBeEmpty,
unionTypeShouldNotHaveASingularTypeArgument,
} from './style.js';
import { templateStringMustHaveExpressionBetweenTwoStringParts } from './other/expressions/templateStrings.js';
import {
assignmentAssigneeMustGetValue,
assignmentShouldNotImplicitlyIgnoreResult,
yieldMustNotBeUsedInPipeline,
} from './other/statements/assignments.js';
import {
argumentTypeMustMatchParameterType,
attributeMustHaveTypeHint,
Expand All @@ -57,101 +153,6 @@ import {
resultMustHaveTypeHint,
yieldTypeMustMatchResultType,
} from './types.js';
import {
moduleDeclarationsMustMatchFileKind,
moduleWithDeclarationsMustStatePackage,
pipelineFileMustNotBeInBuiltinPackage,
} from './other/modules.js';
import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js';
import { parameterListMustNotHaveRequiredParametersAfterOptionalParameters } from './other/declarations/parameterLists.js';
import {
unionTypeMustBeUsedInCorrectContext,
unionTypeMustHaveTypes,
unionTypeShouldNotHaveDuplicateTypes,
} from './other/types/unionTypes.js';
import {
callableTypeMustNotHaveOptionalParameters,
callableTypeParameterMustNotHaveConstModifier,
} from './other/types/callableTypes.js';
import {
argumentListMustNotHavePositionalArgumentsAfterNamedArguments,
argumentListMustNotHaveTooManyArguments,
argumentListMustNotSetParameterMultipleTimes,
argumentListMustSetAllRequiredParameters,
} from './other/argumentLists.js';
import {
referenceMustNotBeFunctionPointer,
referenceMustNotBeStaticClassOrEnumReference,
referenceTargetMustNotBeAnnotationPipelineOrSchema,
} from './other/expressions/references.js';
import {
annotationCallAnnotationShouldNotBeDeprecated,
argumentCorrespondingParameterShouldNotBeDeprecated,
assigneeAssignedResultShouldNotBeDeprecated,
namedTypeDeclarationShouldNotBeDeprecated,
referenceTargetShouldNotBeDeprecated,
requiredParameterMustNotBeDeprecated,
} from './builtins/deprecated.js';
import {
annotationCallAnnotationShouldNotBeExperimental,
argumentCorrespondingParameterShouldNotBeExperimental,
assigneeAssignedResultShouldNotBeExperimental,
namedTypeDeclarationShouldNotBeExperimental,
referenceTargetShouldNotExperimental,
} from './builtins/experimental.js';
import { placeholderShouldBeUsed, placeholdersMustNotBeAnAlias } from './other/declarations/placeholders.js';
import {
segmentParameterShouldBeUsed,
segmentResultMustBeAssignedExactlyOnce,
segmentShouldBeUsed,
} from './other/declarations/segments.js';
import {
lambdaMustBeAssignedToTypedParameter,
lambdaParameterMustNotHaveConstModifier,
} from './other/expressions/lambdas.js';
import {
indexedAccessesShouldBeUsedWithCaution,
literalTypesShouldBeUsedWithCaution,
mapsShouldBeUsedWithCaution,
unionTypesShouldBeUsedWithCaution,
} from './experimentalLanguageFeatures.js';
import { requiredParameterMustNotBeExpert } from './builtins/expert.js';
import {
annotationCallArgumentsMustBeConstant,
annotationCallMustNotLackArgumentList,
callableTypeParametersMustNotBeAnnotated,
callableTypeResultsMustNotBeAnnotated,
lambdaParametersMustNotBeAnnotated,
} from './other/declarations/annotationCalls.js';
import {
memberAccessMustBeNullSafeIfReceiverIsNullable,
memberAccessOfEnumVariantMustNotLackInstantiation,
} from './other/expressions/memberAccesses.js';
import { importPackageMustExist, importPackageShouldNotBeEmpty } from './other/imports.js';
import { singleUseAnnotationsMustNotBeRepeated } from './builtins/repeatable.js';
import {
namedTypeMustNotHaveTooManyTypeArguments,
namedTypeMustNotSetTypeParameterMultipleTimes,
namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments,
} from './other/types/namedTypes.js';
import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './inheritance.js';
import {
pythonNameMustNotBeSetIfPythonCallIsSet,
pythonNameShouldDifferFromSafeDsName,
} from './builtins/pythonName.js';
import { pythonModuleShouldDifferFromSafeDsPackage } from './builtins/pythonModule.js';
import { divisionDivisorMustNotBeZero } from './other/expressions/infixOperations.js';
import { constantParameterMustHaveConstantDefaultValue } from './other/declarations/parameters.js';
import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expressions/calls.js';
import {
literalTypeMustHaveLiterals,
literalTypeMustNotContainListLiteral,
literalTypeMustNotContainMapLiteral,
literalTypeShouldNotHaveDuplicateLiteral,
} from './other/types/literalTypes.js';
import { annotationCallMustHaveCorrectTarget, targetShouldNotHaveDuplicateEntries } from './builtins/target.js';
import { pythonCallMustOnlyContainValidTemplateExpressions } from './builtins/pythonCall.js';
import { typeParameterMustHaveSufficientContext } from './other/declarations/typeParameters.js';

/**
* Register custom validation checks.
Expand Down Expand Up @@ -283,6 +284,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
constantParameterMustHaveConstantDefaultValue(services),
parameterMustHaveTypeHint,
parameterDefaultValueTypeMustMatchParameterType(services),
pureParameterMustHaveCallableType(services),
requiredParameterMustNotBeDeprecated(services),
requiredParameterMustNotBeExpert(services),
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tests.validation.builtins.pure.pureParameterMustHaveCallableType

// $TEST$ error "A pure parameter must have a callable type."
// $TEST$ error "A pure parameter must have a callable type."
// $TEST$ no error "A pure parameter must have a callable type."
// $TEST$ no error "A pure parameter must have a callable type."
// $TEST$ no error "A pure parameter must have a callable type."
annotation MyAnnotation(
@Pure »a«: Int,
@Pure »b«: Unresolved,
@Pure »c«: () -> (),
@Pure »d«,
»e«: Int,
)

0 comments on commit 6c52868

Please sign in to comment.