Skip to content

Commit c52b5e6

Browse files
WinPlay02lars-reimannmegalinter-bot
authored
feat: generation (#634)
Closes partially #542 ### Summary of Changes - added an implementation of the generator - enabled tests for the generator (and removed the dummy test) - fixed an error related to constant expressions of integers being interpreted as floats - fixed an error where test files were read in an incorrect order - added a generation test for lists and maps - removed varargs from existing tests --------- Co-authored-by: Lars Reimann <[email protected]> Co-authored-by: megalinter-bot <[email protected]>
1 parent fe0c8d5 commit c52b5e6

File tree

143 files changed

+1026
-304
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+1026
-304
lines changed

src/cli/generator.ts

Lines changed: 579 additions & 16 deletions
Large diffs are not rendered by default.

src/language/validation/names.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,26 @@ import { isInPipelineFile, isInStubFile, isInTestFile } from '../helpers/fileExt
3636
import { declarationIsAllowedInPipelineFile, declarationIsAllowedInStubFile } from './other/modules.js';
3737
import { SafeDsServices } from '../safe-ds-module.js';
3838
import { listBuiltinFiles } from '../builtins/fileFinder.js';
39+
import { CODEGEN_PREFIX } from '../../cli/generator.js';
3940

40-
export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix';
41+
export const CODE_NAME_CODEGEN_PREFIX = 'name/codegen-prefix';
4142
export const CODE_NAME_CASING = 'name/casing';
4243
export const CODE_NAME_DUPLICATE = 'name/duplicate';
4344

4445
// -----------------------------------------------------------------------------
45-
// Block lambda prefix
46+
// Codegen prefix
4647
// -----------------------------------------------------------------------------
4748

48-
export const nameMustNotStartWithBlockLambdaPrefix = (node: SdsDeclaration, accept: ValidationAcceptor) => {
49+
export const nameMustNotStartWithCodegenPrefix = (node: SdsDeclaration, accept: ValidationAcceptor) => {
4950
const name = node.name ?? '';
50-
const blockLambdaPrefix = '__block_lambda_';
51-
if (name.startsWith(blockLambdaPrefix)) {
51+
if (name.startsWith(CODEGEN_PREFIX)) {
5252
accept(
5353
'error',
54-
"Names of declarations must not start with '__block_lambda_'. This is reserved for code generation of block lambdas.",
54+
`Names of declarations must not start with '${CODEGEN_PREFIX}'. This is reserved for code generation.`,
5555
{
5656
node,
5757
property: 'name',
58-
code: CODE_NAME_BLOCK_LAMBDA_PREFIX,
58+
code: CODE_NAME_CODEGEN_PREFIX,
5959
},
6060
);
6161
}

src/language/validation/safe-ds-validator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
functionMustContainUniqueNames,
1313
moduleMemberMustHaveNameThatIsUniqueInPackage,
1414
moduleMustContainUniqueNames,
15-
nameMustNotStartWithBlockLambdaPrefix,
15+
nameMustNotStartWithCodegenPrefix,
1616
nameShouldHaveCorrectCasing,
1717
pipelineMustContainUniqueNames,
1818
schemaMustContainUniqueNames,
@@ -188,7 +188,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
188188
SdsClassBody: [classBodyShouldNotBeEmpty],
189189
SdsConstraintList: [constraintListShouldNotBeEmpty],
190190
SdsDeclaration: [
191-
nameMustNotStartWithBlockLambdaPrefix,
191+
nameMustNotStartWithCodegenPrefix,
192192
nameShouldHaveCorrectCasing,
193193
pythonNameShouldDifferFromSafeDsName(services),
194194
singleUseAnnotationsMustNotBeRepeated(services),

tests/helpers/diagnostics.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ export const getErrors = async (services: LangiumServices, code: string): Promis
4141
return diagnostics.filter((d) => d.severity === DiagnosticSeverity.Error);
4242
};
4343

44+
/**
45+
* Get all errors from a loaded document.
46+
*
47+
* @param services The language services.
48+
* @param uri The URI of the document to check.
49+
* @returns The errors.
50+
*/
51+
export const getErrorsAtURI = (services: LangiumServices, uri: URI): Diagnostic[] => {
52+
const diagnostics = getDiagnosticsAtURI(services, uri);
53+
return diagnostics.filter((d) => d.severity === DiagnosticSeverity.Error);
54+
};
55+
4456
/**
4557
* Get all diagnostics from a code snippet.
4658
*
@@ -57,6 +69,18 @@ const getDiagnostics = async (services: LangiumServices, code: string): Promise<
5769
return document.diagnostics ?? [];
5870
};
5971

72+
/**
73+
* Get all diagnostics from a loaded document.
74+
*
75+
* @param services The language services.
76+
* @param uri The URI of the document to check.
77+
* @returns The diagnostics.
78+
*/
79+
const getDiagnosticsAtURI = (services: LangiumServices, uri: URI): Diagnostic[] => {
80+
const document = services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
81+
return document.diagnostics ?? [];
82+
};
83+
6084
/**
6185
* The code contains syntax errors.
6286
*/

tests/helpers/testResources.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import path from 'path';
22
import { globSync } from 'glob';
33
import { SAFE_DS_FILE_EXTENSIONS } from '../../src/language/helpers/fileExtensions.js';
44
import { group } from 'radash';
5-
import { URI } from 'langium';
5+
import { BuildOptions, LangiumDocument, URI } from 'langium';
6+
import { SafeDsServices } from '../../src/language/safe-ds-module.js';
67

78
const TEST_RESOURCES_PATH = path.join(__dirname, '..', 'resources');
89

@@ -92,3 +93,21 @@ const isNotSkipped = (pathRelativeToResources: string) => {
9293
const segments = pathRelativeToResources.split(path.sep);
9394
return !segments.some((segment) => segment.startsWith('skip'));
9495
};
96+
97+
/**
98+
* Load the documents at the specified URIs into the workspace managed by the given services.
99+
*
100+
* @param services The language services.
101+
* @param uris The URIs of the documents to load.
102+
* @param options The build options.
103+
* @returns The loaded documents.
104+
*/
105+
export const loadDocuments = async (
106+
services: SafeDsServices,
107+
uris: URI[],
108+
options: BuildOptions = {},
109+
): Promise<LangiumDocument[]> => {
110+
const documents = uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
111+
await services.shared.workspace.DocumentBuilder.build(documents, options);
112+
return documents;
113+
};

tests/language/generation/creator.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {
22
listTestPythonFiles,
33
listTestSafeDsFilesGroupedByParentDirectory,
4+
loadDocuments,
45
uriToShortenedTestResourceName,
56
} from '../../helpers/testResources.js';
67
import path from 'path';
78
import fs from 'fs';
89
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
9-
import { ErrorsInCodeError, getErrors } from '../../helpers/diagnostics.js';
10+
import { ErrorsInCodeError, getErrorsAtURI } from '../../helpers/diagnostics.js';
1011
import { findTestChecks } from '../../helpers/testChecks.js';
1112
import { Location } from 'vscode-languageserver';
1213
import { NodeFileSystem } from 'langium/node';
@@ -31,11 +32,14 @@ const createGenerationTest = async (parentDirectory: URI, inputUris: URI[]): Pro
3132
const expectedOutputFiles = readExpectedOutputFiles(expectedOutputRoot, actualOutputRoot);
3233
let runUntil: Location | undefined;
3334

35+
// Load all files, so they get linked
36+
await loadDocuments(services, inputUris, { validation: true });
37+
3438
for (const uri of inputUris) {
35-
const code = fs.readFileSync(uri.fsPath).toString();
39+
const code = services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri).textDocument.getText();
3640

3741
// File must not contain any errors
38-
const errors = await getErrors(services, code);
42+
const errors = getErrorsAtURI(services, uri);
3943
if (errors.length > 0) {
4044
return invalidTest('FILE', new ErrorsInCodeError(errors, uri));
4145
}

tests/language/generation/testGeneration.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createGenerationTests } from './creator.js';
66
import { SdsModule } from '../../../src/language/generated/ast.js';
77
import { generatePython } from '../../../src/cli/generator.js';
88
import fs from 'fs';
9+
import { loadDocuments } from '../../helpers/testResources.js';
910

1011
const services = createSafeDsServices(NodeFileSystem).SafeDs;
1112
const generationTests = createGenerationTests();
@@ -27,18 +28,15 @@ describe('generation', async () => {
2728
}
2829

2930
// Load all documents
30-
const documents = test.inputUris.map((uri) =>
31-
services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri),
32-
);
33-
await services.shared.workspace.DocumentBuilder.build(documents);
31+
const documents = await loadDocuments(services, test.inputUris);
3432

3533
// Generate code for all documents
3634
const actualOutputPaths: string[] = [];
3735

3836
for (const document of documents) {
3937
const module = document.parseResult.value as SdsModule;
4038
const fileName = document.uri.fsPath;
41-
const generatedFilePaths = generatePython(module, fileName, test.actualOutputRoot.fsPath);
39+
const generatedFilePaths = generatePython(services, module, fileName, test.actualOutputRoot.fsPath);
4240
actualOutputPaths.push(...generatedFilePaths);
4341
}
4442

tests/language/partialEvaluation/testPartialEvaluation.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AssertionError } from 'assert';
66
import { locationToString } from '../../helpers/location.js';
77
import { createPartialEvaluationTests } from './creator.js';
88
import { getNodeByLocation } from '../../helpers/nodeFinder.js';
9+
import { loadDocuments } from '../../helpers/testResources.js';
910

1011
const services = createSafeDsServices(NodeFileSystem).SafeDs;
1112
const partialEvaluator = services.evaluation.PartialEvaluator;
@@ -22,8 +23,7 @@ describe('partial evaluation', async () => {
2223
}
2324

2425
// Load all documents
25-
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
26-
await services.shared.workspace.DocumentBuilder.build(documents);
26+
await loadDocuments(services, test.uris);
2727

2828
// Ensure that partially evaluating nodes in the same equivalence class yields the same result
2929
for (const equivalenceClassAssertion of test.equivalenceClassAssertions) {

tests/language/scoping/testScoping.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AssertionError } from 'assert';
77
import { isLocationEqual, locationToString } from '../../helpers/location.js';
88
import { createScopingTests, ExpectedReference } from './creator.js';
99
import { Location } from 'vscode-languageserver';
10+
import { loadDocuments } from '../../helpers/testResources.js';
1011

1112
const services = createSafeDsServices(NodeFileSystem).SafeDs;
1213

@@ -27,8 +28,7 @@ describe('scoping', async () => {
2728
}
2829

2930
// Load all documents
30-
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
31-
await services.shared.workspace.DocumentBuilder.build(documents);
31+
await loadDocuments(services, test.uris);
3232

3333
// Ensure all expected references match
3434
for (const expectedReference of test.expectedReferences) {

tests/language/typing/creator.ts renamed to tests/language/typing/type computer/creator.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import {
22
listTestSafeDsFilesGroupedByParentDirectory,
33
uriToShortenedTestResourceName,
4-
} from '../../helpers/testResources.js';
4+
} from '../../../helpers/testResources.js';
55
import fs from 'fs';
6-
import { findTestChecks } from '../../helpers/testChecks.js';
6+
import { findTestChecks } from '../../../helpers/testChecks.js';
77
import { Location } from 'vscode-languageserver';
8-
import { getSyntaxErrors, SyntaxErrorsInCodeError } from '../../helpers/diagnostics.js';
8+
import { getSyntaxErrors, SyntaxErrorsInCodeError } from '../../../helpers/diagnostics.js';
99
import { EmptyFileSystem, URI } from 'langium';
10-
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
11-
import { TestDescription, TestDescriptionError } from '../../helpers/testDescription.js';
10+
import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js';
11+
import { TestDescription, TestDescriptionError } from '../../../helpers/testDescription.js';
1212

1313
const services = createSafeDsServices(EmptyFileSystem).SafeDs;
1414
const rootResourceName = 'typing';

tests/language/typing/testTyping.test.ts renamed to tests/language/typing/type computer/testTyping.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { afterEach, beforeEach, describe, it } from 'vitest';
2-
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
2+
import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js';
33
import { NodeFileSystem } from 'langium/node';
44
import { clearDocuments } from 'langium/test';
55
import { AssertionError } from 'assert';
6-
import { locationToString } from '../../helpers/location.js';
6+
import { locationToString } from '../../../helpers/location.js';
77
import { createTypingTests } from './creator.js';
8-
import { getNodeByLocation } from '../../helpers/nodeFinder.js';
8+
import { getNodeByLocation } from '../../../helpers/nodeFinder.js';
9+
import { loadDocuments } from '../../../helpers/testResources.js';
910

1011
const services = createSafeDsServices(NodeFileSystem).SafeDs;
1112
const typeComputer = services.types.TypeComputer;
@@ -27,8 +28,7 @@ describe('typing', async () => {
2728
}
2829

2930
// Load all documents
30-
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
31-
await services.shared.workspace.DocumentBuilder.build(documents);
31+
await loadDocuments(services, test.uris);
3232

3333
// Ensure all nodes in the equivalence class have the same type
3434
for (const equivalenceClassAssertion of test.equivalenceClassAssertions) {

tests/language/validation/testValidation.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
66
import { AssertionError } from 'assert';
77
import { clearDocuments, isRangeEqual } from 'langium/test';
88
import { locationToString } from '../../helpers/location.js';
9+
import { loadDocuments } from '../../helpers/testResources.js';
910

1011
const services = createSafeDsServices(NodeFileSystem).SafeDs;
1112

@@ -26,8 +27,7 @@ describe('validation', async () => {
2627
}
2728

2829
// Load all documents
29-
const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri));
30-
await services.shared.workspace.DocumentBuilder.build(documents, { validation: true });
30+
await loadDocuments(services, test.uris, { validation: true });
3131

3232
// Ensure all expected issues match
3333
for (const expectedIssue of test.expectedIssues) {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package tests.generator.parameterWithPythonName
2+
3+
fun f1(param: (a: Int, b: Int, c: Int) -> r: Int)
4+
fun f2(param: (a: Int, b: Int, c: Int) -> ())
5+
6+
segment test(param1: Int, @PythonName("param_2") param2: Int, @PythonName("param_3") param3: Int = 0) {
7+
f1((param1: Int, param2: Int, param3: Int = 0) -> 1);
8+
f2((param1: Int, param2: Int, param3: Int = 0) {});
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Steps ------------------------------------------------------------------------
2+
3+
def test(param1, param_2, param_3=0):
4+
f1(lambda param1, param2, param3=0: 1)
5+
def __gen_block_lambda_0(param1, param2, param3=0):
6+
pass
7+
f2(__gen_block_lambda_0)

tests/resources/generation/skip-declarations/two steps/input.sdstest renamed to tests/resources/generation/declarations/two steps/input.sdstest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ segment test1(a: Int, b: Int = 0) {
66
f();
77
}
88

9-
segment test2(a: Int, vararg c: Int) {
9+
segment test2(a: Int, c: Int) {
1010
f();
1111
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
def test1(a, b=0):
44
f()
55

6-
def test2(a, *c):
6+
def test2(a, c):
77
f()

tests/resources/generation/dummy/output/test.py

Whitespace-only changes.

tests/resources/generation/dummy/test.sdstest

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package tests.generator.blockLambdaResult
2+
3+
fun g() -> a: Int
4+
5+
fun h(a: Int)
6+
7+
segment f1(l: (a: Int, b: Int) -> d: Int) {
8+
h(l(1, 2).d);
9+
}
10+
11+
segment f2(l: (a: Int, b: Int) -> (d1: Int, e1: Int)) {
12+
h(l(1, 2).e1);
13+
h(l(1, 2).d1);
14+
}
15+
16+
pipeline test {
17+
18+
f1((a: Int, b: Int) {
19+
yield d = g();
20+
});
21+
f2((a: Int, b: Int) {
22+
yield d = g();
23+
yield e = g();
24+
});
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Steps ------------------------------------------------------------------------
2+
3+
def f1(l):
4+
h(l(1, 2))
5+
6+
def f2(l):
7+
h(l(1, 2)[1])
8+
h(l(1, 2)[0])
9+
10+
# Pipelines --------------------------------------------------------------------
11+
12+
def test():
13+
def __gen_block_lambda_0(a, b):
14+
__gen_block_lambda_result_d = g()
15+
return __gen_block_lambda_result_d
16+
f1(__gen_block_lambda_0)
17+
def __gen_block_lambda_1(a, b):
18+
__gen_block_lambda_result_d = g()
19+
__gen_block_lambda_result_e = g()
20+
return __gen_block_lambda_result_d, __gen_block_lambda_result_e
21+
f2(__gen_block_lambda_1)
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package tests.generator.blockLambda
22

33
fun f1(param: (a: Int, b: Int) -> r: Int)
4-
fun f2(param: (a: Int, vararg b: Int) -> r: Int)
5-
fun f3(param: () -> ())
4+
fun f2(param: () -> ())
65

76
fun g() -> a: Int
87

98
pipeline test {
109
f1((a: Int, b: Int = 2) {
1110
yield d = g();
1211
});
13-
f2((a: Int, vararg c: Int) {
12+
f1((a: Int, c: Int) {
1413
yield d = g();
1514
});
16-
f3(() {});
15+
f2(() {});
1716
}

0 commit comments

Comments
 (0)