From e514ac383e68f215533201f56061882ed573f49c Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Tue, 17 Oct 2023 12:23:03 +0200 Subject: [PATCH 1/5] feat: error if default value of constant parameters is not constant --- src/language/helpers/nodeProperties.ts | 14 ++ .../other/declarations/parameters.ts | 22 +++ src/language/validation/safe-ds-validator.ts | 2 + .../main.sdstest | 133 ++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 src/language/validation/other/declarations/parameters.ts create mode 100644 tests/resources/validation/other/declarations/parameters/default value must be constant if parameter is constant/main.sdstest diff --git a/src/language/helpers/nodeProperties.ts b/src/language/helpers/nodeProperties.ts index a0340cfb3..a8c6a88dd 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,17 @@ export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => { return Boolean(node.typeParameter); }; +export const isConstantParameter = (node: SdsParameter): boolean => { + 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/validation/other/declarations/parameters.ts b/src/language/validation/other/declarations/parameters.ts new file mode 100644 index 000000000..6f95d9f9f --- /dev/null +++ b/src/language/validation/other/declarations/parameters.ts @@ -0,0 +1,22 @@ +import { isSdsAnnotation, isSdsCallable, SdsParameter } from '../../../generated/ast.js'; +import { getContainerOfType, ValidationAcceptor } from 'langium'; +import { isConstantParameter } from '../../../helpers/nodeProperties.js'; +import { toConstantExpressionOrUndefined } from '../../../partialEvaluation/toConstantExpressionOrUndefined.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 = toConstantExpressionOrUndefined(node.defaultValue); + if (!defaultValue) { + 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/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index ee0239bd5..8544632a5 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -105,6 +105,7 @@ 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"; /** * Register custom validation checks. @@ -192,6 +193,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments, ], SdsParameter: [ + constantParameterMustHaveConstantDefaultValue, parameterMustHaveTypeHint, requiredParameterMustNotBeDeprecated(services), requiredParameterMustNotBeExpert(services), 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; +} From 0b572ace229e915759b1a16f4dfbba386253c45a Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Tue, 17 Oct 2023 12:28:24 +0200 Subject: [PATCH 2/5] feat: error if arguments of annotation calls are not constant --- .../other/declarations/annotationCalls.ts | 18 ++++++++++++++++- src/language/validation/safe-ds-validator.ts | 2 ++ .../arguments must be constant/main.sdstest | 20 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/resources/validation/other/declarations/annotation calls/arguments must be constant/main.sdstest diff --git a/src/language/validation/other/declarations/annotationCalls.ts b/src/language/validation/other/declarations/annotationCalls.ts index 8c8fa825a..b3882924a 100644 --- a/src/language/validation/other/declarations/annotationCalls.ts +++ b/src/language/validation/other/declarations/annotationCalls.ts @@ -9,17 +9,33 @@ import { } from '../../../generated/ast.js'; import { getContainerOfType, ValidationAcceptor } from 'langium'; import { - annotationCallsOrEmpty, + annotationCallsOrEmpty, argumentsOrEmpty, isRequiredParameter, parametersOrEmpty, resultsOrEmpty, } from '../../../helpers/nodeProperties.js'; import { isEmpty } from 'radash'; +import {toConstantExpressionOrUndefined} from "../../../partialEvaluation/toConstantExpressionOrUndefined.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 = toConstantExpressionOrUndefined(argument.value); + + if (!constantValue) { + 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/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 8544632a5..0aba5fd0d 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, @@ -131,6 +132,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { annotationCallAnnotationShouldNotBeDeprecated(services), annotationCallAnnotationShouldNotBeExperimental(services), annotationCallArgumentListShouldBeNeeded, + annotationCallArgumentsMustBeConstant, annotationCallMustNotLackArgumentList, ], SdsArgument: [ 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 From 12d7b7644c36712f71abd1985dae51575ac19283 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Tue, 17 Oct 2023 12:33:11 +0200 Subject: [PATCH 3/5] feat: error if arguments of assigned to constant parameters are not constant --- src/language/helpers/nodeProperties.ts | 6 +++- .../validation/other/expressions/calls.ts | 27 +++++++++++++++ src/language/validation/safe-ds-validator.ts | 9 +++-- .../main.sdstest | 33 +++++++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/language/validation/other/expressions/calls.ts create mode 100644 tests/resources/validation/other/expressions/arguments/must be constant if parameter is constant/main.sdstest diff --git a/src/language/helpers/nodeProperties.ts b/src/language/helpers/nodeProperties.ts index a8c6a88dd..cb8a0717d 100644 --- a/src/language/helpers/nodeProperties.ts +++ b/src/language/helpers/nodeProperties.ts @@ -83,7 +83,11 @@ export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => { return Boolean(node.typeParameter); }; -export const isConstantParameter = (node: SdsParameter): boolean => { +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 diff --git a/src/language/validation/other/expressions/calls.ts b/src/language/validation/other/expressions/calls.ts new file mode 100644 index 000000000..e2dd5d74c --- /dev/null +++ b/src/language/validation/other/expressions/calls.ts @@ -0,0 +1,27 @@ +import { SdsCall } from '../../../generated/ast.js'; +import { ValidationAcceptor } from 'langium'; +import { argumentsOrEmpty, isConstantParameter } from '../../../helpers/nodeProperties.js'; +import { toConstantExpressionOrUndefined } from '../../../partialEvaluation/toConstantExpressionOrUndefined.js'; +import { SafeDsServices } from '../../../safe-ds-module.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 constantValue = toConstantExpressionOrUndefined(argument.value); + if (!constantValue) { + 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/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 0aba5fd0d..769d137c9 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -106,7 +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 { constantParameterMustHaveConstantDefaultValue } from './other/declarations/parameters.js'; +import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expressions/calls.js'; /** * Register custom validation checks. @@ -142,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, 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()«); +} From 873822a25c5e5dd5129d6d2c143bd9020fb11dcc Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Tue, 17 Oct 2023 12:56:57 +0200 Subject: [PATCH 4/5] fix: errors in builtin files --- src/language/builtins/safe-ds-annotations.ts | 4 +- src/language/partialEvaluation/model.ts | 55 +++++++++++ ...OrUndefined.ts => toConstantExpression.ts} | 97 +++++++++---------- .../other/declarations/annotationCalls.ts | 7 +- .../other/declarations/parameters.ts | 7 +- .../validation/other/expressions/calls.ts | 9 +- .../other/expressions/infixOperations.ts | 4 +- src/language/validation/style.ts | 6 +- .../safeds/lang/annotationUsage.sdsstub | 2 +- .../testPartialEvaluation.test.ts | 15 +-- 10 files changed, 132 insertions(+), 74 deletions(-) rename src/language/partialEvaluation/{toConstantExpressionOrUndefined.ts => toConstantExpression.ts} (87%) 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/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..12e8ca9a4 100644 --- a/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts +++ b/src/language/partialEvaluation/toConstantExpression.ts @@ -1,14 +1,14 @@ 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 +22,7 @@ import { isSdsIndexedAccess, isSdsInfixOperation, isSdsInt, + isSdsList, isSdsMap, isSdsMemberAccess, isSdsNull, isSdsParenthesizedExpression, @@ -45,32 +46,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 +99,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 +127,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 +136,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 +151,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 +235,7 @@ const simplifyInfixOperation = ( // else -> constantLeft // } // } - return undefined; + return UnknownValue; }; // private fun simplifyLogicalOp( @@ -288,9 +291,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 +309,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 +345,7 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S // ) // } // } - return undefined; + return UnknownValue; }; // private fun SdsCall.simplifyReceiver(substitutions: ParameterSubstitutions): SdsIntermediateCallable? { @@ -387,19 +392,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 +415,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 +429,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 b3882924a..fc5c05b09 100644 --- a/src/language/validation/other/declarations/annotationCalls.ts +++ b/src/language/validation/other/declarations/annotationCalls.ts @@ -15,7 +15,8 @@ import { resultsOrEmpty, } from '../../../helpers/nodeProperties.js'; import { isEmpty } from 'radash'; -import {toConstantExpressionOrUndefined} from "../../../partialEvaluation/toConstantExpressionOrUndefined.js"; +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'; @@ -24,9 +25,9 @@ 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 = toConstantExpressionOrUndefined(argument.value); + const constantValue = toConstantExpression(argument.value); - if (!constantValue) { + if (constantValue === UnknownValue) { accept('error', "Arguments of annotation calls must be constant.", { node: argument, property: 'value', diff --git a/src/language/validation/other/declarations/parameters.ts b/src/language/validation/other/declarations/parameters.ts index 6f95d9f9f..624168a2e 100644 --- a/src/language/validation/other/declarations/parameters.ts +++ b/src/language/validation/other/declarations/parameters.ts @@ -1,15 +1,16 @@ import { isSdsAnnotation, isSdsCallable, SdsParameter } from '../../../generated/ast.js'; import { getContainerOfType, ValidationAcceptor } from 'langium'; import { isConstantParameter } from '../../../helpers/nodeProperties.js'; -import { toConstantExpressionOrUndefined } from '../../../partialEvaluation/toConstantExpressionOrUndefined.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 = toConstantExpressionOrUndefined(node.defaultValue); - if (!defaultValue) { + const defaultValue = toConstantExpression(node.defaultValue); + if (defaultValue === UnknownValue) { const containingCallable = getContainerOfType(node, isSdsCallable); const kind = isSdsAnnotation(containingCallable) ? 'annotation' : 'constant'; diff --git a/src/language/validation/other/expressions/calls.ts b/src/language/validation/other/expressions/calls.ts index e2dd5d74c..3f63c2d70 100644 --- a/src/language/validation/other/expressions/calls.ts +++ b/src/language/validation/other/expressions/calls.ts @@ -1,8 +1,9 @@ import { SdsCall } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; import { argumentsOrEmpty, isConstantParameter } from '../../../helpers/nodeProperties.js'; -import { toConstantExpressionOrUndefined } from '../../../partialEvaluation/toConstantExpressionOrUndefined.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'; @@ -14,9 +15,9 @@ export const callArgumentsMustBeConstantIfParameterIsConstant = (services: SafeD const parameter = nodeMapper.argumentToParameterOrUndefined(argument); if (!isConstantParameter(parameter)) continue; - const constantValue = toConstantExpressionOrUndefined(argument.value); - if (!constantValue) { - accept('error', "Arguments assigned to constant parameters must be constant.", { + 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/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', }); } } From 09c986534ea5060c246bca992a0ce850a51f2efe Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Tue, 17 Oct 2023 10:59:47 +0000 Subject: [PATCH 5/5] style: apply automated linter fixes --- .../partialEvaluation/toConstantExpression.ts | 6 ++++-- .../validation/other/declarations/annotationCalls.ts | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/language/partialEvaluation/toConstantExpression.ts b/src/language/partialEvaluation/toConstantExpression.ts index 12e8ca9a4..fbb021d6d 100644 --- a/src/language/partialEvaluation/toConstantExpression.ts +++ b/src/language/partialEvaluation/toConstantExpression.ts @@ -3,7 +3,8 @@ import { ConstantExpression, ConstantFloat, ConstantInt, - ConstantList, ConstantMap, + ConstantList, + ConstantMap, ConstantNull, ConstantString, ParameterSubstitutions, @@ -22,7 +23,8 @@ import { isSdsIndexedAccess, isSdsInfixOperation, isSdsInt, - isSdsList, isSdsMap, + isSdsList, + isSdsMap, isSdsMemberAccess, isSdsNull, isSdsParenthesizedExpression, diff --git a/src/language/validation/other/declarations/annotationCalls.ts b/src/language/validation/other/declarations/annotationCalls.ts index fc5c05b09..57b40d127 100644 --- a/src/language/validation/other/declarations/annotationCalls.ts +++ b/src/language/validation/other/declarations/annotationCalls.ts @@ -9,14 +9,15 @@ import { } from '../../../generated/ast.js'; import { getContainerOfType, ValidationAcceptor } from 'langium'; import { - annotationCallsOrEmpty, argumentsOrEmpty, + 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"; +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'; @@ -28,14 +29,14 @@ export const annotationCallArgumentsMustBeConstant = (node: SdsAnnotationCall, a const constantValue = toConstantExpression(argument.value); if (constantValue === UnknownValue) { - accept('error', "Arguments of annotation calls must be constant.", { + 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) {