Skip to content

feat: show an error if a pure parameter does not have a callable type #736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
)