diff --git a/docs/lexer/safe_ds_lexer/_safe_ds_lexer.py b/docs/lexer/safe_ds_lexer/_safe_ds_lexer.py index 70178294d..bdb501665 100644 --- a/docs/lexer/safe_ds_lexer/_safe_ds_lexer.py +++ b/docs/lexer/safe_ds_lexer/_safe_ds_lexer.py @@ -23,6 +23,7 @@ "false", "null", "true", + "unknown", ) keywords_namespace = ( 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 1e134bedc..1c6b6b12e 100644 --- a/packages/safe-ds-lang/src/language/grammar/safe-ds.langium +++ b/packages/safe-ds-lang/src/language/grammar/safe-ds.langium @@ -756,6 +756,7 @@ SdsLiteral returns SdsLiteral: | SdsMap | SdsNull | SdsString + | SdsUnknown ; interface SdsBoolean extends SdsLiteral { @@ -824,6 +825,12 @@ SdsString returns SdsString: value=STRING ; +interface SdsUnknown extends SdsLiteral {} + +SdsUnknown returns SdsUnknown: + {SdsUnknown} 'unknown' +; + interface SdsParenthesizedExpression extends SdsExpression { expression: SdsExpression } diff --git a/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts b/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts index 0e5963a79..ce92880fa 100644 --- a/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts +++ b/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts @@ -34,6 +34,7 @@ import { isSdsTemplateStringInner, isSdsTemplateStringStart, isSdsTypeCast, + isSdsUnknown, type SdsArgument, type SdsAssignee, type SdsCall, @@ -212,6 +213,8 @@ export class SafeDsPartialEvaluator { return new StringConstant(node.value); } else if (isSdsTemplateStringEnd(node)) { return new StringConstant(node.value); + } else if (isSdsUnknown(node)) { + return UnknownEvaluatedNode; } else if (isSdsBlockLambda(node)) { return new BlockLambdaClosure(node, substitutions); } else if (isSdsExpressionLambda(node)) { @@ -678,7 +681,14 @@ export class SafeDsPartialEvaluator { * Returns whether the given expression can be the value of a constant parameter. */ canBeValueOfConstantParameter = (node: SdsExpression): boolean => { - if (isSdsBoolean(node) || isSdsFloat(node) || isSdsInt(node) || isSdsNull(node) || isSdsString(node)) { + if ( + isSdsBoolean(node) || + isSdsFloat(node) || + isSdsInt(node) || + isSdsNull(node) || + isSdsString(node) || + isSdsUnknown(node) + ) { return true; } else if (isSdsCall(node)) { // If some arguments are not provided, we already show an error. diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts index 5caa13305..44edcf271 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts @@ -41,6 +41,7 @@ import { isSdsTypeCast, isSdsTypeParameter, isSdsUnionType, + isSdsUnknown, isSdsYield, SdsAbstractResult, SdsAssignee, @@ -345,6 +346,8 @@ export class SafeDsTypeComputer { return this.coreTypes.Map(keyType, valueType); } else if (isSdsTemplateString(node)) { return this.coreTypes.String; + } else if (isSdsUnknown(node)) { + return this.coreTypes.Nothing; } // Recursive cases diff --git a/packages/safe-ds-lang/src/language/validation/other/expressions/literals.ts b/packages/safe-ds-lang/src/language/validation/other/expressions/literals.ts new file mode 100644 index 000000000..5f8f6c965 --- /dev/null +++ b/packages/safe-ds-lang/src/language/validation/other/expressions/literals.ts @@ -0,0 +1,39 @@ +import { + isSdsCallable, + isSdsCallableType, + isSdsClass, + isSdsEnumVariant, + isSdsFunction, + isSdsParameter, + SdsUnknown, +} from '../../../generated/ast.js'; +import { AstUtils, ValidationAcceptor } from 'langium'; + +export const CODE_LITERALS_UNKNOWN = 'literals/unknown'; + +export const unknownMustOnlyBeUsedAsDefaultValueOfStub = (node: SdsUnknown, accept: ValidationAcceptor): void => { + if (!unknownIsUsedCorrectly(node)) { + accept( + 'error', + 'unknown is only allowed as the default value of a parameter of a class, enum variant, or function.', + { + node, + code: CODE_LITERALS_UNKNOWN, + }, + ); + } +}; + +const unknownIsUsedCorrectly = (node: SdsUnknown): boolean => { + if (!isSdsParameter(node.$container) || node.$containerProperty !== 'defaultValue') { + return false; + } + + const containingCallable = AstUtils.getContainerOfType(node.$container, isSdsCallable); + return ( + isSdsCallableType(containingCallable) || // Callable types must not have default values in general + isSdsClass(containingCallable) || + isSdsEnumVariant(containingCallable) || + isSdsFunction(containingCallable) + ); +}; 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 8f9e870bc..af090c093 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 @@ -185,6 +185,7 @@ import { parameterBoundRightOperandMustEvaluateToFloatConstantOrIntConstant, parameterDefaultValueMustRespectParameterBounds, } from './other/declarations/parameterBounds.js'; +import { unknownMustOnlyBeUsedAsDefaultValueOfStub } from './other/expressions/literals.js'; /** * Register custom validation checks. @@ -379,6 +380,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { unionTypeShouldNotHaveDuplicateTypes(services), unionTypeShouldNotHaveASingularTypeArgument(services), ], + SdsUnknown: [unknownMustOnlyBeUsedAsDefaultValueOfStub], SdsYield: [yieldMustNotBeUsedInPipeline, yieldTypeMustMatchResultType(services)], }; registry.register(checks); diff --git a/packages/safe-ds-lang/tests/language/partialEvaluation/canBeValueOfConstantParameter.test.ts b/packages/safe-ds-lang/tests/language/partialEvaluation/canBeValueOfConstantParameter.test.ts index 677325e9f..232bd9764 100644 --- a/packages/safe-ds-lang/tests/language/partialEvaluation/canBeValueOfConstantParameter.test.ts +++ b/packages/safe-ds-lang/tests/language/partialEvaluation/canBeValueOfConstantParameter.test.ts @@ -33,6 +33,10 @@ describe('SafeDsTypeChecker', async () => { code: '"text"', expected: true, }, + { + code: 'unknown', + expected: true, + }, { code: 'unresolved()', expected: true, diff --git a/packages/safe-ds-lang/tests/language/typing/type computer/computeUpperBound.test.ts b/packages/safe-ds-lang/tests/language/typing/type computer/computeUpperBound.test.ts index 916e1826b..cce9a1e27 100644 --- a/packages/safe-ds-lang/tests/language/typing/type computer/computeUpperBound.test.ts +++ b/packages/safe-ds-lang/tests/language/typing/type computer/computeUpperBound.test.ts @@ -16,7 +16,7 @@ const code = ` LegalDirectBounds sub Number, LegalIndirectBounds sub LegalDirectBounds, UnnamedBounds sub literal<2>, - UnresolvedBounds sub unknown, + UnresolvedBounds sub Unresolved, > `; const module = await getNodeOfType(services, code, isSdsModule); diff --git a/packages/safe-ds-lang/tests/resources/formatting/expressions/literals/unknown.sdstest b/packages/safe-ds-lang/tests/resources/formatting/expressions/literals/unknown.sdstest new file mode 100644 index 000000000..c8bd3b32c --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/formatting/expressions/literals/unknown.sdstest @@ -0,0 +1,9 @@ +pipeline myPipeline { + unknown; +} + +// ----------------------------------------------------------------------------- + +pipeline myPipeline { + unknown; +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/good-unknown.sdstest b/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/good-unknown.sdstest new file mode 100644 index 000000000..c7dc54186 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/good-unknown.sdstest @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +pipeline myPipeline { + unknown; +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/keywords as names/bad-unescaped unknown.sdstest b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/bad-unescaped unknown.sdstest new file mode 100644 index 000000000..3cd139a36 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/bad-unescaped unknown.sdstest @@ -0,0 +1,3 @@ +// $TEST$ syntax_error + +class unknown diff --git a/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdstest b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdstest index b44cdb5f2..74f373619 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdstest +++ b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdstest @@ -26,6 +26,7 @@ class `segment` class `sub` class `true` class `union` +class `unknown` class `val` class `where` class `yield` diff --git a/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/unknown literals/main.sdstest b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/unknown literals/main.sdstest new file mode 100644 index 000000000..432b082c9 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/unknown literals/main.sdstest @@ -0,0 +1,6 @@ +package tests.partialValidation.baseCases.unknownLiterals + +pipeline test { + // $TEST$ serialization ? + »unknown«; +} diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/literals/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/expressions/literals/main.sdstest index f17a08ae7..ddb12e0e2 100644 --- a/packages/safe-ds-lang/tests/resources/typing/expressions/literals/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/literals/main.sdstest @@ -16,4 +16,7 @@ pipeline myPipeline { // $TEST$ serialization literal<"myString"> val stringLiteral = »"myString"«; + + // $TEST$ serialization Nothing + val unknownLiteral = »unknown«; } diff --git a/packages/safe-ds-lang/tests/resources/typing/highest common subtype/unknown type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/highest common subtype/unknown type/main.sdstest index 2ad0d1123..0d700e362 100644 --- a/packages/safe-ds-lang/tests/resources/typing/highest common subtype/unknown type/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/highest common subtype/unknown type/main.sdstest @@ -5,11 +5,11 @@ class Contravariant segment mySegment( one: Contravariant>, nullable: Contravariant>, - unknown: Contravariant, + unresolved: Contravariant, ) { // $TEST$ serialization List> - »[one, unknown]«; + »[one, unresolved]«; // $TEST$ serialization List> - »[nullable, unknown]«; + »[nullable, unresolved]«; } diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/singular type after simplification/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/singular type after simplification/main.sdstest index 72381e3e6..8ab2ae79e 100644 --- a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/singular type after simplification/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/singular type after simplification/main.sdstest @@ -32,7 +32,7 @@ segment mySegment( »[C]«; // $TEST$ serialization List<$unknown> - »[unknown]«; + »[unresolved]«; } // $TEST$ serialization List diff --git a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/unknown type/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/unknown type/main.sdstest index e73d1d988..43a823ae4 100644 --- a/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/unknown type/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/lowest common supertype/unknown type/main.sdstest @@ -2,8 +2,8 @@ package tests.typing.lowestCommonSupertype.unknownType segment mySegment() { // $TEST$ serialization List - »[1, unknown]«; + »[1, unresolved]«; // $TEST$ serialization List - »[null, unknown]«; + »[null, unresolved]«; } diff --git a/packages/safe-ds-lang/tests/resources/validation/other/expressions/literals/unknown must only be used as default value of stub/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/other/expressions/literals/unknown must only be used as default value of stub/main.sdstest new file mode 100644 index 000000000..d8a3c4d73 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/expressions/literals/unknown must only be used as default value of stub/main.sdstest @@ -0,0 +1,30 @@ +package tests.validation.other.expressions.literals.unknownMustOnlyBeUsedAsDefaultValueOfStub + +// $TEST$ error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." +annotation MyAnnotation(p: Int = »unknown«) + +// $TEST$ no error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." +class MyClass(p: Int = »unknown«) + +enum MyEnum { + // $TEST$ no error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." + MyVariant(p: Int = »unknown«) +} + +// $TEST$ no error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." +fun myFunction(p: Int = »unknown«) + +segment mySegment( + // $TEST$ no error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." + f: (p: Int = »unknown«) -> (), + // $TEST$ error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." + p: Int = »unknown«, +) { + // $TEST$ error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." + (p: Int = »unknown«) {}; + // $TEST$ error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." + (p: Int = »unknown«) -> 1; + + // $TEST$ error "unknown is only allowed as the default value of a parameter of a class, enum variant, or function." + »unknown«; +} diff --git a/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json b/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json index 67a35c8ca..77c6c9213 100644 --- a/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json +++ b/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json @@ -12,7 +12,7 @@ }, { "name": "constant.language.safe-ds", - "match": "\\b(false|null|true)\\b" + "match": "\\b(false|null|true|unknown)\\b" }, { "name": "storage.type.safe-ds",