diff --git a/packages/safe-ds-lang/src/language/generation/python/safe-ds-python-generator.ts b/packages/safe-ds-lang/src/language/generation/python/safe-ds-python-generator.ts index 37137122f..1e863d11a 100644 --- a/packages/safe-ds-lang/src/language/generation/python/safe-ds-python-generator.ts +++ b/packages/safe-ds-lang/src/language/generation/python/safe-ds-python-generator.ts @@ -27,6 +27,7 @@ import { isSdsClass, isSdsDeclaration, isSdsEnumVariant, + isSdsExpression, isSdsExpressionLambda, isSdsExpressionStatement, isSdsFunction, @@ -48,6 +49,7 @@ import { isSdsTemplateStringInner, isSdsTemplateStringPart, isSdsTemplateStringStart, + isSdsThis, isSdsTypeCast, isSdsWildcard, isSdsYield, @@ -115,6 +117,7 @@ import { CODEGEN_PREFIX } from './constants.js'; const LAMBDA_PREFIX = `${CODEGEN_PREFIX}lambda_`; const BLOCK_LAMBDA_RESULT_PREFIX = `${CODEGEN_PREFIX}block_lambda_result_`; +const RECEIVER_PREFIX = `${CODEGEN_PREFIX}receiver_`; const YIELD_PREFIX = `${CODEGEN_PREFIX}yield_`; const RUNNER_PACKAGE = 'safeds_runner'; @@ -541,35 +544,21 @@ export class SafeDsPythonGenerator { private generateStatement(statement: SdsStatement, frame: GenerationInfoFrame, generateLambda: boolean): Generated { const result: Generated[] = []; + if (isSdsAssignment(statement)) { - if (statement.expression) { - for (const node of AstUtils.streamAllContents(statement.expression)) { - if (isSdsBlockLambda(node)) { - result.push(this.generateBlockLambda(node, frame)); - } else if (isSdsExpressionLambda(node)) { - result.push(this.generateExpressionLambda(node, frame)); - } - } - } - result.push(this.generateAssignment(statement, frame, generateLambda)); - return joinTracedToNode(statement)(result, (stmt) => stmt, { - separator: NL, - })!; + const assignment = this.generateAssignment(statement, frame, generateLambda); + result.push(...frame.getExtraStatements(), assignment); } else if (isSdsExpressionStatement(statement)) { - for (const node of AstUtils.streamAllContents(statement.expression)) { - if (isSdsBlockLambda(node)) { - result.push(this.generateBlockLambda(node, frame)); - } else if (isSdsExpressionLambda(node)) { - result.push(this.generateExpressionLambda(node, frame)); - } - } - result.push(this.generateExpression(statement.expression, frame)); - return joinTracedToNode(statement)(result, (stmt) => stmt, { - separator: NL, - })!; - } - /* c8 ignore next 2 */ - throw new Error(`Unknown SdsStatement: ${statement}`); + const expressionStatement = this.generateExpression(statement.expression, frame); + result.push(...frame.getExtraStatements(), expressionStatement); + } /* c8 ignore start */ else { + throw new Error(`Unknown statement: ${statement}`); + } /* c8 ignore stop */ + + frame.resetExtraStatements(); + return joinTracedToNode(statement)(result, { + separator: NL, + }); } private generateAssignment( @@ -656,7 +645,8 @@ export class SafeDsPythonGenerator { )}`, ); } - return expandTracedToNode(blockLambda)`def ${frame.getUniqueLambdaName( + + const extraStatement = expandTracedToNode(blockLambda)`def ${frame.getUniqueLambdaName( blockLambda, )}(${this.generateParameters(blockLambda.parameterList, frame)}):` .appendNewLine() @@ -664,6 +654,9 @@ export class SafeDsPythonGenerator { indentedChildren: [lambdaBlock], indentation: PYTHON_INDENT, }); + frame.addExtraStatement(blockLambda, extraStatement); + + return traceToNode(blockLambda)(frame.getUniqueLambdaName(blockLambda)); } private generateExpressionLambda(node: SdsExpressionLambda, frame: GenerationInfoFrame): Generated { @@ -671,13 +664,20 @@ export class SafeDsPythonGenerator { const parameters = this.generateParameters(node.parameterList, frame); const result = this.generateExpression(node.result, frame); - return expandTracedToNode(node)` + const extraStatement = expandTracedToNode(node)` def ${name}(${parameters}): return ${result} `; + frame.addExtraStatement(node, extraStatement); + + return traceToNode(node)(name); } - private generateExpression(expression: SdsExpression, frame: GenerationInfoFrame): Generated { + private generateExpression( + expression: SdsExpression, + frame: GenerationInfoFrame, + thisParam?: Generated, + ): Generated { if (isSdsTemplateStringPart(expression)) { if (isSdsTemplateStringStart(expression)) { return expandTracedToNode(expression)`${this.formatStringSingleLine(expression.value)}{ `; @@ -731,7 +731,7 @@ export class SafeDsPythonGenerator { { separator: ', ' }, )}]`; } else if (isSdsBlockLambda(expression)) { - return traceToNode(expression)(frame.getUniqueLambdaName(expression)); + return this.generateBlockLambda(expression, frame); } else if (isSdsCall(expression)) { const callable = this.nodeMapper.callToCallable(expression); const receiver = this.generateExpression(expression.receiver, frame); @@ -742,20 +742,20 @@ export class SafeDsPythonGenerator { if (isSdsFunction(callable)) { const pythonCall = this.builtinAnnotations.getPythonCall(callable); if (pythonCall) { - let thisParam: Generated | undefined = undefined; + let newReceiver: SdsExpression | undefined = undefined; if (isSdsMemberAccess(expression.receiver)) { - thisParam = this.generateExpression(expression.receiver.receiver, frame); + newReceiver = expression.receiver.receiver; } const argumentsMap = this.getArgumentsMap(getArguments(expression), frame); - call = this.generatePythonCall(expression, pythonCall, argumentsMap, frame, thisParam); + call = this.generatePythonCall(expression, pythonCall, argumentsMap, frame, newReceiver); } } if (!call && this.isMemoizableCall(expression) && !frame.disableRunnerIntegration) { - let thisParam: Generated | undefined = undefined; + let newReceiver: SdsExpression | undefined = undefined; if (isSdsMemberAccess(expression.receiver)) { - thisParam = this.generateExpression(expression.receiver.receiver, frame); + newReceiver = expression.receiver.receiver; } - call = this.generateMemoizedCall(expression, frame, thisParam); + call = this.generateMemoizedCall(expression, frame, newReceiver); } } @@ -773,7 +773,7 @@ export class SafeDsPythonGenerator { return call; } } else if (isSdsExpressionLambda(expression)) { - return traceToNode(expression)(frame.getUniqueLambdaName(expression)); + return this.generateExpressionLambda(expression, frame); } else if (isSdsInfixOperation(expression)) { const leftOperand = this.generateExpression(expression.leftOperand, frame); const rightOperand = this.generateExpression(expression.rightOperand, frame); @@ -871,6 +871,8 @@ export class SafeDsPythonGenerator { const referenceImport = this.createImportDataForReference(expression); frame.addImport(referenceImport); return traceToNode(expression)(referenceImport?.alias ?? this.getPythonNameOrDefault(declaration)); + } else if (isSdsThis(expression)) { + return thisParam; } else if (isSdsTypeCast(expression)) { return traceToNode(expression)(this.generateExpression(expression.expression, frame)); } @@ -892,9 +894,17 @@ export class SafeDsPythonGenerator { pythonCall: string, argumentsMap: Map, frame: GenerationInfoFrame, - thisParam: Generated | undefined = undefined, + receiver: SdsExpression | undefined, ): Generated { - if (thisParam) { + let thisParam: Generated | undefined = undefined; + + if (receiver) { + thisParam = frame.getUniqueReceiverName(receiver); + const extraStatement = expandTracedToNode(receiver)` + ${thisParam} = ${this.generateExpression(receiver, frame)} + `; + frame.addExtraStatement(receiver, extraStatement); + argumentsMap.set('this', thisParam); } const splitRegex = /(\$[_a-zA-Z][_a-zA-Z0-9]*)/gu; @@ -919,7 +929,7 @@ export class SafeDsPythonGenerator { const fullyQualifiedTargetName = this.generateFullyQualifiedFunctionName(expression); const hiddenParameters = this.getMemoizedCallHiddenParameters(expression, frame); - if (isSdsFunction(callable) && !isStatic(callable) && isSdsMemberAccess(expression.receiver)) { + if (isSdsFunction(callable) && !isStatic(callable) && isSdsMemberAccess(expression.receiver) && thisParam) { return expandTracedToNode(expression)` ${MEMOIZED_STATIC_CALL}( "${fullyQualifiedTargetName}", @@ -980,12 +990,12 @@ export class SafeDsPythonGenerator { private generateMemoizedCall( expression: SdsCall, frame: GenerationInfoFrame, - thisParam: Generated | undefined = undefined, + receiver: SdsExpression | undefined, ): Generated { const callable = this.nodeMapper.callToCallable(expression); - if (isSdsFunction(callable) && !isStatic(callable) && isSdsMemberAccess(expression.receiver)) { - return this.generateMemoizedDynamicCall(expression, callable, thisParam, frame); + if (isSdsFunction(callable) && !isStatic(callable) && isSdsExpression(receiver)) { + return this.generateMemoizedDynamicCall(expression, callable, receiver, frame); } else { return this.generateMemoizedStaticCall(expression, callable, frame); } @@ -994,19 +1004,24 @@ export class SafeDsPythonGenerator { private generateMemoizedDynamicCall( expression: SdsCall, callable: SdsFunction, - thisParam: Generated, + receiver: SdsExpression, frame: GenerationInfoFrame, ) { frame.addImport({ importPath: RUNNER_PACKAGE }); const hiddenParameters = this.getMemoizedCallHiddenParameters(expression, frame); + const thisParam = frame.getUniqueReceiverName(receiver); + const extraStatement = expandTracedToNode(receiver)` + ${thisParam} = ${this.generateExpression(receiver, frame)} + `; + frame.addExtraStatement(receiver, extraStatement); return expandTracedToNode(expression)` ${MEMOIZED_DYNAMIC_CALL}( ${thisParam}, "${this.getPythonNameOrDefault(callable)}", [${this.generateMemoizedPositionalArgumentList(expression, frame)}], - {${this.generateMemoizedKeywordArgumentList(expression, frame)}}, + {${this.generateMemoizedKeywordArgumentList(expression, frame, thisParam)}}, [${joinToNode(hiddenParameters, (param) => param, { separator: ', ' })}] ) `; @@ -1060,7 +1075,11 @@ export class SafeDsPythonGenerator { ); } - private generateMemoizedKeywordArgumentList(node: SdsCall, frame: GenerationInfoFrame): Generated { + private generateMemoizedKeywordArgumentList( + node: SdsCall, + frame: GenerationInfoFrame, + thisParam?: Generated, + ): Generated { const callable = this.nodeMapper.callToCallable(node); const parameters = getParameters(callable); const optionalParameters = getParameters(callable).filter(Parameter.isOptional); @@ -1070,7 +1089,7 @@ export class SafeDsPythonGenerator { optionalParameters, (parameter) => { const argument = parametersToArgument.get(parameter); - return expandToNode`"${this.getPythonNameOrDefault(parameter)}": ${this.generateMemoizedArgument(argument, parameter, frame)}`; + return expandToNode`"${this.getPythonNameOrDefault(parameter)}": ${this.generateMemoizedArgument(argument, parameter, frame, thisParam)}`; }, { separator: ', ', @@ -1082,6 +1101,7 @@ export class SafeDsPythonGenerator { argument: SdsArgument | undefined, parameter: SdsParameter, frame: GenerationInfoFrame, + thisParam?: Generated | undefined, ): Generated { const value = argument?.value ?? parameter?.defaultValue; if (!value) { @@ -1089,7 +1109,7 @@ export class SafeDsPythonGenerator { throw new Error(`No value passed for required parameter "${parameter.name}".`); } - const result = this.generateExpression(value, frame); + const result = this.generateExpression(value, frame, thisParam); if (!this.isMemoizedPath(parameter)) { return result; } @@ -1252,13 +1272,14 @@ interface ImportData { } class GenerationInfoFrame { - private readonly lambdaManager: IdManager; + private readonly idManager: IdManager; private readonly importSet: Map; private readonly utilitySet: Set; private readonly typeVariableSet: Set; public readonly isInsidePipeline: boolean; public readonly targetPlaceholder: string | undefined; public readonly disableRunnerIntegration: boolean; + private extraStatements = new Map(); constructor( importSet: Map = new Map(), @@ -1268,7 +1289,7 @@ class GenerationInfoFrame { targetPlaceholder: string | undefined = undefined, disableRunnerIntegration: boolean = false, ) { - this.lambdaManager = new IdManager(); + this.idManager = new IdManager(); this.importSet = importSet; this.utilitySet = utilitySet; this.typeVariableSet = typeVariableSet; @@ -1304,8 +1325,26 @@ class GenerationInfoFrame { } } + addExtraStatement(node: SdsExpression, statement: Generated): void { + if (!this.extraStatements.has(node)) { + this.extraStatements.set(node, statement); + } + } + + resetExtraStatements(): void { + this.extraStatements.clear(); + } + + getExtraStatements(): Generated[] { + return Array.from(this.extraStatements.values()); + } + getUniqueLambdaName(lambda: SdsLambda): string { - return `${LAMBDA_PREFIX}${this.lambdaManager.assignId(lambda)}`; + return `${LAMBDA_PREFIX}${this.idManager.assignId(lambda)}`; + } + + getUniqueReceiverName(receiver: SdsExpression): string { + return `${RECEIVER_PREFIX}${this.idManager.assignId(receiver)}`; } } 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 2b7e91e7c..29f411270 100644 --- a/packages/safe-ds-lang/src/language/grammar/safe-ds.langium +++ b/packages/safe-ds-lang/src/language/grammar/safe-ds.langium @@ -757,6 +757,7 @@ SdsPrimaryExpression returns SdsExpression: | SdsParenthesizedExpression | SdsReference | SdsTemplateString + | SdsThis ; interface SdsLiteral extends SdsExpression {} @@ -896,6 +897,12 @@ SdsTemplateStringEnd returns SdsExpression: value=TEMPLATE_STRING_END ; +interface SdsThis extends SdsExpression {} + +SdsThis returns SdsThis: + {SdsThis} 'this' +; + // ----------------------------------------------------------------------------- // Types diff --git a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts index c04dd6c01..0810c2af3 100644 --- a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts +++ b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts @@ -73,8 +73,8 @@ export class SafeDsCompletionProvider extends DefaultCompletionProvider { return true; } - private illegalKeywordsInPipelineFile = new Set(['annotation', 'class', 'enum', 'fun']); - private illegalKeywordsInStubFile = new Set(['pipeline', 'segment']); + private illegalKeywordsInModuleContextOfPipelineFile = new Set(['annotation', 'class', 'enum', 'fun', 'this']); + private illegalKeywordsInModuleContextOfStubFile = new Set(['pipeline', 'segment']); protected override filterKeyword(context: CompletionContext, keyword: Keyword): boolean { // Filter out keywords that do not contain any word character @@ -84,12 +84,15 @@ export class SafeDsCompletionProvider extends DefaultCompletionProvider { if ((!context.node || isSdsModule(context.node)) && !getPackageName(context.node)) { return keyword.value === 'package'; - } else if (isSdsModule(context.node) && isInPipelineFile(context.node)) { - return !this.illegalKeywordsInPipelineFile.has(keyword.value); + } else if (isInPipelineFile(context.node)) { + if (isSdsModule(context.node)) { + return !this.illegalKeywordsInModuleContextOfPipelineFile.has(keyword.value); + } else { + return keyword.value !== 'this'; + } } else if (isSdsModule(context.node) && isInStubFile(context.node)) { - return !this.illegalKeywordsInStubFile.has(keyword.value); + return !this.illegalKeywordsInModuleContextOfStubFile.has(keyword.value); } - return true; } 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 ce92880fa..5bda923ec 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 @@ -33,6 +33,7 @@ import { isSdsTemplateStringEnd, isSdsTemplateStringInner, isSdsTemplateStringStart, + isSdsThis, isSdsTypeCast, isSdsUnknown, type SdsArgument, @@ -213,7 +214,7 @@ export class SafeDsPartialEvaluator { return new StringConstant(node.value); } else if (isSdsTemplateStringEnd(node)) { return new StringConstant(node.value); - } else if (isSdsUnknown(node)) { + } else if (isSdsThis(node) || isSdsUnknown(node)) { return UnknownEvaluatedNode; } else if (isSdsBlockLambda(node)) { return new BlockLambdaClosure(node, substitutions); 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 2964267b9..acde31016 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 @@ -11,6 +11,7 @@ import { isSdsCallable, isSdsCallableType, isSdsClass, + isSdsClassMember, isSdsDeclaration, isSdsEnum, isSdsEnumVariant, @@ -36,6 +37,7 @@ import { isSdsSchema, isSdsSegment, isSdsTemplateString, + isSdsThis, isSdsType, isSdsTypeArgument, isSdsTypeCast, @@ -64,6 +66,7 @@ import { SdsPrefixOperation, SdsReference, SdsSegment, + SdsThis, SdsType, SdsTypeArgument, SdsTypeParameter, @@ -77,6 +80,7 @@ import { getResults, getTypeArguments, getTypeParameters, + isStatic, streamBlockLambdaResults, TypeParameter, } from '../helpers/nodeProperties.js'; @@ -364,27 +368,7 @@ export class SafeDsTypeComputer { } // Terminal cases - if (isSdsList(node)) { - const elementType = this.lowestCommonSupertype( - node.elements.map((it) => this.computeTypeWithRecursionCheck(it, state)), - ); - return this.coreTypes.List(elementType); - } else if (isSdsMap(node)) { - let keyType = this.lowestCommonSupertype( - node.entries.map((it) => this.computeTypeWithRecursionCheck(it.key, state)), - ); - - // Keeping literal types for keys is too strict: We would otherwise infer the key type of `{"a": 1, "b": 2}` - // as `Literal<"a", "b">`. But then we would be unable to pass an unknown `String` as the key in an indexed - // access. Where possible, we already validate the existence of keys in indexed accesses using the partial - // evaluator. - keyType = this.computeClassTypeForLiteralType(keyType); - - const valueType = this.lowestCommonSupertype( - node.entries.map((it) => this.computeTypeWithRecursionCheck(it.value, state)), - ); - return this.coreTypes.Map(keyType, valueType); - } else if (isSdsTemplateString(node)) { + if (isSdsTemplateString(node)) { return this.coreTypes.String; } else if (isSdsUnknown(node)) { return this.coreTypes.Nothing; @@ -438,6 +422,26 @@ export class SafeDsTypeComputer { default: return UnknownType; } + } else if (isSdsList(node)) { + const elementType = this.lowestCommonSupertype( + node.elements.map((it) => this.computeTypeWithRecursionCheck(it, state)), + ); + return this.coreTypes.List(elementType); + } else if (isSdsMap(node)) { + let keyType = this.lowestCommonSupertype( + node.entries.map((it) => this.computeTypeWithRecursionCheck(it.key, state)), + ); + + // Keeping literal types for keys is too strict: We would otherwise infer the key type of `{"a": 1, "b": 2}` + // as `Literal<"a", "b">`. But then we would be unable to pass an unknown `String` as the key in an indexed + // access. Where possible, we already validate the existence of keys in indexed accesses using the partial + // evaluator. + keyType = this.computeClassTypeForLiteralType(keyType); + + const valueType = this.lowestCommonSupertype( + node.entries.map((it) => this.computeTypeWithRecursionCheck(it.value, state)), + ); + return this.coreTypes.Map(keyType, valueType); } else if (isSdsMemberAccess(node)) { return this.computeTypeOfMemberAccess(node, state); } else if (isSdsParenthesizedExpression(node)) { @@ -456,6 +460,8 @@ export class SafeDsTypeComputer { } } else if (isSdsReference(node)) { return this.computeTypeOfReference(node, state); + } else if (isSdsThis(node)) { + return this.computeTypeOfThis(node, state); } /* c8 ignore start */ else { return UnknownType; } /* c8 ignore stop */ @@ -629,6 +635,23 @@ export class SafeDsTypeComputer { } } + private computeTypeOfThis(node: SdsThis, state: ComputeTypeState): Type { + // If closest callable is a class, return the class type + const containingCallable = AstUtils.getContainerOfType(node, isSdsCallable); + if (isSdsClass(containingCallable)) { + return this.computeTypeWithRecursionCheck(containingCallable, state); + } + + // Invalid if the callable is not a class member or static + if (!isSdsClassMember(containingCallable) || isStatic(containingCallable)) { + return UnknownType; + } + + // Otherwise, return the type of the containing class or unknown if not in a class + const containingClass = AstUtils.getContainerOfType(containingCallable, isSdsClass); + return this.computeTypeWithRecursionCheck(containingClass, state); + } + private computeTypeOfType(node: SdsType, state: ComputeTypeState): Type { if (isSdsCallableType(node)) { return this.computeTypeOfCallableWithManifestTypes(node, state); diff --git a/packages/safe-ds-lang/src/language/validation/other/expressions/this.ts b/packages/safe-ds-lang/src/language/validation/other/expressions/this.ts new file mode 100644 index 000000000..18c146fc7 --- /dev/null +++ b/packages/safe-ds-lang/src/language/validation/other/expressions/this.ts @@ -0,0 +1,20 @@ +import { SdsThis } from '../../../generated/ast.js'; +import { ValidationAcceptor } from 'langium'; +import { SafeDsServices } from '../../../safe-ds-module.js'; +import { UnknownType } from '../../../typing/model.js'; + +export const CODE_THIS_USAGE = 'this/usage'; + +export const thisMustReferToClassInstance = (services: SafeDsServices) => { + const typeComputer = services.typing.TypeComputer; + + return (node: SdsThis, accept: ValidationAcceptor): void => { + const type = typeComputer.computeType(node); + if (type === UnknownType) { + accept('error', `The keyword 'this' must refer to a class instance.`, { + node, + code: CODE_THIS_USAGE, + }); + } + }; +}; 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 cb9434a91..bf919ffa4 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 @@ -188,6 +188,7 @@ import { unknownMustOnlyBeUsedAsDefaultValueOfStub } from './other/expressions/l import { tagsShouldNotHaveDuplicateEntries } from './builtins/tags.js'; import { moduleMemberShouldBeUsed } from './other/declarations/moduleMembers.js'; import { pipelinesMustBePrivate } from './other/declarations/pipelines.js'; +import { thisMustReferToClassInstance } from './other/expressions/this.js'; /** * Register custom validation checks. @@ -369,6 +370,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { ], SdsStatement: [statementMustDoSomething(services)], SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts], + SdsThis: [thisMustReferToClassInstance(services)], SdsTypeCast: [typeCastMustNotAlwaysFail(services)], SdsTypeParameter: [ typeParameterDefaultValueMustMatchUpperBound(services), diff --git a/packages/safe-ds-lang/tests/language/generation/safe-ds-markdown-generator/safe-ds-markdown-generator.test.ts b/packages/safe-ds-lang/tests/language/generation/safe-ds-markdown-generator/safe-ds-markdown-generator.test.ts index 4d63ff0d7..ab8c14c4a 100644 --- a/packages/safe-ds-lang/tests/language/generation/safe-ds-markdown-generator/safe-ds-markdown-generator.test.ts +++ b/packages/safe-ds-lang/tests/language/generation/safe-ds-markdown-generator/safe-ds-markdown-generator.test.ts @@ -4,6 +4,7 @@ import { createMarkdownGenerationTests } from './creator.js'; import { loadDocuments } from '../../../helpers/testResources.js'; import { URI } from 'langium'; import { createSafeDsServices } from '../../../../src/language/index.js'; +import { normalizeEOL } from 'langium/generate'; const services = (await createSafeDsServices(NodeFileSystem)).SafeDs; const markdownGenerator = services.generation.MarkdownGenerator; @@ -32,7 +33,7 @@ describe('generation', async () => { // File contents must match for (const [uriString, code] of actualOutputs) { const fsPath = URI.parse(uriString).fsPath; - await expect(code).toMatchFileSnapshot(fsPath); + await expect(normalizeEOL(code)).toMatchFileSnapshot(fsPath); } // File paths must match diff --git a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts index a2261f7ab..fe47838cf 100644 --- a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts +++ b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts @@ -71,6 +71,17 @@ describe('SafeDsCompletionProvider', async () => { shouldEqual: ['static', 'attr', 'class', 'enum', 'fun'], }, }, + { + testName: 'in pipeline', + uri: `file:///test4.sds`, + code: ` + pipeline myPipeline { + <|> + `, + expectedLabels: { + shouldNotContain: ['this'], + }, + }, // Cross-references { diff --git a/packages/safe-ds-lang/tests/resources/formatting/expressions/this.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/expressions/this.sdsdev new file mode 100644 index 000000000..a7c9185d0 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/formatting/expressions/this.sdsdev @@ -0,0 +1,9 @@ +pipeline myPipeline { + this; +} + +// ----------------------------------------------------------------------------- + +pipeline myPipeline { + this; +} diff --git a/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py b/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py index 020e2e539..c4694ff4b 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py +++ b/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py @@ -22,7 +22,8 @@ def test(): f(C().c) f(__gen_null_safe_member_access(factory(), 'a')) f(__gen_null_safe_member_access(factory(), 'c')) - f(1.i(C())) + __gen_receiver_0 = C() + f(1.i(__gen_receiver_0)) f(C().j(123)) f(C().k2('abc')) f(C.from_csv_file('abc.csv')) diff --git a/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py.map b/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py.map index eeecdf253..42c618f95 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py.map +++ b/packages/safe-ds-lang/tests/resources/generation/python/expressions/member access/generated/tests/generator/memberAccess/gen_input.py.map @@ -1 +1 @@ -{"version":3,"sources":["input.sdsdev"],"names":["test","f","g","h","result1","result2","c","a","b","factory","j","k","from_csv_file"],"mappings":"AAAA;;;;;;;;;;;;;;;;AA4BA,IAASA,IAAI;IACTC,CAAC,CAACC,CAAC;IACHD,CAAC,CAACE,CAAC,GAAGC,CAAO;IACbH,CAAC,CAACE,CAAC,GAAGE,CAAO;IACbJ,CAAC,CAACK,CAAC,GAAGC,CAAC;IACPN,CAAC,CAACK,CAAC,GAAGE,CAAC;IACPP,CAAC,CAAU,6BAAC,CAAVQ,OAAO,KAAIF,CAAC;IACdN,CAAC,CAAU,6BAAC,CAAVQ,OAAO,KAAID,CAAC;IACdP,CAAC,CAAO,CAAC,GAAPK,CAAC;IACHL,CAAC,CAACK,CAAC,GAAGI,CAAC,CAAC,GAAG;IACXT,CAAC,CAACK,CAAC,GAAGK,EAAC,CAAC,KAAK;IACbV,CAAC,CAACK,CAAC,CAACM,aAAa,CAAC,SAAS","file":"gen_input.py"} \ No newline at end of file +{"version":3,"sources":["input.sdsdev"],"names":["test","f","g","h","result1","result2","c","a","b","factory","j","k","from_csv_file"],"mappings":"AAAA;;;;;;;;;;;;;;;;AA4BA,IAASA,IAAI;IACTC,CAAC,CAACC,CAAC;IACHD,CAAC,CAACE,CAAC,GAAGC,CAAO;IACbH,CAAC,CAACE,CAAC,GAAGE,CAAO;IACbJ,CAAC,CAACK,CAAC,GAAGC,CAAC;IACPN,CAAC,CAACK,CAAC,GAAGE,CAAC;IACPP,CAAC,CAAU,6BAAC,CAAVQ,OAAO,KAAIF,CAAC;IACdN,CAAC,CAAU,6BAAC,CAAVQ,OAAO,KAAID,CAAC;IACZ,mBAAAF,CAAC;IAAHL,CAAC,CAAO,CAAC;IACTA,CAAC,CAACK,CAAC,GAAGI,CAAC,CAAC,GAAG;IACXT,CAAC,CAACK,CAAC,GAAGK,EAAC,CAAC,KAAK;IACbV,CAAC,CAACK,CAAC,CAACM,aAAa,CAAC,SAAS","file":"gen_input.py"} \ No newline at end of file diff --git a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py index 651358ce1..6cbaad10c 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py +++ b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py @@ -67,22 +67,17 @@ def test(): {}, [] ), 'c')) + __gen_receiver_0 = safeds_runner.memoized_static_call( + "tests.generator.memberAccessWithRunnerIntegration.C", + C, + [], + {}, + [] + ) f(safeds_runner.memoized_static_call( "tests.generator.memberAccessWithRunnerIntegration.C.i", - lambda *_ : 1.i(safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C", - C, - [], - {}, - [] - )), - [safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C", - C, - [], - {}, - [] - ), 1], + lambda *_ : 1.i(__gen_receiver_0), + [__gen_receiver_0, 1], {}, [] )) @@ -94,39 +89,43 @@ def test(): [] ) safeds_runner.save_placeholder('c1', c1) + __gen_receiver_1 = c1 f(safeds_runner.memoized_static_call( "tests.generator.memberAccessWithRunnerIntegration.C.i", - lambda *_ : 1.i(c1), - [c1, 1], + lambda *_ : 1.i(__gen_receiver_1), + [__gen_receiver_1, 1], {}, [] )) + __gen_receiver_2 = safeds_runner.memoized_static_call( + "tests.generator.memberAccessWithRunnerIntegration.C", + C, + [], + {}, + [] + ) f(safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C", - C, - [], - {}, - [] - ), + __gen_receiver_2, "j", [123], {}, [] )) + __gen_receiver_3 = safeds_runner.memoized_static_call( + "tests.generator.memberAccessWithRunnerIntegration.C", + C, + [], + {}, + [] + ) f(safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C", - C, - [], - {}, - [] - ), + __gen_receiver_3, "k2", ['abc'], {}, [] )) + __gen_receiver_4 = C f(safeds_runner.memoized_static_call( "tests.generator.memberAccessWithRunnerIntegration.C.l", lambda *_ : 2.i(), @@ -134,21 +133,23 @@ def test(): {}, [] )) + __gen_receiver_5 = safeds_runner.memoized_static_call( + "tests.generator.memberAccessWithRunnerIntegration.C", + C, + [], + {}, + [] + ) f(safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C", - C, - [], - {}, - [] - ), + __gen_receiver_5, "m", [], {"param": 213}, [] )) + __gen_receiver_6 = c1 f(safeds_runner.memoized_dynamic_call( - c1, + __gen_receiver_6, "m", [], {"param": 213}, @@ -161,40 +162,43 @@ def test(): {"param": 213}, [] )) + __gen_receiver_7 = safeds_runner.memoized_static_call( + "tests.generator.memberAccessWithRunnerIntegration.C.o", + C.o, + [], + {"param": 42}, + [] + ) f(safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C.o", - C.o, - [], - {"param": 42}, - [] - ), + __gen_receiver_7, "m", [], {"param": 213}, [] )) + __gen_receiver_8 = safeds_runner.memoized_static_call( + "tests.generator.memberAccessWithRunnerIntegration.C.o", + C.o, + [], + {"param": 42}, + [] + ) f(safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C.o", - C.o, - [], - {"param": 42}, - [] - ), + __gen_receiver_8, "j", [213], {}, [] )) + __gen_receiver_9 = safeds_runner.memoized_static_call( + "tests.generator.memberAccessWithRunnerIntegration.C.p", + C.p, + [], + {}, + [] + ) f(safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "tests.generator.memberAccessWithRunnerIntegration.C.p", - C.p, - [], - {}, - [] - ), + __gen_receiver_9, "j", [213], {}, @@ -215,90 +219,102 @@ def test(): [safeds_runner.file_mtime('abc.csv')] ) safeds_runner.save_placeholder('a', a) + __gen_receiver_10 = safeds_runner.memoized_static_call( + "safeds.data.tabular.containers.Table.from_csv_file", + Table.from_csv_file, + [safeds_runner.absolute_path('abc.csv')], + {}, + [safeds_runner.file_mtime('abc.csv')] + ) a2 = safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "safeds.data.tabular.containers.Table.from_csv_file", - Table.from_csv_file, - [safeds_runner.absolute_path('abc.csv')], - {}, - [safeds_runner.file_mtime('abc.csv')] - ), + __gen_receiver_10, "remove_columns", [['u']], {}, [] ) safeds_runner.save_placeholder('a2', a2) + __gen_receiver_11 = a v = safeds_runner.memoized_dynamic_call( - a, + __gen_receiver_11, "get_column", ['b'], {}, [] ) safeds_runner.save_placeholder('v', v) + __gen_receiver_12 = v d = safeds_runner.memoized_dynamic_call( - v, + __gen_receiver_12, "plot_histogram", [], {}, [] ) safeds_runner.save_placeholder('d', d) + __gen_receiver_13 = a + __gen_receiver_14 = safeds_runner.memoized_dynamic_call( + __gen_receiver_13, + "get_column", + ['b'], + {}, + [] + ) p = safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_dynamic_call( - a, - "get_column", - ['b'], - {}, - [] - ), + __gen_receiver_14, "plot_histogram", [], {}, [] ) safeds_runner.save_placeholder('p', p) + __gen_receiver_15 = a + __gen_receiver_16 = safeds_runner.memoized_dynamic_call( + __gen_receiver_15, + "get_column", + ['b'], + {}, + [] + ) + __gen_receiver_17 = safeds_runner.memoized_dynamic_call( + __gen_receiver_16, + "plot_histogram", + [], + {}, + [] + ) r = safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_dynamic_call( - a, - "get_column", - ['b'], - {}, - [] - ), - "plot_histogram", - [], - {}, - [] - ), + __gen_receiver_17, "flip_vertically", [], {}, [] ) safeds_runner.save_placeholder('r', r) + __gen_receiver_18 = a + __gen_receiver_19 = safeds_runner.memoized_dynamic_call( + __gen_receiver_18, + "get_column", + ['b'], + {}, + [] + ) + __gen_receiver_20 = safeds_runner.memoized_dynamic_call( + __gen_receiver_19, + "plot_histogram", + [], + {}, + [] + ) + __gen_receiver_21 = safeds_runner.memoized_dynamic_call( + __gen_receiver_20, + "flip_vertically", + [], + {}, + [] + ) q = safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_dynamic_call( - a, - "get_column", - ['b'], - {}, - [] - ), - "plot_histogram", - [], - {}, - [] - ), - "flip_vertically", - [], - {}, - [] - ), + __gen_receiver_21, "adjust_contrast", [1.2], {}, @@ -324,8 +340,9 @@ def test(): [] ) safeds_runner.save_placeholder('nestedInstance', nestedInstance) + __gen_receiver_22 = nestedInstance nestedResult = safeds_runner.memoized_dynamic_call( - nestedInstance, + __gen_receiver_22, "g", [], {}, @@ -333,22 +350,24 @@ def test(): ) safeds_runner.save_placeholder('nestedResult', nestedResult) f(nestedResult) + __gen_receiver_23 = safeds_runner.memoized_static_call( + "safeds.data.tabular.transformation.OneHotEncoder", + OneHotEncoder, + [], + {}, + [] + ) encoder = safeds_runner.memoized_dynamic_call( - safeds_runner.memoized_static_call( - "safeds.data.tabular.transformation.OneHotEncoder", - OneHotEncoder, - [], - {}, - [] - ), + __gen_receiver_23, "fit", [a, ['b']], {}, [] ) safeds_runner.save_placeholder('encoder', encoder) + __gen_receiver_24 = encoder transformedTable = safeds_runner.memoized_dynamic_call( - encoder, + __gen_receiver_24, "transform", [a], {}, diff --git a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py.map b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py.map index 0cabe0027..b55084a1c 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py.map +++ b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/member access/generated/tests/generator/memberAccessWithRunnerIntegration/gen_input.py.map @@ -1 +1 @@ -{"version":3,"sources":["input.sdsdev"],"names":["test","f","g","h","result1","result2","c","a","b","factory","c1","v","d","p","r","q","factorynested","nestedinstance","nestedresult","onehotencoder","encoder"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;AAwDA,IAASA,IAAI;IACTC,CAAC,CAAC;;QAAAC,CAAC;;;;;IACHD,CAAC,CAAC;;QAAAE,CAAC;;;;MAAGC,CAAO;IACbH,CAAC,CAAC;;QAAAE,CAAC;;;;MAAGE,CAAO;IACbJ,CAAC,CAAC;;QAAAK,CAAC;;;;MAAGC,CAAC;IACPN,CAAC,CAAC;;QAAAK,CAAC;;;;MAAGE,CAAC;IACPP,CAAC,CAAU,6BAAC,CAAV;;QAAAQ,OAAO;;;;QAAIF,CAAC;IACdN,CAAC,CAAU,6BAAC,CAAV;;QAAAQ,OAAO;;;;QAAID,CAAC;IACdP,CAAC,CAAC;;oBAAM,CAAC,GAAP;;YAAAK,CAAC;;;;;SAAD;;YAAAA,CAAC;;;;WAAK,CAAC;;;;IACT,KAAS;;QAAAA,CAAC;;;;;IAAV;IACAL,CAAC,CAAC;;oBAAK,CAAC,GAANS,EAAE;SAAFA,EAAE,EAAG,CAAC;;;;IACRT,CAAC,CAAC;QAAA;;YAAAK,CAAC;;;;;;SAAK,GAAG;;;;IACXL,CAAC,CAAC;QAAA;;YAAAK,CAAC;;;;;;SAAK,KAAK;;;;IACbL,CAAC,CAAC;;oBAAI,CAAC;SAAD,CAAC;;;;IACPA,CAAC,CAAC;QAAA;;YAAAK,CAAC;;;;;;;SAAK,SAAA,GAAG;;;IACXL,CAAC,CAAC;QAAAS,EAAE;;;SAAG,SAAA,GAAG;;;IACVT,CAAC,CAAC;;;;SAAI,SAAA,GAAG;;;IACTA,CAAC,CAAC;QAAG;;;;sBAxCqB,EAAE;;;;;SAwClB,SAAA,GAAG;;;IACbA,CAAC,CAAC;QAAG;;;;sBAzCqB,EAAE;;;;SAyClB,GAAG;;;;IACbA,CAAC,CAAC;QAAG;;;;;;;;SAAK,GAAG;;;;IACbA,CAAC,CAAC;;;SAAgB,4BAAA,SAAS;;SAAT,yBAAA,SAAS;;IAC3B,IAAQ;;;SAAkB,4BAAA,SAAS;;SAAT,yBAAA,SAAS;;IAAnC;IACA,KAAS;QAAiB;;;aAAC,4BAAA,SAAS;;aAAT,yBAAA,SAAS;;;SAAgB,CAAC,GAAG;;;;IAAxD;IACA,IAAQ;QAAAM,CAAC;;SAAW,GAAG;;;;IAAvB;IACA,IAAQ;QAAAI,CAAC;;;;;;IAAT;IACA,IAAQ;QAAW;YAAXJ,CAAC;;aAAW,GAAG;;;;;;;;;IAAvB;IACA,IAAQ;QAA8B;YAAnB;gBAAXA,CAAC;;iBAAW,GAAG;;;;;;;;;;;;;;IAAvB;IACA,IAAQ;QAA+C;YAAjB;gBAAnB;oBAAXA,CAAC;;qBAAW,GAAG;;;;;;;;;;;;;;;SAAkD,GAAG;;;;IAA5E;IACAN,CAAC,CAACW,CAAC;IACHX,CAAC,CAACY,CAAC;IACHZ,CAAC,CAACa,CAAC;IACHb,CAAC,CAACc,CAAC;IACHd,CAAC,CAAC;;;;;;;IACF,iBAAqB;;QAAAe,aAAa;;;;;IAAlC;IACA,eAAmB;QAAAC,cAAc;;;;;;IAAjC;IACAhB,CAAC,CAACiB,YAAY;IACd,UAAc;QAAA;;YAAAC,aAAa;;;;;;SAAOZ,CAAC,EAAE,CAAC,GAAG;;;;IAAzC;IACA,mBAAuB;QAAAa,OAAO;;SAAWb,CAAC;;;;IAA1C","file":"gen_input.py"} \ No newline at end of file +{"version":3,"sources":["input.sdsdev"],"names":["test","f","g","h","result1","result2","c","a","b","factory","c1","v","d","p","r","q","factorynested","nestedinstance","nestedresult","onehotencoder","encoder"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;AAwDA,IAASA,IAAI;IACTC,CAAC,CAAC;;QAAAC,CAAC;;;;;IACHD,CAAC,CAAC;;QAAAE,CAAC;;;;MAAGC,CAAO;IACbH,CAAC,CAAC;;QAAAE,CAAC;;;;MAAGE,CAAO;IACbJ,CAAC,CAAC;;QAAAK,CAAC;;;;MAAGC,CAAC;IACPN,CAAC,CAAC;;QAAAK,CAAC;;;;MAAGE,CAAC;IACPP,CAAC,CAAU,6BAAC,CAAV;;QAAAQ,OAAO;;;;QAAIF,CAAC;IACdN,CAAC,CAAU,6BAAC,CAAV;;QAAAQ,OAAO;;;;QAAID,CAAC;IACZ,mBAAA;;QAAAF,CAAC;;;;;IAAHL,CAAC,CAAC;;oBAAM,CAAC;2BAAD,CAAC;;;;IACT,KAAS;;QAAAK,CAAC;;;;;IAAV;IACEI,mBAAAA,EAAE;IAAJT,CAAC,CAAC;;oBAAK,CAAC;2BAAD,CAAC;;;;IACN,mBAAA;;QAAAK,CAAC;;;;;IAAHL,CAAC,CAAC;;;SAAM,GAAG;;;;IACT,mBAAA;;QAAAK,CAAC;;;;;IAAHL,CAAC,CAAC;;;SAAM,KAAK;;;;IACXK,mBAAAA,CAAC;IAAHL,CAAC,CAAC;;oBAAI,CAAC;SAAD,CAAC;;;;IACL,mBAAA;;QAAAK,CAAC;;;;;IAAHL,CAAC,CAAC;;;;SAAM,SAAA,GAAG;;;IACTS,mBAAAA,EAAE;IAAJT,CAAC,CAAC;;;;SAAK,SAAA,GAAG;;;IACVA,CAAC,CAAC;;;;SAAI,SAAA,GAAG;;;IACJ,mBAAA;;;;kBAxCqB,EAAE;;;IAwC5BA,CAAC,CAAC;;;;SAAQ,SAAA,GAAG;;;IACR,mBAAA;;;;kBAzCqB,EAAE;;;IAyC5BA,CAAC,CAAC;;;SAAQ,GAAG;;;;IACR,mBAAA;;;;;;;IAALA,CAAC,CAAC;;;SAAQ,GAAG;;;;IACbA,CAAC,CAAC;;;SAAgB,4BAAA,SAAS;;SAAT,yBAAA,SAAS;;IAC3B,IAAQ;;;SAAkB,4BAAA,SAAS;;SAAT,yBAAA,SAAS;;IAAnC;IAC0B,oBAAA;;;SAAC,4BAAA,SAAS;;SAAT,yBAAA,SAAS;;IAApC,KAAS;;;SAA2C,CAAC,GAAG;;;;IAAxD;IACQM,oBAAAA,CAAC;IAAT,IAAQ;;;SAAY,GAAG;;;;IAAvB;IACQI,oBAAAA,CAAC;IAAT,IAAQ;;;;;;;IAAR;IACQJ,oBAAAA,CAAC;IAAU,oBAAA;;;SAAC,GAAG;;;;IAAvB,IAAQ;;;;;;;IAAR;IACQA,oBAAAA,CAAC;IAAU,oBAAA;;;SAAC,GAAG;;;;IAAe,oBAAA;;;;;;;IAAtC,IAAQ;;;;;;;IAAR;IACQA,oBAAAA,CAAC;IAAU,oBAAA;;;SAAC,GAAG;;;;IAAe,oBAAA;;;;;;;IAAiB,oBAAA;;;;;;;IAAvD,IAAQ;;;SAAiE,GAAG;;;;IAA5E;IACAN,CAAC,CAACW,CAAC;IACHX,CAAC,CAACY,CAAC;IACHZ,CAAC,CAACa,CAAC;IACHb,CAAC,CAACc,CAAC;IACHd,CAAC,CAAC;;;;;;;IACF,iBAAqB;;QAAAe,aAAa;;;;;IAAlC;IACmBC,oBAAAA,cAAc;IAAjC,eAAmB;;;;;;;IAAnB;IACAhB,CAAC,CAACiB,YAAY;IACA,oBAAA;;QAAAC,aAAa;;;;;IAA3B,UAAc;;;SAAoBZ,CAAC,EAAE,CAAC,GAAG;;;;IAAzC;IACuBa,oBAAAA,OAAO;IAA9B,mBAAuB;;;SAAkBb,CAAC;;;;IAA1C","file":"gen_input.py"} \ No newline at end of file diff --git a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input.py b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input.py new file mode 100644 index 000000000..1090e787e --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input.py @@ -0,0 +1,23 @@ +# Imports ---------------------------------------------------------------------- + +import safeds_runner +from tests.generator.runnerIntegration.expressions.this import MyClass + +# Pipelines -------------------------------------------------------------------- + +def myPipeline(): + __gen_receiver_0 = safeds_runner.memoized_static_call( + "tests.generator.runnerIntegration.expressions.this.MyClass", + MyClass, + [], + {}, + [] + ) + a = safeds_runner.memoized_dynamic_call( + __gen_receiver_0, + "myFunction", + [], + {"p": __gen_receiver_0}, + [] + ) + safeds_runner.save_placeholder('a', a) diff --git a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input.py.map b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input.py.map new file mode 100644 index 000000000..af51d051b --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input.py.map @@ -0,0 +1 @@ +{"version":3,"sources":["input.sdsdev"],"names":["mypipeline","myclass"],"mappings":"AAAA;;;;;;;AAOA,IAASA,UAAU;IACP,mBAAA;;QAAAC,OAAO;;;;;IAAf,IAAQ;;;;;;;IAAR","file":"gen_input.py"} \ No newline at end of file diff --git a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input_myPipeline.py b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input_myPipeline.py new file mode 100644 index 000000000..beb776a1f --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/generated/tests/generator/runnerIntegration/expressions/this/gen_input_myPipeline.py @@ -0,0 +1,4 @@ +from .gen_input import myPipeline + +if __name__ == '__main__': + myPipeline() diff --git a/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/input.sdsdev b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/input.sdsdev new file mode 100644 index 000000000..a589e52cf --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/generation/python/runner integration/expressions/this/input.sdsdev @@ -0,0 +1,10 @@ +package tests.generator.runnerIntegration.expressions.`this` + +class MyClass() { + @Pure + fun myFunction(p: MyClass = this) -> r: Int +} + +pipeline myPipeline { + val a = MyClass().myFunction(); +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/good-this.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/good-this.sdsdev new file mode 100644 index 000000000..888f41893 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/good-this.sdsdev @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +pipeline myPipeline { + this; +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/keywords as names/bad-unescaped this.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/bad-unescaped this.sdsdev new file mode 100644 index 000000000..2d4d6dc0d --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/bad-unescaped this.sdsdev @@ -0,0 +1,3 @@ +// $TEST$ syntax_error + +class this diff --git a/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdsdev index 74f373619..88fac7b79 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/keywords as names/good-escapedKeywords.sdsdev @@ -24,6 +24,7 @@ class `schema` class `static` class `segment` class `sub` +class `this` class `true` class `union` class `unknown` diff --git a/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/this/main.sdsdev b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/this/main.sdsdev new file mode 100644 index 000000000..ed5bcd249 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/this/main.sdsdev @@ -0,0 +1,6 @@ +package tests.partialValidation.baseCases.unknownLiterals + +pipeline test { + // $TEST$ serialization ? + »this«; +} diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/this/main.sdsdev b/packages/safe-ds-lang/tests/resources/typing/expressions/this/main.sdsdev new file mode 100644 index 000000000..80dd8f340 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/this/main.sdsdev @@ -0,0 +1,39 @@ +package tests.typing.expressions.`this` + +// $TEST$ serialization unknown +annotation MyAnnotation(p: Any? = »this«) + +// $TEST$ serialization MyClass +class MyClass(p: Any? = »this«) { + + // $TEST$ serialization MyClass + @Pure fun myInstanceMethod(p: Any? = »this«) + + // $TEST$ serialization unknown + @Pure static fun myStaticMethod(p: Any? = »this«) + + // $TEST$ serialization MyNestedClass + class MyNestedClass(p: Any? = »this«) { + + // $TEST$ serialization MyNestedClass + @Pure fun myInstanceMethod(p: Any? = »this«) + } + + enum MyNestedEnum { + // $TEST$ serialization unknown + MyEnumVariant(p: Any? = »this«) + } +} + +enum MyEnum { + // $TEST$ serialization unknown + MyEnumVariant(p: Any? = »this«) +} + +pipeline myPipeline { + // $TEST$ serialization unknown + »this«; +} + +// $TEST$ serialization unknown +segment mySegment(p: Any? = »this«) {} diff --git a/packages/safe-ds-lang/tests/resources/validation/builtins/annotations/pythonCall/main.sdsdev b/packages/safe-ds-lang/tests/resources/validation/builtins/annotations/pythonCall/main.sdsdev index f92a348d0..12229f767 100644 --- a/packages/safe-ds-lang/tests/resources/validation/builtins/annotations/pythonCall/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/validation/builtins/annotations/pythonCall/main.sdsdev @@ -7,7 +7,7 @@ class MyClass { // $TEST$ no error r"The template expressions? .* cannot be interpreted." @»PythonCall«("myMethod2($this)") - fun myMethod2(this: Int) + fun myMethod2(`this`: Int) // $TEST$ no error "The template expression '$this' cannot be interpreted." @»PythonCall«("myMethod3($this)") @@ -24,7 +24,7 @@ fun myFunction1(param: Int) // $TEST$ no error r"The template expressions? .* cannot be interpreted." @»PythonCall«("myFunction2($this)") -fun myFunction2(this: Int) +fun myFunction2(`this`: Int) // $TEST$ error "The template expression '$this' cannot be interpreted." @»PythonCall«("myFunction3($this)") diff --git a/packages/safe-ds-lang/tests/resources/validation/other/expressions/this/must refer to class instance/main.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/expressions/this/must refer to class instance/main.sdsdev new file mode 100644 index 000000000..969107294 --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/expressions/this/must refer to class instance/main.sdsdev @@ -0,0 +1,39 @@ +package tests.validation.other.expressions.`this`.mustReferToClassInstance + +// $TEST$ error "The keyword 'this' must refer to a class instance." +annotation MyAnnotation(p: Any? = »this«) + +// $TEST$ no error "The keyword 'this' must refer to a class instance." +class MyClass(p: Any? = »this«) { + + // $TEST$ no error "The keyword 'this' must refer to a class instance." + @Pure fun myInstanceMethod(p: Any? = »this«) + + // $TEST$ error "The keyword 'this' must refer to a class instance." + @Pure static fun myStaticMethod(p: Any? = »this«) + + // $TEST$ no error "The keyword 'this' must refer to a class instance." + class MyNestedClass(p: Any? = »this«) { + + // $TEST$ no error "The keyword 'this' must refer to a class instance." + @Pure fun myInstanceMethod(p: Any? = »this«) + } + + enum MyNestedEnum { + // $TEST$ error "The keyword 'this' must refer to a class instance." + MyEnumVariant(p: Any? = »this«) + } +} + +enum MyEnum { + // $TEST$ error "The keyword 'this' must refer to a class instance." + MyEnumVariant(p: Any? = »this«) +} + +pipeline myPipeline { + // $TEST$ error "The keyword 'this' must refer to a class instance." + »this«; +} + +// $TEST$ error "The keyword 'this' must refer to a class instance." +segment mySegment(p: Any? = »this«) {} diff --git a/packages/safe-ds-vscode/syntaxes/safe-ds-dev.tmLanguage.json b/packages/safe-ds-vscode/syntaxes/safe-ds-dev.tmLanguage.json index 12f3dfe92..98c81c34a 100644 --- a/packages/safe-ds-vscode/syntaxes/safe-ds-dev.tmLanguage.json +++ b/packages/safe-ds-vscode/syntaxes/safe-ds-dev.tmLanguage.json @@ -17,6 +17,10 @@ { "name": "keyword.operator.expression.safe-ds-dev", "match": "\\b(and|not|or|sub)\\b" + }, + { + "name": "variable.language.safe-ds-dev", + "match": "\\b(this)\\b" } ] } 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 d243ad3af..2e5100b2f 100644 --- a/packages/safe-ds-vscode/syntaxes/safe-ds-stub.tmLanguage.json +++ b/packages/safe-ds-vscode/syntaxes/safe-ds-stub.tmLanguage.json @@ -17,6 +17,10 @@ { "name": "keyword.operator.expression.safe-ds-stub", "match": "\\b(and|not|or|sub)\\b" + }, + { + "name": "variable.language.safe-ds-dev", + "match": "\\b(this)\\b" } ] }