Skip to content

Commit b3d786c

Browse files
feat: very basic type computer (#596)
Closes partially #541 ### Summary of Changes Add a very basic implementation of a type computer that can only deal with literals and template strings. It does, however, lay the groundwork to implement more complex features later, by adding a system to access builtin classes like `String` and registering the type computer as an added service. --------- Co-authored-by: megalinter-bot <[email protected]>
1 parent 928b520 commit b3d786c

Some content is hidden

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

48 files changed

+1258
-90
lines changed

src/language/builtins/fileFinder.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import path from 'path';
2+
import { URI } from 'langium';
3+
import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js';
4+
import { globSync } from 'glob';
5+
6+
let builtinsPath: string;
7+
if (__filename.endsWith('.ts')) {
8+
// Before running ESBuild
9+
builtinsPath = path.join(__dirname, '..', '..', 'resources', 'builtins');
10+
} /* c8 ignore start */ else {
11+
// After running ESBuild
12+
builtinsPath = path.join(__dirname, '..', 'resources', 'builtins');
13+
} /* c8 ignore stop */
14+
15+
/**
16+
* Lists all Safe-DS files in `src/resources/builtins`.
17+
*
18+
* @return URIs of all discovered files.
19+
*/
20+
export const listBuiltinsFiles = (): URI[] => {
21+
const pattern = `**/*.{${SAFE_DS_FILE_EXTENSIONS.join(',')}}`;
22+
const relativePaths = globSync(pattern, { cwd: builtinsPath, nodir: true });
23+
return relativePaths.map((relativePath) => {
24+
const absolutePath = path.join(builtinsPath, relativePath);
25+
return URI.file(absolutePath);
26+
});
27+
};
28+
29+
/**
30+
* Resolves a relative path to a builtin file.
31+
*
32+
* @param relativePath
33+
*/
34+
export const resolveRelativePathToBuiltinFile = (relativePath: string): URI => {
35+
const absolutePath = path.join(builtinsPath, relativePath);
36+
return URI.file(absolutePath);
37+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { SafeDsServices } from '../safe-ds-module.js';
2+
import { resolveRelativePathToBuiltinFile } from './fileFinder.js';
3+
import { isSdsClass, isSdsModule, SdsClass } from '../generated/ast.js';
4+
import { LangiumDocuments } from 'langium';
5+
import { moduleMembersOrEmpty } from '../helpers/shortcuts.js';
6+
7+
const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub');
8+
9+
export class SafeDsCoreClasses {
10+
private readonly langiumDocuments: LangiumDocuments;
11+
12+
constructor(services: SafeDsServices) {
13+
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
14+
}
15+
16+
private cachedAny: SdsClass | undefined;
17+
18+
/* c8 ignore start */
19+
get Any(): SdsClass | undefined {
20+
if (!this.cachedAny) {
21+
this.cachedAny = this.getClass('Any');
22+
}
23+
return this.cachedAny;
24+
}
25+
26+
private cachedBoolean: SdsClass | undefined;
27+
/* c8 ignore stop */
28+
29+
get Boolean(): SdsClass | undefined {
30+
if (!this.cachedBoolean) {
31+
this.cachedBoolean = this.getClass('Boolean');
32+
}
33+
return this.cachedBoolean;
34+
}
35+
36+
private cachedFloat: SdsClass | undefined;
37+
38+
get Float(): SdsClass | undefined {
39+
if (!this.cachedFloat) {
40+
this.cachedFloat = this.getClass('Float');
41+
}
42+
return this.cachedFloat;
43+
}
44+
45+
private cachedInt: SdsClass | undefined;
46+
47+
get Int(): SdsClass | undefined {
48+
if (!this.cachedInt) {
49+
this.cachedInt = this.getClass('Int');
50+
}
51+
return this.cachedInt;
52+
}
53+
54+
private cachedNothing: SdsClass | undefined;
55+
56+
get Nothing(): SdsClass | undefined {
57+
if (!this.cachedNothing) {
58+
this.cachedNothing = this.getClass('Nothing');
59+
}
60+
return this.cachedNothing;
61+
}
62+
63+
private cachedString: SdsClass | undefined;
64+
65+
get String(): SdsClass | undefined {
66+
if (!this.cachedString) {
67+
this.cachedString = this.getClass('String');
68+
}
69+
return this.cachedString;
70+
}
71+
72+
private getClass(name: string): SdsClass | undefined {
73+
if (!this.langiumDocuments.hasDocument(CORE_CLASSES_URI)) {
74+
/* c8 ignore next 2 */
75+
return undefined;
76+
}
77+
78+
const document = this.langiumDocuments.getOrCreateDocument(CORE_CLASSES_URI);
79+
const root = document.parseResult.value;
80+
if (!isSdsModule(root)) {
81+
/* c8 ignore next 2 */
82+
return undefined;
83+
}
84+
85+
const firstMatchingModuleMember = moduleMembersOrEmpty(root).find((m) => m.name === name);
86+
if (!isSdsClass(firstMatchingModuleMember)) {
87+
/* c8 ignore next 2 */
88+
return undefined;
89+
}
90+
91+
return firstMatchingModuleMember;
92+
}
93+
}
Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices, URI } from 'langium';
1+
import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices } from 'langium';
22
import { WorkspaceFolder } from 'vscode-languageserver';
3-
import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js';
4-
import { globSync } from 'glob';
5-
import path from 'path';
3+
import { listBuiltinsFiles } from './fileFinder.js';
64

75
export class SafeDsWorkspaceManager extends DefaultWorkspaceManager {
86
private documentFactory: LangiumDocumentFactory;
@@ -18,31 +16,9 @@ export class SafeDsWorkspaceManager extends DefaultWorkspaceManager {
1816
): Promise<void> {
1917
await super.loadAdditionalDocuments(folders, collector);
2018

19+
// Load builtin files
2120
for (const uri of listBuiltinsFiles()) {
2221
collector(this.documentFactory.create(uri));
2322
}
2423
}
2524
}
26-
27-
let builtinsPath: string;
28-
if (__filename.endsWith('.ts')) {
29-
// Before running ESBuild
30-
builtinsPath = path.join(__dirname, '..', '..', 'resources', 'builtins');
31-
} /* c8 ignore start */ else {
32-
// After running ESBuild
33-
builtinsPath = path.join(__dirname, '..', 'resources', 'builtins');
34-
} /* c8 ignore stop */
35-
36-
/**
37-
* Lists all Safe-DS files in `src/resources/builtins`.
38-
*
39-
* @return URIs of all discovered files.
40-
*/
41-
export const listBuiltinsFiles = (): URI[] => {
42-
const pattern = `**/*.{${SAFE_DS_FILE_EXTENSIONS.join(',')}}`;
43-
const relativePaths = globSync(pattern, { cwd: builtinsPath, nodir: true });
44-
return relativePaths.map((relativePath) => {
45-
const absolutePath = path.join(builtinsPath, relativePath);
46-
return URI.file(absolutePath);
47-
});
48-
};

src/language/helpers/shortcuts.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
isSdsBlockLambdaResult,
44
isSdsDeclaration,
55
isSdsModule,
6+
isSdsModuleMember,
67
isSdsPlaceholder,
78
SdsAnnotatedObject,
89
SdsAnnotationCall,
@@ -19,6 +20,7 @@ import {
1920
SdsLiteral,
2021
SdsLiteralType,
2122
SdsModule,
23+
SdsModuleMember,
2224
SdsParameter,
2325
SdsParameterList,
2426
SdsPlaceholder,
@@ -77,6 +79,10 @@ export const importsOrEmpty = function (node: SdsModule | undefined): SdsImport[
7779
return node?.imports ?? [];
7880
};
7981

82+
export const moduleMembersOrEmpty = function (node: SdsModule | undefined): SdsModuleMember[] {
83+
return node?.members?.filter(isSdsModuleMember) ?? [];
84+
};
85+
8086
export const packageNameOrNull = function (node: AstNode | undefined): string | null {
8187
return getContainerOfType(node, isSdsModule)?.name ?? null;
8288
};

src/language/safe-ds-module.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,24 @@ import {
1010
PartialLangiumServices,
1111
} from 'langium';
1212
import { SafeDsGeneratedModule, SafeDsGeneratedSharedModule } from './generated/module.js';
13-
import { registerValidationChecks, SafeDsValidator } from './validation/safe-ds-validator.js';
13+
import { registerValidationChecks } from './validation/safe-ds-validator.js';
1414
import { SafeDsFormatter } from './formatting/safe-ds-formatter.js';
1515
import { SafeDsWorkspaceManager } from './builtins/safe-ds-workspace-manager.js';
1616
import { SafeDsScopeComputation } from './scoping/safe-ds-scope-computation.js';
1717
import { SafeDsScopeProvider } from './scoping/safe-ds-scope-provider.js';
1818
import { SafeDsValueConverter } from './grammar/safe-ds-value-converter.js';
19+
import { SafeDsTypeComputer } from './typing/safe-ds-type-computer.js';
20+
import { SafeDsCoreClasses } from './builtins/safe-ds-core-classes.js';
1921

2022
/**
2123
* Declaration of custom services - add your own service classes here.
2224
*/
2325
export type SafeDsAddedServices = {
24-
validation: {
25-
SafeDsValidator: SafeDsValidator;
26+
builtins: {
27+
CoreClasses: SafeDsCoreClasses;
28+
};
29+
types: {
30+
TypeComputer: SafeDsTypeComputer;
2631
};
2732
};
2833

@@ -38,6 +43,9 @@ export type SafeDsServices = LangiumServices & SafeDsAddedServices;
3843
* selected services, while the custom services must be fully specified.
3944
*/
4045
export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeDsAddedServices> = {
46+
builtins: {
47+
CoreClasses: (services) => new SafeDsCoreClasses(services),
48+
},
4149
lsp: {
4250
Formatter: () => new SafeDsFormatter(),
4351
},
@@ -48,8 +56,8 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
4856
ScopeComputation: (services) => new SafeDsScopeComputation(services),
4957
ScopeProvider: (services) => new SafeDsScopeProvider(services),
5058
},
51-
validation: {
52-
SafeDsValidator: () => new SafeDsValidator(),
59+
types: {
60+
TypeComputer: (services) => new SafeDsTypeComputer(services),
5361
},
5462
};
5563

src/language/scoping/safe-ds-scope-provider.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import {
22
AstNode,
33
AstNodeDescription,
4-
AstNodeDescriptionProvider,
54
AstNodeLocator,
65
DefaultScopeProvider,
76
EMPTY_SCOPE,
87
getContainerOfType,
98
getDocument,
109
LangiumDocuments,
11-
LangiumServices,
1210
MultiMap,
1311
ReferenceInfo,
1412
Scope,
@@ -59,18 +57,20 @@ import {
5957
} from '../helpers/shortcuts.js';
6058
import { isContainedIn } from '../helpers/ast.js';
6159
import { isStatic, isWildcardImport } from '../helpers/checks.js';
60+
import { SafeDsServices } from '../safe-ds-module.js';
61+
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
6262

6363
export class SafeDsScopeProvider extends DefaultScopeProvider {
64-
readonly documents: LangiumDocuments;
65-
readonly astNodeDescriptionProvider: AstNodeDescriptionProvider;
66-
readonly astNodeLocator: AstNodeLocator;
64+
private readonly astNodeLocator: AstNodeLocator;
65+
private readonly langiumDocuments: LangiumDocuments;
66+
private readonly typeComputer: SafeDsTypeComputer;
6767

68-
constructor(services: LangiumServices) {
68+
constructor(services: SafeDsServices) {
6969
super(services);
7070

71-
this.documents = services.shared.workspace.LangiumDocuments;
72-
this.astNodeDescriptionProvider = services.workspace.AstNodeDescriptionProvider;
7371
this.astNodeLocator = services.workspace.AstNodeLocator;
72+
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
73+
this.typeComputer = services.types.TypeComputer;
7474
}
7575

7676
override getScope(context: ReferenceInfo): Scope {
@@ -388,7 +388,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
388388
/* c8 ignore next 2 */
389389
return nodeDescription.node;
390390
}
391-
const document = this.documents.getOrCreateDocument(nodeDescription.documentUri);
391+
const document = this.langiumDocuments.getOrCreateDocument(nodeDescription.documentUri);
392392
return this.astNodeLocator.getAstNode(document.parseResult.value, nodeDescription.path);
393393
}
394394

0 commit comments

Comments
 (0)