Skip to content

Commit 38afc07

Browse files
authored
feat: scoping for references to own static members (#582)
Closes partially #540 ### Summary of Changes Member accesses to * own static attributes, * own nested classes, * own nested enums, * own static methods, * enum variants are now resolved properly.
1 parent 3e88f02 commit 38afc07

File tree

47 files changed

+705
-182
lines changed

Some content is hidden

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

47 files changed

+705
-182
lines changed

src/language/ast/checks.ts

-6
This file was deleted.

src/language/builtins/safe-ds-workspace-manager.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices, URI } from 'langium';
22
import { WorkspaceFolder } from 'vscode-languageserver';
3-
import { SAFE_DS_FILE_EXTENSIONS } from '../constants/fileExtensions.js';
3+
import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js';
44
import { globSync } from 'glob';
55
import path from 'path';
66

src/language/formatting/safe-ds-formatter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from 'langium';
1111
import * as ast from '../generated/ast.js';
1212
import { SdsImport, SdsImportAlias, SdsModule } from '../generated/ast.js';
13-
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../ast/shortcuts.js';
13+
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/shortcuts.js';
1414
import noSpace = Formatting.noSpace;
1515
import newLine = Formatting.newLine;
1616
import newLines = Formatting.newLines;

src/language/grammar/safe-ds.langium

+1-1
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ SdsString returns SdsString:
754754
;
755755

756756
interface SdsReference extends SdsExpression {
757-
target?: @SdsDeclaration
757+
target: @SdsDeclaration
758758
}
759759

760760
SdsReference returns SdsReference:
File renamed without changes.

src/language/helpers/checks.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { isSdsAttribute, isSdsClass, isSdsEnum, isSdsFunction, SdsClassMember, SdsImport } from '../generated/ast.js';
2+
3+
export const isStatic = (node: SdsClassMember): boolean => {
4+
if (isSdsClass(node) || isSdsEnum(node)) {
5+
return true;
6+
} else if (isSdsAttribute(node)) {
7+
return node.static;
8+
} else if (isSdsFunction(node)) {
9+
return node.static;
10+
} else {
11+
/* c8 ignore next 2 */
12+
return false;
13+
}
14+
};
15+
16+
export const isWildcardImport = function (node: SdsImport): boolean {
17+
const importedNamespace = node.importedNamespace ?? '';
18+
return importedNamespace.endsWith('*');
19+
};

src/language/ast/shortcuts.ts src/language/helpers/shortcuts.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ export const literalsOrEmpty = function (node: SdsLiteralType | undefined): SdsL
5959
return node?.literalList?.literals ?? [];
6060
};
6161

62-
export const classMembersOrEmpty = function (node: SdsClass | undefined): SdsClassMember[] {
63-
return node?.body?.members ?? [];
62+
export const classMembersOrEmpty = function (
63+
node: SdsClass | undefined,
64+
filterFunction: (member: SdsClassMember) => boolean = () => true,
65+
): SdsClassMember[] {
66+
return node?.body?.members?.filter(filterFunction) ?? [];
6467
};
6568

6669
export const enumVariantsOrEmpty = function (node: SdsEnum | undefined): SdsEnumVariant[] {

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

+134-126
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
isSdsSegment,
2525
isSdsStatement,
2626
isSdsYield,
27+
SdsDeclaration,
28+
SdsExpression,
2729
SdsMemberAccess,
2830
SdsMemberType,
2931
SdsNamedTypeDeclaration,
@@ -33,8 +35,16 @@ import {
3335
SdsType,
3436
SdsYield,
3537
} from '../generated/ast.js';
36-
import { assigneesOrEmpty, parametersOrEmpty, resultsOrEmpty, statementsOrEmpty } from '../ast/shortcuts.js';
37-
import { isContainedIn } from '../ast/utils.js';
38+
import {
39+
assigneesOrEmpty,
40+
classMembersOrEmpty,
41+
enumVariantsOrEmpty,
42+
parametersOrEmpty,
43+
resultsOrEmpty,
44+
statementsOrEmpty,
45+
} from '../helpers/shortcuts.js';
46+
import { isContainedIn } from '../helpers/ast.js';
47+
import { isStatic } from '../helpers/checks.js';
3848

3949
export class SafeDsScopeProvider extends DefaultScopeProvider {
4050
override getScope(context: ReferenceInfo): Scope {
@@ -80,31 +90,145 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
8090
* Returns the unique declaration that is referenced by this type. If the type references none or multiple
8191
* declarations, undefined is returned.
8292
*
83-
* @param type The type to get the referenced declaration for.
93+
* @param node The type to get the referenced declaration for.
8494
* @returns The referenced declaration or undefined.
8595
*/
86-
private getUniqueReferencedDeclarationForType(type: SdsType): SdsNamedTypeDeclaration | undefined {
87-
if (isSdsNamedType(type)) {
88-
return type.declaration.ref;
89-
} else if (isSdsMemberType(type)) {
90-
return type.member.declaration.ref;
96+
private getUniqueReferencedDeclarationForType(node: SdsType): SdsNamedTypeDeclaration | undefined {
97+
if (isSdsNamedType(node)) {
98+
return node.declaration.ref;
99+
} else if (isSdsMemberType(node)) {
100+
return node.member.declaration.ref;
91101
} else {
92102
return undefined;
93103
}
94104
}
95105

96-
private getScopeForMemberAccessMember(_node: SdsMemberAccess): Scope {
97-
return EMPTY_SCOPE;
106+
private getScopeForMemberAccessMember(node: SdsMemberAccess): Scope {
107+
let currentScope = EMPTY_SCOPE;
108+
109+
// Static access
110+
const declaration = this.getUniqueReferencedDeclarationForExpression(node.receiver);
111+
if (isSdsClass(declaration)) {
112+
currentScope = this.createScopeForNodes(classMembersOrEmpty(declaration, isStatic));
113+
114+
// val superTypeMembers = receiverDeclaration.superClassMembers()
115+
// .filter { it.isStatic() }
116+
// .toList()
117+
//
118+
// return Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers))
119+
} else if (isSdsEnum(declaration)) {
120+
currentScope = this.createScopeForNodes(enumVariantsOrEmpty(declaration));
121+
}
122+
123+
// // Call results
124+
// var resultScope = IScope.NULLSCOPE
125+
// if (receiver is SdsCall) {
126+
// val results = receiver.resultsOrNull()
127+
// when {
128+
// results == null -> return IScope.NULLSCOPE
129+
// results.size > 1 -> return Scopes.scopeFor(results)
130+
// results.size == 1 -> resultScope = Scopes.scopeFor(results)
131+
// }
132+
// }
133+
//
134+
// // Members
135+
// val type = (receiver.type() as? NamedType) ?: return resultScope
136+
//
137+
// return when {
138+
// type.isNullable && !context.isNullSafe -> resultScope
139+
// type is ClassType -> {
140+
// val members = type.sdsClass.classMembersOrEmpty().filter { !it.isStatic() }
141+
// val superTypeMembers = type.sdsClass.superClassMembers()
142+
// .filter { !it.isStatic() }
143+
// .toList()
144+
//
145+
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
146+
// }
147+
// type is EnumVariantType -> Scopes.scopeFor(type.sdsEnumVariant.parametersOrEmpty())
148+
// else -> resultScope
149+
// }
150+
151+
return currentScope;
152+
}
153+
154+
/**
155+
* Returns the unique declaration that is referenced by this expression. If the expression references none or
156+
* multiple declarations, undefined is returned.
157+
*
158+
* @param node The expression to get the referenced declaration for.
159+
* @returns The referenced declaration or undefined.
160+
*/
161+
private getUniqueReferencedDeclarationForExpression(node: SdsExpression): SdsDeclaration | undefined {
162+
if (isSdsReference(node)) {
163+
return node.target.ref;
164+
} else if (isSdsMemberAccess(node)) {
165+
return node.member.target.ref;
166+
} else {
167+
return undefined;
168+
}
98169
}
99170

100171
private getScopeForDirectReferenceTarget(node: SdsReference): Scope {
172+
// val resource = context.eResource()
173+
// val packageName = context.containingCompilationUnitOrNull()?.qualifiedNameOrNull()
174+
//
175+
// // Declarations in other files
176+
// var result: IScope = FilteringScope(
177+
// super.delegateGetScope(context, SafeDSPackage.Literals.SDS_REFERENCE__DECLARATION),
178+
// ) {
179+
// it.isReferencableExternalDeclaration(resource, packageName)
180+
// }
181+
101182
// Declarations in this file
102183
const currentScope = this.globalDeclarationsInSameFile(node, EMPTY_SCOPE);
103184

185+
// // Declarations in containing classes
186+
// context.containingClassOrNull()?.let {
187+
// result = classMembers(it, result)
188+
// }
189+
//
190+
104191
// Declarations in containing blocks
105192
return this.localDeclarations(node, currentScope);
106193
}
107194

195+
// private fun classMembers(context: SdsClass, parentScope: IScope): IScope {
196+
// return when (val containingClassOrNull = context.containingClassOrNull()) {
197+
// is SdsClass -> Scopes.scopeFor(
198+
// context.classMembersOrEmpty(),
199+
// classMembers(containingClassOrNull, parentScope),
200+
// )
201+
// else -> Scopes.scopeFor(context.classMembersOrEmpty(), parentScope)
202+
// }
203+
// }
204+
205+
// /**
206+
// * Removes declarations in this [Resource], [SdsAnnotation]s, and internal [SdsStep]s located in other
207+
// * [SdsCompilationUnit]s.
208+
// */
209+
// private fun IEObjectDescription?.isReferencableExternalDeclaration(
210+
// fromResource: Resource,
211+
// fromPackageWithQualifiedName: QualifiedName?,
212+
// ): Boolean {
213+
// // Resolution failed in delegate scope
214+
// if (this == null) return false
215+
//
216+
// val obj = this.eObjectOrProxy
217+
//
218+
// // Local declarations are added later using custom scoping rules
219+
// if (obj.eResource() == fromResource) return false
220+
//
221+
// // Annotations cannot be referenced
222+
// if (obj is SdsAnnotation) return false
223+
//
224+
// // Internal steps in another package cannot be referenced
225+
// return !(
226+
// obj is SdsStep &&
227+
// obj.visibility() == SdsVisibility.Internal &&
228+
// obj.containingCompilationUnitOrNull()?.qualifiedNameOrNull() != fromPackageWithQualifiedName
229+
// )
230+
// }
231+
108232
private globalDeclarationsInSameFile(node: AstNode, outerScope: Scope): Scope {
109233
const module = getContainerOfType(node, isSdsModule);
110234
if (!module) {
@@ -167,122 +291,6 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
167291
}
168292
}
169293

170-
// private fun scopeForReferenceDeclaration(context: SdsReference): IScope {
171-
// val resource = context.eResource()
172-
// val packageName = context.containingCompilationUnitOrNull()?.qualifiedNameOrNull()
173-
//
174-
// // Declarations in other files
175-
// var result: IScope = FilteringScope(
176-
// super.delegateGetScope(context, SafeDSPackage.Literals.SDS_REFERENCE__DECLARATION),
177-
// ) {
178-
// it.isReferencableExternalDeclaration(resource, packageName)
179-
// }
180-
//
181-
// // Declarations in this file
182-
// result = declarationsInSameFile(resource, result)
183-
//
184-
// // Declarations in containing classes
185-
// context.containingClassOrNull()?.let {
186-
// result = classMembers(it, result)
187-
// }
188-
//
189-
// // Declarations in containing blocks
190-
// localDeclarations(context, result)
191-
// }
192-
// }
193-
// }
194-
//
195-
// /**
196-
// * Removes declarations in this [Resource], [SdsAnnotation]s, and internal [SdsStep]s located in other
197-
// * [SdsCompilationUnit]s.
198-
// */
199-
// private fun IEObjectDescription?.isReferencableExternalDeclaration(
200-
// fromResource: Resource,
201-
// fromPackageWithQualifiedName: QualifiedName?,
202-
// ): Boolean {
203-
// // Resolution failed in delegate scope
204-
// if (this == null) return false
205-
//
206-
// val obj = this.eObjectOrProxy
207-
//
208-
// // Local declarations are added later using custom scoping rules
209-
// if (obj.eResource() == fromResource) return false
210-
//
211-
// // Annotations cannot be referenced
212-
// if (obj is SdsAnnotation) return false
213-
//
214-
// // Internal steps in another package cannot be referenced
215-
// return !(
216-
// obj is SdsStep &&
217-
// obj.visibility() == SdsVisibility.Internal &&
218-
// obj.containingCompilationUnitOrNull()?.qualifiedNameOrNull() != fromPackageWithQualifiedName
219-
// )
220-
// }
221-
//
222-
// private fun scopeForMemberAccessDeclaration(context: SdsMemberAccess): IScope {
223-
// val receiver = context.receiver
224-
//
225-
// // Static access
226-
// val receiverDeclaration = when (receiver) {
227-
// is SdsReference -> receiver.declaration
228-
// is SdsMemberAccess -> receiver.member.declaration
229-
// else -> null
230-
// }
231-
// if (receiverDeclaration != null) {
232-
// when (receiverDeclaration) {
233-
// is SdsClass -> {
234-
// val members = receiverDeclaration.classMembersOrEmpty().filter { it.isStatic() }
235-
// val superTypeMembers = receiverDeclaration.superClassMembers()
236-
// .filter { it.isStatic() }
237-
// .toList()
238-
//
239-
// return Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers))
240-
// }
241-
// is SdsEnum -> {
242-
// return Scopes.scopeFor(receiverDeclaration.variantsOrEmpty())
243-
// }
244-
// }
245-
// }
246-
//
247-
// // Call results
248-
// var resultScope = IScope.NULLSCOPE
249-
// if (receiver is SdsCall) {
250-
// val results = receiver.resultsOrNull()
251-
// when {
252-
// results == null -> return IScope.NULLSCOPE
253-
// results.size > 1 -> return Scopes.scopeFor(results)
254-
// results.size == 1 -> resultScope = Scopes.scopeFor(results)
255-
// }
256-
// }
257-
//
258-
// // Members
259-
// val type = (receiver.type() as? NamedType) ?: return resultScope
260-
//
261-
// return when {
262-
// type.isNullable && !context.isNullSafe -> resultScope
263-
// type is ClassType -> {
264-
// val members = type.sdsClass.classMembersOrEmpty().filter { !it.isStatic() }
265-
// val superTypeMembers = type.sdsClass.superClassMembers()
266-
// .filter { !it.isStatic() }
267-
// .toList()
268-
//
269-
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
270-
// }
271-
// type is EnumVariantType -> Scopes.scopeFor(type.sdsEnumVariant.parametersOrEmpty())
272-
// else -> resultScope
273-
// }
274-
// }
275-
//
276-
// private fun classMembers(context: SdsClass, parentScope: IScope): IScope {
277-
// return when (val containingClassOrNull = context.containingClassOrNull()) {
278-
// is SdsClass -> Scopes.scopeFor(
279-
// context.classMembersOrEmpty(),
280-
// classMembers(containingClassOrNull, parentScope),
281-
// )
282-
// else -> Scopes.scopeFor(context.classMembersOrEmpty(), parentScope)
283-
// }
284-
// }
285-
286294
private getScopeForYieldResult(node: SdsYield): Scope {
287295
const containingSegment = getContainerOfType(node, isSdsSegment);
288296
if (!containingSegment) {

src/language/validation/names.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
resultsOrEmpty,
2121
typeParametersOrEmpty,
2222
enumVariantsOrEmpty,
23-
} from '../ast/shortcuts.js';
23+
} from '../helpers/shortcuts.js';
2424

2525
export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix';
2626
export const CODE_NAME_CASING = 'name/casing';

src/language/validation/other/imports.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ValidationAcceptor } from 'langium';
22
import { SdsImportAlias } from '../../generated/ast.js';
3-
import { isWildcardImport } from '../../ast/checks.js';
3+
import { isWildcardImport } from '../../helpers/checks.js';
44

55
export const CODE_IMPORT_WILDCARD_IMPORT_WITH_ALIAS = 'import/wildcard-import-with-alias';
66

src/language/validation/other/modules.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ValidationAcceptor } from 'langium';
22
import { isSdsDeclaration, isSdsPipeline, isSdsSegment, SdsModule } from '../../generated/ast.js';
3-
import { isInPipelineFile, isInStubFile } from '../../constants/fileExtensions.js';
3+
import { isInPipelineFile, isInStubFile } from '../../helpers/fileExtensions.js';
44

55
export const CODE_MODULE_MISSING_PACKAGE = 'module/missing-package';
66

0 commit comments

Comments
 (0)