diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index 4229f2315..cbdf5f509 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -5,7 +5,7 @@ import { resourceNameToUri } from '../../helpers/resources.js'; import { URI } from 'langium'; import { SafeDsServices } from '../safe-ds-module.js'; import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js'; -import { toConstantExpressionOrUndefined } from '../partialEvaluation/toConstantExpressionOrUndefined.js'; +import { toConstantExpression } from '../partialEvaluation/toConstantExpression.js'; import { ConstantExpression, ConstantString } from '../partialEvaluation/model.js'; const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub'); @@ -97,6 +97,6 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { const expression = argumentsOrEmpty(annotationCall).find( (it) => this.nodeMapper.argumentToParameterOrUndefined(it)?.name === parameterName, )?.value; - return toConstantExpressionOrUndefined(expression); + return toConstantExpression(expression); } } diff --git a/src/language/helpers/nodeProperties.ts b/src/language/helpers/nodeProperties.ts index a0340cfb3..cb8a0717d 100644 --- a/src/language/helpers/nodeProperties.ts +++ b/src/language/helpers/nodeProperties.ts @@ -1,14 +1,17 @@ import { + isSdsAnnotation, isSdsAssignment, isSdsAttribute, isSdsBlockLambda, isSdsBlockLambdaResult, + isSdsCallable, isSdsCallableType, isSdsClass, isSdsDeclaration, isSdsEnum, isSdsEnumVariant, isSdsFunction, + isSdsLambda, isSdsModule, isSdsModuleMember, isSdsPlaceholder, @@ -80,6 +83,21 @@ export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => { return Boolean(node.typeParameter); }; +export const isConstantParameter = (node: SdsParameter | undefined): boolean => { + if (!node) { + return false; + } + + const containingCallable = getContainerOfType(node, isSdsCallable); + + // In those cases, the const modifier is not applicable + if (isSdsCallableType(containingCallable) || isSdsLambda(containingCallable)) { + return false; + } + + return isSdsAnnotation(containingCallable) || node.isConstant; +}; + export const isRequiredParameter = (node: SdsParameter): boolean => { return !node.defaultValue; }; diff --git a/src/language/partialEvaluation/model.ts b/src/language/partialEvaluation/model.ts index cc3023474..03a0c81bf 100644 --- a/src/language/partialEvaluation/model.ts +++ b/src/language/partialEvaluation/model.ts @@ -179,6 +179,49 @@ export class ConstantInt extends ConstantNumber { } } +export class ConstantList extends ConstantExpression { + constructor(readonly elements: ConstantExpression[]) { + super(); + } + + equals(other: ConstantExpression): boolean { + return other instanceof ConstantList && this.elements.every((e, i) => e.equals(other.elements[i])); + } + + toString(): string { + return `[${this.elements.join(', ')}]`; + } +} + +export class ConstantMap extends ConstantExpression { + constructor(readonly entries: ConstantMapEntry[]) { + super(); + } + + equals(other: ConstantExpression): boolean { + return other instanceof ConstantMap && this.entries.every((e, i) => e.equals(other.entries[i])); + } + + toString(): string { + return `{${this.entries.join(', ')}}`; + } +} + +export class ConstantMapEntry { + constructor( + readonly key: ConstantExpression, + readonly value: ConstantExpression, + ) {} + + equals(other: ConstantMapEntry): boolean { + return this.key.equals(other.key) && this.value.equals(other.value); + } + + toString(): string { + return `${this.key}: ${this.value}`; + } +} + class ConstantNullClass extends ConstantExpression { equals(other: ConstantExpression): boolean { return other instanceof ConstantNullClass; @@ -209,4 +252,16 @@ export class ConstantString extends ConstantExpression { } } +class UnknownValueClass extends ConstantExpression { + override equals(other: ConstantExpression): boolean { + return other instanceof UnknownValueClass; + } + + toString(): string { + return '$unknown'; + } +} + +export const UnknownValue = new UnknownValueClass(); + /* c8 ignore stop */ diff --git a/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts b/src/language/partialEvaluation/toConstantExpression.ts similarity index 87% rename from src/language/partialEvaluation/toConstantExpressionOrUndefined.ts rename to src/language/partialEvaluation/toConstantExpression.ts index 062c0244c..fbb021d6d 100644 --- a/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts +++ b/src/language/partialEvaluation/toConstantExpression.ts @@ -1,14 +1,15 @@ import { - ParameterSubstitutions, ConstantBoolean, ConstantExpression, ConstantFloat, ConstantInt, + ConstantList, + ConstantMap, ConstantNull, ConstantString, - IntermediateBlockLambda, - IntermediateExpressionLambda, + ParameterSubstitutions, SimplifiedExpression, + UnknownValue, } from './model.js'; import { AstNode } from 'langium'; import { @@ -22,6 +23,8 @@ import { isSdsIndexedAccess, isSdsInfixOperation, isSdsInt, + isSdsList, + isSdsMap, isSdsMemberAccess, isSdsNull, isSdsParenthesizedExpression, @@ -45,32 +48,29 @@ import { /* c8 ignore start */ /** - * Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `undefined`. + * Tries to evaluate this expression. */ -export const toConstantExpressionOrUndefined = (node: AstNode | undefined): ConstantExpression | undefined => { +export const toConstantExpression = (node: AstNode | undefined): ConstantExpression => { if (!node) { - return undefined; + return UnknownValue; } - return toConstantExpressionOrUndefinedImpl(node, new Map()); + return toConstantExpressionImpl(node, new Map()); }; -const toConstantExpressionOrUndefinedImpl = ( - node: AstNode, - substitutions: ParameterSubstitutions, -): ConstantExpression | undefined => { +const toConstantExpressionImpl = (node: AstNode, substitutions: ParameterSubstitutions): ConstantExpression => { const simplifiedExpression = simplify(node, substitutions)?.unwrap(); if (simplifiedExpression instanceof ConstantExpression) { return simplifiedExpression; } else { - return undefined; + return UnknownValue; } }; -const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => { +const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression => { // Only expressions have a value if (!isSdsExpression(node)) { - return undefined; + return UnknownValue; } // Base cases @@ -101,6 +101,10 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): Simplif return simplify(node.value, substitutions); } else if (isSdsInfixOperation(node)) { return simplifyInfixOperation(node, substitutions); + } else if (isSdsList(node)) { + return new ConstantList([]); + } else if (isSdsMap(node)) { + return new ConstantMap([]); } else if (isSdsParenthesizedExpression(node)) { return simplify(node.expression, substitutions); } else if (isSdsPrefixOperation(node)) { @@ -125,10 +129,7 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): Simplif throw new Error(`Missing case to handle ${node.$type}.`); }; -const simplifyBlockLambda = ( - _node: SdsBlockLambda, - _substitutions: ParameterSubstitutions, -): IntermediateBlockLambda | undefined => { +const simplifyBlockLambda = (_node: SdsBlockLambda, _substitutions: ParameterSubstitutions): SimplifiedExpression => { // return when { // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateBlockLambda( // parameters = parametersOrEmpty(), @@ -137,13 +138,13 @@ const simplifyBlockLambda = ( // ) // else -> undefined // } - return undefined; + return UnknownValue; }; const simplifyExpressionLambda = ( _node: SdsExpressionLambda, _substitutions: ParameterSubstitutions, -): IntermediateExpressionLambda | undefined => { +): SimplifiedExpression => { // return when { // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateExpressionLambda( // parameters = parametersOrEmpty(), @@ -152,19 +153,23 @@ const simplifyExpressionLambda = ( // ) // else -> undefined // } - return undefined; + return UnknownValue; }; const simplifyInfixOperation = ( node: SdsInfixOperation, substitutions: ParameterSubstitutions, -): ConstantExpression | undefined => { +): SimplifiedExpression => { // By design none of the operators are short-circuited - const constantLeft = toConstantExpressionOrUndefinedImpl(node.leftOperand, substitutions); - if (!constantLeft) return; + const constantLeft = toConstantExpressionImpl(node.leftOperand, substitutions); + if (constantLeft === UnknownValue) { + return UnknownValue; + } - const constantRight = toConstantExpressionOrUndefinedImpl(node.rightOperand, substitutions); - if (!constantRight) return; + const constantRight = toConstantExpressionImpl(node.rightOperand, substitutions); + if (constantRight === UnknownValue) { + return UnknownValue; + } // return when (operator()) { // Or -> simplifyLogicalOp(constantLeft, Boolean::or, constantRight) @@ -232,7 +237,7 @@ const simplifyInfixOperation = ( // else -> constantLeft // } // } - return undefined; + return UnknownValue; }; // private fun simplifyLogicalOp( @@ -288,9 +293,11 @@ const simplifyInfixOperation = ( const simplifyPrefixOperation = ( node: SdsPrefixOperation, substitutions: ParameterSubstitutions, -): ConstantExpression | undefined => { - const constantOperand = toConstantExpressionOrUndefinedImpl(node.operand, substitutions); - if (!constantOperand) return; +): SimplifiedExpression => { + const constantOperand = toConstantExpressionImpl(node.operand, substitutions); + if (constantOperand === UnknownValue) { + return UnknownValue; + } if (node.operator === 'not') { if (constantOperand instanceof ConstantBoolean) { @@ -304,22 +311,22 @@ const simplifyPrefixOperation = ( } } - return undefined; + return UnknownValue; }; const simplifyTemplateString = ( node: SdsTemplateString, substitutions: ParameterSubstitutions, -): ConstantExpression | undefined => { - const constantExpressions = node.expressions.map((it) => toConstantExpressionOrUndefinedImpl(it, substitutions)); - if (constantExpressions.some((it) => it === undefined)) { - return undefined; +): SimplifiedExpression => { + const constantExpressions = node.expressions.map((it) => toConstantExpressionImpl(it, substitutions)); + if (constantExpressions.some((it) => it === UnknownValue)) { + return UnknownValue; } return new ConstantString(constantExpressions.map((it) => it!.toInterpolationString()).join('')); }; -const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => { +const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression => { // val simpleReceiver = simplifyReceiver(substitutions) ?: return undefined // val newSubstitutions = buildNewSubstitutions(simpleReceiver, substitutions) // @@ -340,7 +347,7 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S // ) // } // } - return undefined; + return UnknownValue; }; // private fun SdsCall.simplifyReceiver(substitutions: ParameterSubstitutions): SdsIntermediateCallable? { @@ -387,19 +394,16 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S const simplifyIndexedAccess = ( _node: SdsIndexedAccess, _substitutions: ParameterSubstitutions, -): SimplifiedExpression | undefined => { +): SimplifiedExpression => { // val simpleReceiver = receiver.simplify(substitutions) as? SdsIntermediateVariadicArguments ?: return undefined // val simpleIndex = index.simplify(substitutions) as? SdsConstantInt ?: return undefined // // return simpleReceiver.getArgumentByIndexOrNull(simpleIndex.value) // } - return undefined; + return UnknownValue; }; -const simplifyMemberAccess = ( - _node: SdsMemberAccess, - _substitutions: ParameterSubstitutions, -): SimplifiedExpression | undefined => { +const simplifyMemberAccess = (_node: SdsMemberAccess, _substitutions: ParameterSubstitutions): SimplifiedExpression => { // private fun SdsMemberAccess.simplifyMemberAccess(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { // if (member.declaration is SdsEnumVariant) { // return member.simplifyReference(substitutions) @@ -413,13 +417,10 @@ const simplifyMemberAccess = ( // is SdsIntermediateRecord -> simpleReceiver.getSubstitutionByReferenceOrNull(member) // else -> undefined // } - return undefined; + return UnknownValue; }; -const simplifyReference = ( - _node: SdsReference, - _substitutions: ParameterSubstitutions, -): SimplifiedExpression | undefined => { +const simplifyReference = (_node: SdsReference, _substitutions: ParameterSubstitutions): SimplifiedExpression => { // return when (val declaration = this.declaration) { // is SdsEnumVariant -> when { // declaration.parametersOrEmpty().isEmpty() -> SdsConstantEnumVariant(declaration) @@ -430,7 +431,7 @@ const simplifyReference = ( // is SdsStep -> declaration.simplifyStep() // else -> undefined // } - return undefined; + return UnknownValue; }; // private fun SdsAbstractAssignee.simplifyAssignee(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { diff --git a/src/language/validation/other/declarations/annotationCalls.ts b/src/language/validation/other/declarations/annotationCalls.ts index 8c8fa825a..57b40d127 100644 --- a/src/language/validation/other/declarations/annotationCalls.ts +++ b/src/language/validation/other/declarations/annotationCalls.ts @@ -10,16 +10,34 @@ import { import { getContainerOfType, ValidationAcceptor } from 'langium'; import { annotationCallsOrEmpty, + argumentsOrEmpty, isRequiredParameter, parametersOrEmpty, resultsOrEmpty, } from '../../../helpers/nodeProperties.js'; import { isEmpty } from 'radash'; +import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; +import { UnknownValue } from '../../../partialEvaluation/model.js'; +export const CODE_ANNOTATION_CALL_CONSTANT_ARGUMENT = 'annotation-call/constant-argument'; export const CODE_ANNOTATION_CALL_MISSING_ARGUMENT_LIST = 'annotation-call/missing-argument-list'; export const CODE_ANNOTATION_CALL_TARGET_PARAMETER = 'annotation-call/target-parameter'; export const CODE_ANNOTATION_CALL_TARGET_RESULT = 'annotation-call/target-result'; +export const annotationCallArgumentsMustBeConstant = (node: SdsAnnotationCall, accept: ValidationAcceptor) => { + for (const argument of argumentsOrEmpty(node)) { + const constantValue = toConstantExpression(argument.value); + + if (constantValue === UnknownValue) { + accept('error', 'Arguments of annotation calls must be constant.', { + node: argument, + property: 'value', + code: CODE_ANNOTATION_CALL_CONSTANT_ARGUMENT, + }); + } + } +}; + export const annotationCallMustNotLackArgumentList = (node: SdsAnnotationCall, accept: ValidationAcceptor) => { if (node.argumentList) { return; diff --git a/src/language/validation/other/declarations/parameters.ts b/src/language/validation/other/declarations/parameters.ts new file mode 100644 index 000000000..624168a2e --- /dev/null +++ b/src/language/validation/other/declarations/parameters.ts @@ -0,0 +1,23 @@ +import { isSdsAnnotation, isSdsCallable, SdsParameter } from '../../../generated/ast.js'; +import { getContainerOfType, ValidationAcceptor } from 'langium'; +import { isConstantParameter } from '../../../helpers/nodeProperties.js'; +import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; +import { UnknownValue } from '../../../partialEvaluation/model.js'; + +export const CODE_PARAMETER_CONSTANT_DEFAULT_VALUE = 'parameter/constant-default-value'; + +export const constantParameterMustHaveConstantDefaultValue = (node: SdsParameter, accept: ValidationAcceptor) => { + if (!isConstantParameter(node) || !node.defaultValue) return; + + const defaultValue = toConstantExpression(node.defaultValue); + if (defaultValue === UnknownValue) { + const containingCallable = getContainerOfType(node, isSdsCallable); + const kind = isSdsAnnotation(containingCallable) ? 'annotation' : 'constant'; + + accept('error', `Default values of ${kind} parameters must be constant.`, { + node, + property: 'defaultValue', + code: CODE_PARAMETER_CONSTANT_DEFAULT_VALUE, + }); + } +}; diff --git a/src/language/validation/other/expressions/calls.ts b/src/language/validation/other/expressions/calls.ts new file mode 100644 index 000000000..3f63c2d70 --- /dev/null +++ b/src/language/validation/other/expressions/calls.ts @@ -0,0 +1,28 @@ +import { SdsCall } from '../../../generated/ast.js'; +import { ValidationAcceptor } from 'langium'; +import { argumentsOrEmpty, isConstantParameter } from '../../../helpers/nodeProperties.js'; +import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; +import { SafeDsServices } from '../../../safe-ds-module.js'; +import { UnknownValue } from '../../../partialEvaluation/model.js'; + +export const CODE_CALL_CONSTANT_ARGUMENT = 'call/constant-argument'; + +export const callArgumentsMustBeConstantIfParameterIsConstant = (services: SafeDsServices) => { + const nodeMapper = services.helpers.NodeMapper; + + return (node: SdsCall, accept: ValidationAcceptor) => { + for (const argument of argumentsOrEmpty(node)) { + const parameter = nodeMapper.argumentToParameterOrUndefined(argument); + if (!isConstantParameter(parameter)) continue; + + const value = toConstantExpression(argument.value); + if (value === UnknownValue) { + accept('error', 'Arguments assigned to constant parameters must be constant.', { + node: argument, + property: 'value', + code: CODE_CALL_CONSTANT_ARGUMENT, + }); + } + } + }; +}; diff --git a/src/language/validation/other/expressions/infixOperations.ts b/src/language/validation/other/expressions/infixOperations.ts index 06a6ebaad..58f703bbd 100644 --- a/src/language/validation/other/expressions/infixOperations.ts +++ b/src/language/validation/other/expressions/infixOperations.ts @@ -1,7 +1,7 @@ import { SdsInfixOperation } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; import { SafeDsServices } from '../../../safe-ds-module.js'; -import { toConstantExpressionOrUndefined } from '../../../partialEvaluation/toConstantExpressionOrUndefined.js'; +import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; import { ConstantFloat, ConstantInt } from '../../../partialEvaluation/model.js'; import { UnknownType } from '../../../typing/model.js'; @@ -26,7 +26,7 @@ export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => { return; } - const divisorValue = toConstantExpressionOrUndefined(node.rightOperand); + const divisorValue = toConstantExpression(node.rightOperand); if ( divisorValue && (divisorValue.equals(zeroInt) || divisorValue.equals(zeroFloat) || divisorValue.equals(minusZeroFloat)) diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index ee0239bd5..769d137c9 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -85,6 +85,7 @@ import { lambdaParameterMustNotHaveConstModifier } from './other/expressions/lam import { indexedAccessesShouldBeUsedWithCaution } from './experimentalLanguageFeatures.js'; import { requiredParameterMustNotBeExpert } from './builtins/expert.js'; import { + annotationCallArgumentsMustBeConstant, annotationCallMustNotLackArgumentList, callableTypeParametersMustNotBeAnnotated, callableTypeResultsMustNotBeAnnotated, @@ -105,6 +106,8 @@ import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './i import { 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'; /** * Register custom validation checks. @@ -130,6 +133,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { annotationCallAnnotationShouldNotBeDeprecated(services), annotationCallAnnotationShouldNotBeExperimental(services), annotationCallArgumentListShouldBeNeeded, + annotationCallArgumentsMustBeConstant, annotationCallMustNotLackArgumentList, ], SdsArgument: [ @@ -139,7 +143,11 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsArgumentList: [argumentListMustNotHavePositionalArgumentsAfterNamedArguments], SdsAttribute: [attributeMustHaveTypeHint], SdsBlockLambda: [blockLambdaMustContainUniqueNames], - SdsCall: [callArgumentListShouldBeNeeded(services), callReceiverMustBeCallable(services)], + SdsCall: [ + callArgumentListShouldBeNeeded(services), + callArgumentsMustBeConstantIfParameterIsConstant(services), + callReceiverMustBeCallable(services), + ], SdsCallableType: [ callableTypeMustContainUniqueNames, callableTypeMustNotHaveOptionalParameters, @@ -192,6 +200,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments, ], SdsParameter: [ + constantParameterMustHaveConstantDefaultValue, parameterMustHaveTypeHint, requiredParameterMustNotBeDeprecated(services), requiredParameterMustNotBeExpert(services), diff --git a/src/language/validation/style.ts b/src/language/validation/style.ts index 01a4b718c..a8c3fa8fd 100644 --- a/src/language/validation/style.ts +++ b/src/language/validation/style.ts @@ -23,7 +23,7 @@ import { isEmpty } from 'radash'; import { isRequiredParameter, parametersOrEmpty, typeParametersOrEmpty } from '../helpers/nodeProperties.js'; import { SafeDsServices } from '../safe-ds-module.js'; import { UnknownType } from '../typing/model.js'; -import { toConstantExpressionOrUndefined } from '../partialEvaluation/toConstantExpressionOrUndefined.js'; +import { toConstantExpression } from '../partialEvaluation/toConstantExpression.js'; import { ConstantNull } from '../partialEvaluation/model.js'; export const CODE_STYLE_UNNECESSARY_ASSIGNMENT = 'style/unnecessary-assignment'; @@ -177,8 +177,8 @@ export const elvisOperatorShouldBeNeeded = (services: SafeDsServices) => { ); } - const leftValue = toConstantExpressionOrUndefined(node.leftOperand); - const rightValue = toConstantExpressionOrUndefined(node.rightOperand); + const leftValue = toConstantExpression(node.leftOperand); + const rightValue = toConstantExpression(node.rightOperand); if (leftValue === ConstantNull && rightValue === ConstantNull) { accept( 'info', diff --git a/src/resources/builtins/safeds/lang/annotationUsage.sdsstub b/src/resources/builtins/safeds/lang/annotationUsage.sdsstub index cc68ec009..82ce3d9d1 100644 --- a/src/resources/builtins/safeds/lang/annotationUsage.sdsstub +++ b/src/resources/builtins/safeds/lang/annotationUsage.sdsstub @@ -1,7 +1,7 @@ package safeds.lang @Description("The annotation can target these declaration types. If the @Target annotation is not used any declaration type can be targeted.") -@Target(AnnotationTarget.Annotation) +@Target([AnnotationTarget.Annotation]) annotation Target( @Description("The valid targets.") targets: List diff --git a/tests/language/partialEvaluation/testPartialEvaluation.test.ts b/tests/language/partialEvaluation/testPartialEvaluation.test.ts index 9a0617945..1a705ef60 100644 --- a/tests/language/partialEvaluation/testPartialEvaluation.test.ts +++ b/tests/language/partialEvaluation/testPartialEvaluation.test.ts @@ -5,9 +5,10 @@ import { clearDocuments } from 'langium/test'; import { AssertionError } from 'assert'; import { locationToString } from '../../helpers/location.js'; import { createPartialEvaluationTests } from './creator.js'; -import { toConstantExpressionOrUndefined } from '../../../src/language/partialEvaluation/toConstantExpressionOrUndefined.js'; +import { toConstantExpression } from '../../../src/language/partialEvaluation/toConstantExpression.js'; import { Location } from 'vscode-languageserver'; import { getNodeByLocation } from '../../helpers/nodeFinder.js'; +import { UnknownValue } from '../../../src/language/partialEvaluation/model.js'; const services = createSafeDsServices(NodeFileSystem).SafeDs; @@ -31,14 +32,14 @@ describe('partial evaluation', async () => { if (equivalenceClassAssertion.locations.length > 1) { const firstLocation = equivalenceClassAssertion.locations[0]; const firstNode = getNodeByLocation(services, firstLocation); - const firstValue = toConstantExpressionOrUndefined(firstNode); + const firstValue = toConstantExpression(firstNode); if (!firstValue) { return reportUndefinedValue(firstLocation); } for (const currentLocation of equivalenceClassAssertion.locations.slice(1)) { const currentNode = getNodeByLocation(services, currentLocation); - const currentValue = toConstantExpressionOrUndefined(currentNode); + const currentValue = toConstantExpression(currentNode); if (!currentValue) { return reportUndefinedValue(currentLocation); } @@ -59,7 +60,7 @@ describe('partial evaluation', async () => { // Ensure the serialized constant expression matches the expected one for (const serializationAssertion of test.serializationAssertions) { const node = getNodeByLocation(services, serializationAssertion.location); - const actualValue = toConstantExpressionOrUndefined(node); + const actualValue = toConstantExpression(node); if (!actualValue) { return reportUndefinedValue(serializationAssertion.location); } @@ -78,14 +79,14 @@ describe('partial evaluation', async () => { // Ensure the node does not evaluate to a constant expression for (const undefinedAssertion of test.undefinedAssertions) { const node = getNodeByLocation(services, undefinedAssertion.location); - const actualValue = toConstantExpressionOrUndefined(node); - if (actualValue) { + const actualValue = toConstantExpression(node); + if (actualValue !== UnknownValue) { throw new AssertionError({ message: `A node evaluates to a constant expression, but it should not.\n Location: ${locationToString( undefinedAssertion.location, )}`, actual: actualValue.toString(), - expected: 'undefined', + expected: '$unknown', }); } } diff --git a/tests/resources/validation/other/declarations/annotation calls/arguments must be constant/main.sdstest b/tests/resources/validation/other/declarations/annotation calls/arguments must be constant/main.sdstest new file mode 100644 index 000000000..1cde651cd --- /dev/null +++ b/tests/resources/validation/other/declarations/annotation calls/arguments must be constant/main.sdstest @@ -0,0 +1,20 @@ +package tests.validation.other.declarations.annotationCalls.argumentsMustBeConstant + +@Repeatable +annotation MyAnnotation(value: Int) + +fun myFunction() -> value: Int + +// $TEST$ no error "Arguments of annotation calls must be constant." +@MyAnnotation(»1«) +// $TEST$ no error "Arguments of annotation calls must be constant." +@MyAnnotation(»-2«) +// $TEST$ error "Arguments of annotation calls must be constant." +@MyAnnotation(»myFunction()«) +// $TEST$ error "Arguments of annotation calls must be constant." +@MyAnnotation(value = »myFunction()«) +// $TEST$ error "Arguments of annotation calls must be constant." +@Unresolved(»myFunction()«) +// $TEST$ error "Arguments of annotation calls must be constant." +@MyAnnotation(unresolved = »myFunction()«) +class MyClass diff --git a/tests/resources/validation/other/declarations/parameters/default value must be constant if parameter is constant/main.sdstest b/tests/resources/validation/other/declarations/parameters/default value must be constant if parameter is constant/main.sdstest new file mode 100644 index 000000000..98b8584fc --- /dev/null +++ b/tests/resources/validation/other/declarations/parameters/default value must be constant if parameter is constant/main.sdstest @@ -0,0 +1,133 @@ +package tests.validation.other.parameters.defaultValueMustBeConstantIfParameterIsConstant + +fun f() -> value: Int + +annotation MyAnnotation( + // $TEST$ no error "Default values of annotation parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of annotation parameters must be constant." + param2: Int = »-2«, + // $TEST$ error "Default values of annotation parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of annotation parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of annotation parameters must be constant." + const param5: Int = »-2«, + // $TEST$ error "Default values of annotation parameters must be constant." + const param6: Int = »f()« +) + +class MyClass( + // $TEST$ no error "Default values of constant parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + param2: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of constant parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param5: Int = »-2«, + // $TEST$ error "Default values of constant parameters must be constant." + const param6: Int = »f()« +) + +enum MyEnum { + MyEnumVariant( + // $TEST$ no error "Default values of constant parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + param2: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of constant parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param5: Int = »-2«, + // $TEST$ error "Default values of constant parameters must be constant." + const param6: Int = »f()« + ) +} + +fun myFunction( + // $TEST$ no error "Default values of constant parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + param2: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of constant parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param5: Int = »-2«, + // $TEST$ error "Default values of constant parameters must be constant." + const param6: Int = »f()« +) + +segment mySegment( + // $TEST$ no error "Default values of constant parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + param2: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of constant parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param5: Int = »-2«, + // $TEST$ error "Default values of constant parameters must be constant." + const param6: Int = »f()«, + + callableType: ( + // $TEST$ no error "Default values of constant parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + param2: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of constant parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param5: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param6: Int = »f()«, + ) -> () +) { + ( + // $TEST$ no error "Default values of constant parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + param2: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of constant parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param5: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param6: Int = »f()«, + ) {}; + + ( + // $TEST$ no error "Default values of constant parameters must be constant." + param1: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + param2: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + param3: Int = »f()«, + + // $TEST$ no error "Default values of constant parameters must be constant." + const param4: Int = »1«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param5: Int = »-2«, + // $TEST$ no error "Default values of constant parameters must be constant." + const param6: Int = »f()«, + ) -> 1; +} diff --git a/tests/resources/validation/other/expressions/arguments/must be constant if parameter is constant/main.sdstest b/tests/resources/validation/other/expressions/arguments/must be constant if parameter is constant/main.sdstest new file mode 100644 index 000000000..9c62d7bf8 --- /dev/null +++ b/tests/resources/validation/other/expressions/arguments/must be constant if parameter is constant/main.sdstest @@ -0,0 +1,33 @@ +package tests.validation.other.expressions.arguments.argumentsMustBeConstantIfParameterIsConstant + +fun myFunction() -> res: Int + +fun myFunctionWithConstantParameter( + const constantParam: Int +) + +fun myFunctionWithNormalParameter( + param: Int +) + + +pipeline testPipeline { + // $TEST$ no error "Arguments assigned to constant parameters must be constant." + myFunctionWithConstantParameter(»1«); + // $TEST$ no error "Arguments assigned to constant parameters must be constant." + myFunctionWithConstantParameter(»-2«); + // $TEST$ error "Arguments assigned to constant parameters must be constant." + myFunctionWithConstantParameter(»myFunction()«); + // $TEST$ error "Arguments assigned to constant parameters must be constant." + myFunctionWithConstantParameter(constantParam = »myFunction()«); + + // $TEST$ no error "Arguments assigned to constant parameters must be constant." + myFunctionWithNormalParameter(»myFunction()«); + // $TEST$ no error "Arguments assigned to constant parameters must be constant." + myFunctionWithNormalParameter(param = »myFunction()«); + + // $TEST$ no error "Arguments assigned to constant parameters must be constant." + unresolved(»myFunction()«); + // $TEST$ no error "Arguments assigned to constant parameters must be constant." + myFunctionWithConstantParameter(unresolved = »myFunction()«); +}