diff --git a/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py b/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py index 93233b1ac..be31bfc58 100644 --- a/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py +++ b/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py @@ -33,6 +33,7 @@ keywords_generic = ( "as", + "else", "const", "import", "in", diff --git a/packages/safe-ds-lang/src/language/grammar/safe-ds.langium b/packages/safe-ds-lang/src/language/grammar/safe-ds.langium index 73ebf3849..e14d6f3d9 100644 --- a/packages/safe-ds-lang/src/language/grammar/safe-ds.langium +++ b/packages/safe-ds-lang/src/language/grammar/safe-ds.langium @@ -384,7 +384,9 @@ SdsConstraintList returns SdsConstraintList: '}' ; -interface SdsConstraint extends SdsObject {} +interface SdsConstraint extends SdsObject { + message?: SdsString | SdsTemplateString +} SdsConstraint returns SdsConstraint: SdsParameterBound @@ -400,6 +402,7 @@ SdsParameterBound returns SdsParameterBound: leftOperand=[SdsParameter:ID] operator=SdsComparisonOperator rightOperand=SdsExpression + ('else' message=(SdsString | SdsTemplateString))? ; diff --git a/packages/safe-ds-lang/src/language/lsp/safe-ds-formatter.ts b/packages/safe-ds-lang/src/language/lsp/safe-ds-formatter.ts index 88c0e2559..601206859 100644 --- a/packages/safe-ds-lang/src/language/lsp/safe-ds-formatter.ts +++ b/packages/safe-ds-lang/src/language/lsp/safe-ds-formatter.ts @@ -510,6 +510,7 @@ export class SafeDsFormatter extends AbstractFormatter { const formatter = this.getNodeFormatter(node); formatter.property('operator').surround(oneSpace()); + formatter.keyword('else').surround(oneSpace()); } // ----------------------------------------------------------------------------- diff --git a/packages/safe-ds-lang/src/language/validation/other/declarations/constraints.ts b/packages/safe-ds-lang/src/language/validation/other/declarations/constraints.ts new file mode 100644 index 000000000..466a086e4 --- /dev/null +++ b/packages/safe-ds-lang/src/language/validation/other/declarations/constraints.ts @@ -0,0 +1,31 @@ +import { isSdsParameter, isSdsReference, isSdsString, SdsConstraint } from '../../../generated/ast.js'; +import { AstUtils, ValidationAcceptor } from 'langium'; +import { Parameter } from '../../../helpers/nodeProperties.js'; + +export const CODE_CONSTRAINT_MESSAGE = 'constraint/message'; + +export const messageOfConstraintsMustOnlyReferenceConstantParameters = ( + node: SdsConstraint, + accept: ValidationAcceptor, +) => { + if (!node.message || isSdsString(node.message)) { + return; + } + + for (const expression of node.message.expressions) { + const isInvalid = AstUtils.streamAst(expression) + .filter(isSdsReference) + .map((reference) => reference.target.ref) + .some((target) => { + return target && (!isSdsParameter(target) || !Parameter.isConstant(target)); + }); + + if (isInvalid) { + accept('error', 'The message of a constraint must only reference constant parameters.', { + node: expression, + property: 'target', + code: CODE_CONSTRAINT_MESSAGE, + }); + } + } +}; diff --git a/packages/safe-ds-lang/src/language/validation/other/declarations/parameterBounds.ts b/packages/safe-ds-lang/src/language/validation/other/declarations/parameterBounds.ts index 0057f7d68..4ae9dd7ae 100644 --- a/packages/safe-ds-lang/src/language/validation/other/declarations/parameterBounds.ts +++ b/packages/safe-ds-lang/src/language/validation/other/declarations/parameterBounds.ts @@ -8,7 +8,13 @@ import { } from '../../../generated/ast.js'; import { AstUtils, ValidationAcceptor } from 'langium'; import { getArguments, getParameters, Parameter } from '../../../helpers/nodeProperties.js'; -import { Constant, EvaluatedNode, FloatConstant, IntConstant } from '../../../partialEvaluation/model.js'; +import { + Constant, + EvaluatedNode, + FloatConstant, + IntConstant, + StringConstant, +} from '../../../partialEvaluation/model.js'; export const CODE_PARAMETER_BOUND_INVALID_VALUE = 'parameter-bound/invalid-value'; export const CODE_PARAMETER_BOUND_PARAMETER = 'parameter-bound/parameter'; @@ -34,10 +40,13 @@ export const callArgumentMustRespectParameterBounds = (services: SafeDsServices) for (const bound of Parameter.getBounds(parameter)) { const rightOperand = partialEvaluator.evaluate(bound.rightOperand, substitutions); - const errorMessage = checkBound(parameter.name, value, bound.operator, rightOperand); + const messageEvaluatedNode = partialEvaluator.evaluate(bound.message, substitutions); + const customMessage = + messageEvaluatedNode instanceof StringConstant ? messageEvaluatedNode.value : undefined; - if (errorMessage) { - accept('error', errorMessage, { + const error = checkBound(parameter.name, value, bound.operator, rightOperand, customMessage); + if (error) { + accept('error', error, { node: argument, property: 'value', code: CODE_PARAMETER_BOUND_INVALID_VALUE, @@ -74,12 +83,18 @@ export const parameterDefaultValueMustRespectParameterBounds = (services: SafeDs } } + const substitutions = new Map([[node, value]]); + // Error if the default value violates some bounds for (const bound of Parameter.getBounds(node)) { const rightOperand = partialEvaluator.evaluate(bound.rightOperand); - const errorMessage = checkBound(node.name, value, bound.operator, rightOperand); - if (errorMessage) { - accept('error', errorMessage, { + const messageEvaluatedNode = partialEvaluator.evaluate(bound.message, substitutions); + const customMessage = + messageEvaluatedNode instanceof StringConstant ? messageEvaluatedNode.value : undefined; + + const error = checkBound(node.name, value, bound.operator, rightOperand, customMessage); + if (error) { + accept('error', error, { node, property: 'defaultValue', code: CODE_PARAMETER_BOUND_INVALID_VALUE, @@ -94,6 +109,7 @@ const checkBound = ( leftOperand: EvaluatedNode, operator: string, rightOperand: EvaluatedNode, + customMessage?: string, ): string | undefined => { // Arguments must be valid if ( @@ -101,11 +117,15 @@ const checkBound = ( !isSdsComparisonOperator(operator) || (!(rightOperand instanceof FloatConstant) && !(rightOperand instanceof IntConstant)) ) { - return; + return undefined; } const createMessage = (relation: string) => { - return `The value of '${parameterName}' must be ${relation} ${rightOperand.toString()} but was ${leftOperand.toString()}.`; + if (customMessage) { + return customMessage; + } else { + return `The value of '${parameterName}' must be ${relation} ${rightOperand.toString()} but was ${leftOperand.toString()}.`; + } }; if (operator === '<') { diff --git a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts index 88d956482..cb5bc598d 100644 --- a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts +++ b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts @@ -191,6 +191,7 @@ import { outputStatementMustHaveValue, outputStatementMustOnlyBeUsedInPipeline, } from './other/statements/outputStatements.js'; +import { messageOfConstraintsMustOnlyReferenceConstantParameters } from './other/declarations/constraints.js'; /** * Register custom validation checks. @@ -267,6 +268,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { classMemberMustMatchOverriddenMemberAndShouldBeNeeded(services), overridingMemberPythonNameMustMatchOverriddenMember(services), ], + SdsConstraint: [messageOfConstraintsMustOnlyReferenceConstantParameters], SdsConstraintList: [constraintListsShouldBeUsedWithCaution(services), constraintListShouldNotBeEmpty(services)], SdsDeclaration: [ nameMustNotOccurOnCoreDeclaration(services), diff --git a/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/greater than constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/greater than constraint with message.sdsdev new file mode 100644 index 000000000..11ee392de --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/greater than constraint with message.sdsdev @@ -0,0 +1,7 @@ +annotation MyAnnotation where { p > 0 else "" } + +// ----------------------------------------------------------------------------- + +annotation MyAnnotation where { + p > 0 else "" +} diff --git a/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/greater than or equal constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/greater than or equal constraint with message.sdsdev new file mode 100644 index 000000000..36d9a695b --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/greater than or equal constraint with message.sdsdev @@ -0,0 +1,7 @@ +annotation MyAnnotation where { p >= 0 else "" } + +// ----------------------------------------------------------------------------- + +annotation MyAnnotation where { + p >= 0 else "" +} diff --git a/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/less than constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/less than constraint with message.sdsdev new file mode 100644 index 000000000..fa470e62f --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/less than constraint with message.sdsdev @@ -0,0 +1,7 @@ +annotation MyAnnotation where { p < 0 else "" } + +// ----------------------------------------------------------------------------- + +annotation MyAnnotation where { + p < 0 else "" +} diff --git a/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/less than or equal constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/less than or equal constraint with message.sdsdev new file mode 100644 index 000000000..c4a6da4a4 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/less than or equal constraint with message.sdsdev @@ -0,0 +1,7 @@ +annotation MyAnnotation where { p <= 0 else "" } + +// ----------------------------------------------------------------------------- + +annotation MyAnnotation where { + p <= 0 else "" +} diff --git a/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/multiple constraints.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/multiple constraints.sdsdev index f5414c717..571ed9454 100644 --- a/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/multiple constraints.sdsdev +++ b/packages/safe-ds-lang/tests/resources/formatting/declarations/constraints/multiple constraints.sdsdev @@ -1,8 +1,8 @@ -annotation MyAnnotation where { p < 0 , q > 1 } +annotation MyAnnotation where { p < 0 , q > 1 else "" } // ----------------------------------------------------------------------------- annotation MyAnnotation where { p < 0, - q > 1 + q > 1 else "" } diff --git a/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-greater than constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-greater than constraint with message.sdsdev new file mode 100644 index 000000000..bde1185de --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-greater than constraint with message.sdsdev @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +annotation MyAnnotation where { + p > 0 else "p must be positive but was {{ p }}" +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-greater than or equals constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-greater than or equals constraint with message.sdsdev new file mode 100644 index 000000000..af34ba769 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-greater than or equals constraint with message.sdsdev @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +annotation MyAnnotation where { + p >= 0 else "p must be non-negative but was {{ p }}" +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-less than constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-less than constraint with message.sdsdev new file mode 100644 index 000000000..7cec83cd4 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-less than constraint with message.sdsdev @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +annotation MyAnnotation where { + p < 0 else "p must be negative" +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-less than or equals constraint with message.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-less than or equals constraint with message.sdsdev new file mode 100644 index 000000000..fc738a492 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-less than or equals constraint with message.sdsdev @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +annotation MyAnnotation where { + p <= 0 else "p must be non-positive" +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-multiple constraints.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-multiple constraints.sdsdev index c20fe5b6c..e6eb31159 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-multiple constraints.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/declarations/constraints/good-multiple constraints.sdsdev @@ -2,5 +2,5 @@ annotation MyAnnotation where { p < 0, - q > 0 + q > 0 else "q must be positive" } diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev index 314584ad0..0d5e041e8 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev @@ -7,21 +7,27 @@ annotation MyAnnotation( »own«: Int ) where { // $TEST$ references own - »own« < 0, + // $TEST$ references own + »own« < 0 else "{{ »own« }}", // $TEST$ unresolved - »before« < 0, + // $TEST$ unresolved + »before« < 0 else "{{ »before« }}", // $TEST$ unresolved - »after« < 0, + // $TEST$ unresolved + »after« < 0 else "{{ »after« }}", // $TEST$ unresolved - »notAParameter« < 0, + // $TEST$ references notAParameter + »notAParameter« < 0 else "{{ »notAParameter« }}", // $TEST$ unresolved - »unresolved« < 0 + // $TEST$ unresolved + »unresolved« < 0 else "{{ »unresolved« }}" } fun myFunction2(after: Int) -class notAParameter +// $TEST$ target notAParameter +class »notAParameter« diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev index 16e0811f8..b9cdcdbb5 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev @@ -9,26 +9,33 @@ class MyClass(container: Int) { »own«: Int ) where { // $TEST$ references own - »own« < 0, + // $TEST$ references own + »own« < 0 else "{{ »own« }}", // $TEST$ unresolved - »container« < 0, + // $TEST$ unresolved + »container« < 0 else "{{ »container« }}", // $TEST$ unresolved - »beforeEnum« < 0, + // $TEST$ unresolved + »beforeEnum« < 0 else "{{ »beforeEnum« }}", // $TEST$ unresolved - »afterEnum« < 0, + // $TEST$ unresolved + »afterEnum« < 0 else "{{ »afterEnum« }}", // $TEST$ unresolved - »notAParameter« < 0, + // $TEST$ references notAParameter + »notAParameter« < 0 else "{{ »notAParameter« }}", // $TEST$ unresolved - »unresolved« < 0 + // $TEST$ unresolved + »unresolved« < 0 else "{{ »unresolved« }}" } } fun myFunction2(afterEnum: Int) } -class notAParameter +// $TEST$ target notAParameter +class »notAParameter« diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev index e3ada2150..ccdceab67 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev @@ -7,21 +7,27 @@ class MyClass( »own«: Int ) where { // $TEST$ references own - »own« < 0, + // $TEST$ references own + »own« < 0 else "{{ »own« }}", // $TEST$ unresolved - »before« < 0, + // $TEST$ unresolved + »before« < 0 else "{{ »before« }}", // $TEST$ unresolved - »after« < 0, + // $TEST$ unresolved + »after« < 0 else "{{ »after« }}", // $TEST$ unresolved - »notAParameter« < 0, + // $TEST$ references notAParameter + »notAParameter« < 0 else "{{ »notAParameter« }}", // $TEST$ unresolved - »unresolved« < 0 + // $TEST$ unresolved + »unresolved« < 0 else "{{ »unresolved« }}" } fun myFunction2(after: Int) -class notAParameter +// $TEST$ target notAParameter +class »notAParameter« diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev index 796412173..5f000feba 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev @@ -7,21 +7,27 @@ fun myFunction2( »own«: Int ) where { // $TEST$ references own - »own« < 0, + // $TEST$ references own + »own« < 0 else "{{ »own« }}", // $TEST$ unresolved - »before« < 0, + // $TEST$ unresolved + »before« < 0 else "{{ »before« }}", // $TEST$ unresolved - »after« < 0, + // $TEST$ unresolved + »after« < 0 else "{{ »after« }}", // $TEST$ unresolved - »notAParameter« < 0, + // $TEST$ references notAParameter + »notAParameter« < 0 else "{{ »notAParameter« }}", // $TEST$ unresolved - »unresolved« < 0 + // $TEST$ unresolved + »unresolved« < 0 else "{{ »unresolved« }}" } fun myFunction3(after: Int) -class notAParameter +// $TEST$ target notAParameter +class »notAParameter« diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev index 3ea3d3bc5..198006d39 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev @@ -13,31 +13,40 @@ class MyClass1(container: Int, overridden: Int) { »overridden«: Int, ) where { // $TEST$ references own - »own« < 0, + // $TEST$ references own + »own« < 0 else "{{ »own« }}", // $TEST$ references overridden - »overridden« < 0, + // $TEST$ references overridden + »overridden« < 0 else "{{ »overridden« }}", // $TEST$ unresolved - »container« < 0, + // $TEST$ unresolved + »container« < 0 else "{{ »container« }}", // $TEST$ unresolved - »beforeMember« < 0, + // $TEST$ unresolved + »beforeMember« < 0 else "{{ »beforeMember« }}", // $TEST$ unresolved - »afterMember« < 0, + // $TEST$ unresolved + »afterMember« < 0 else "{{ »afterMember« }}", // $TEST$ unresolved - »beforeGlobal« < 0, + // $TEST$ unresolved + »beforeGlobal« < 0 else "{{ »beforeGlobal« }}", // $TEST$ unresolved - »afterGlobal« < 0, + // $TEST$ unresolved + »afterGlobal« < 0 else "{{ »afterGlobal« }}", // $TEST$ unresolved - »notAParameter« < 0, + // $TEST$ references notAParameter + »notAParameter« < 0 else "{{ »notAParameter« }}", // $TEST$ unresolved - »unresolved« < 0 + // $TEST$ unresolved + »unresolved« < 0 else "{{ »unresolved« }}" } fun myFunction5(afterMember: Int) @@ -45,4 +54,5 @@ class MyClass1(container: Int, overridden: Int) { fun myFunction5(afterGlobal: Int) -class notAParameter +// $TEST$ target notAParameter +class »notAParameter« diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev index 1d4ec7481..73c8a0484 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev @@ -13,31 +13,40 @@ class MyClass1(container: Int, overridden: Int){ »overridden«: Int, ) where { // $TEST$ references own - »own« < 0, + // $TEST$ references own + »own« < 0 else "{{ »own« }}", // $TEST$ references overridden - »overridden« < 0, + // $TEST$ references overridden + »overridden« < 0 else "{{ »overridden« }}", // $TEST$ unresolved - »container« < 0, + // $TEST$ unresolved + »container« < 0 else "{{ »container« }}", // $TEST$ unresolved - »beforeMember« < 0, + // $TEST$ unresolved + »beforeMember« < 0 else "{{ »beforeMember« }}", // $TEST$ unresolved - »afterMember« < 0, + // $TEST$ unresolved + »afterMember« < 0 else "{{ »afterMember« }}", // $TEST$ unresolved - »beforeGlobal« < 0, + // $TEST$ unresolved + »beforeGlobal« < 0 else "{{ »beforeGlobal« }}", // $TEST$ unresolved - »afterGlobal« < 0, + // $TEST$ unresolved + »afterGlobal« < 0 else "{{ »afterGlobal« }}", // $TEST$ unresolved - »notAParameter« < 0, + // $TEST$ references notAParameter + »notAParameter« < 0 else "{{ »notAParameter« }}", // $TEST$ unresolved - »unresolved« < 0 + // $TEST$ unresolved + »unresolved« < 0 else "{{ »unresolved« }}" } fun myFunction3(afterMember: Int) @@ -45,4 +54,5 @@ class MyClass1(container: Int, overridden: Int){ fun myFunction4(afterGlobal: Int) -class notAParameter +// $TEST$ target notAParameter +class »notAParameter« diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev index d426d269d..e5e7dccc7 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev @@ -7,21 +7,27 @@ segment mySegment2( »own«: Int ) where { // $TEST$ references own - »own« < 0, + // $TEST$ references own + »own« < 0 else "{{ »own« }}", // $TEST$ unresolved - »before« < 0, + // $TEST$ unresolved + »before« < 0 else "{{ »before« }}", // $TEST$ unresolved - »after« < 0, + // $TEST$ unresolved + »after« < 0 else "{{ »after« }}", // $TEST$ unresolved - »notAParameter« < 0, + // $TEST$ references notAParameter + »notAParameter« < 0 else "{{ »notAParameter« }}", // $TEST$ unresolved - »unresolved« < 0 + // $TEST$ unresolved + »unresolved« < 0 else "{{ »unresolved« }}" } {} segment mySegment3(after: Int) {} -class notAParameter +// $TEST$ target notAParameter +class »notAParameter« diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/no message.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/no message.sdsdev new file mode 100644 index 000000000..00f66328b --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/no message.sdsdev @@ -0,0 +1,10 @@ +package tests.validation.other.declarations.constraints.messageMustOnlyReferenceConstantParameters + +// $TEST$ no error "The message of a constraint must only reference constant parameters." + +class MyClass1( + const constant: Int, +) where { + constant < 0, + +} diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/strings.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/strings.sdsdev new file mode 100644 index 000000000..28f0ccc7d --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/strings.sdsdev @@ -0,0 +1,10 @@ +package tests.validation.other.declarations.constraints.messageMustOnlyReferenceConstantParameters + +// $TEST$ no error "The message of a constraint must only reference constant parameters." + +class MyClass1( + const constant: Int, +) where { + constant < 0 else "", + +} diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/template strings.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/template strings.sdsdev new file mode 100644 index 000000000..6366ccc1c --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/template strings.sdsdev @@ -0,0 +1,19 @@ +package tests.validation.other.declarations.constraints.messageMustOnlyReferenceConstantParameters + +class MyClass2( + const constant: Int, + nonConstant: Int, +) where { + // $TEST$ no error "The message of a constraint must only reference constant parameters." + constant < 0 else "{{ »constant« }}", + // $TEST$ error "The message of a constraint must only reference constant parameters." + constant < 1 else "{{ »nonConstant« }}", + // $TEST$ error "The message of a constraint must only reference constant parameters." + constant < 2 else "{{ »NotAParameter()« }}", + // $TEST$ error "The message of a constraint must only reference constant parameters." + constant < 3 else "{{ »NotAParameter.a« }}", +} + +class NotAParameter() { + static attr a: Int +} diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/parameter bounds/arguments must match parameter bounds/custom message.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/declarations/parameter bounds/arguments must match parameter bounds/custom message.sdsdev new file mode 100644 index 000000000..9efe21b61 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/parameter bounds/arguments must match parameter bounds/custom message.sdsdev @@ -0,0 +1,33 @@ +package tests.validation.other.declarations.parameterBounds.argumentsMustMatchParameterBounds + +@Pure fun f6( + const p1: Int, + const p2: Int = -2, +) where { + p1 >= 0 else "This parameter must be non-negative.", + p1 > p2 else "p1 must be greater than p2, but p1 was {{ p1 }} and p2 was {{ p2 }}.", +} + +@Pure fun f7( + // $TEST$ error "This parameter must be non-negative." + const p1: Int = »-1«, + // $TEST$ error "This parameter must be non-negative, but was -2." + const p2: Int = »-2«, + // $TEST$ error "The value of 'p3' must be greater than or equal to 0 but was -3." + const p3: Int = »-3«, +) where { + p1 >= 0 else "This parameter must be non-negative.", + p2 >= 0 else "This parameter must be non-negative, but was {{ p2 }}.", + p3 >= 0 else "This parameter must be non-negative, but was {{ p3 }}. p2 was {{ p2 }} by the way.", +} + +segment mySegment(p: Int) { + // $TEST$ error "This parameter must be non-negative." + f6(»-1«); + + // $TEST$ error "p1 must be greater than p2, but p1 was -3 and p2 was -2." + f6(»-3«); + + // $TEST$ error "p1 must be greater than p2, but p1 was 1 and p2 was 1." + f6(»1«, 1); +} diff --git a/packages/safe-ds-vscode/syntaxes/safe-ds-stub.tmLanguage.json b/packages/safe-ds-vscode/syntaxes/safe-ds-stub.tmLanguage.json index c210c42cc..3df67243d 100644 --- a/packages/safe-ds-vscode/syntaxes/safe-ds-stub.tmLanguage.json +++ b/packages/safe-ds-vscode/syntaxes/safe-ds-stub.tmLanguage.json @@ -19,7 +19,7 @@ "match": "\\b(and|not|or|sub)\\b" }, { - "name": "variable.language.safe-ds-dev", + "name": "variable.language.safe-ds-stub", "match": "\\b(this)\\b" } ] diff --git a/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json b/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json index b82ff6de0..dee90d9b3 100644 --- a/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json +++ b/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json @@ -33,7 +33,7 @@ }, { "name": "keyword.other.safe-ds", - "match": "\\b(as|from|import|literal|union|where|yield)\\b" + "match": "\\b(as|else|from|import|literal|union|where|yield)\\b" }, { "name": "meta.safe-ds",