Skip to content

Commit

Permalink
feat: scoping for references to own static members (#582)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lars-reimann authored Sep 29, 2023
1 parent 3e88f02 commit 38afc07
Show file tree
Hide file tree
Showing 47 changed files with 705 additions and 182 deletions.
6 changes: 0 additions & 6 deletions src/language/ast/checks.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/language/builtins/safe-ds-workspace-manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices, URI } from 'langium';
import { WorkspaceFolder } from 'vscode-languageserver';
import { SAFE_DS_FILE_EXTENSIONS } from '../constants/fileExtensions.js';
import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js';
import { globSync } from 'glob';
import path from 'path';

Expand Down
2 changes: 1 addition & 1 deletion src/language/formatting/safe-ds-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from 'langium';
import * as ast from '../generated/ast.js';
import { SdsImport, SdsImportAlias, SdsModule } from '../generated/ast.js';
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../ast/shortcuts.js';
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/shortcuts.js';
import noSpace = Formatting.noSpace;
import newLine = Formatting.newLine;
import newLines = Formatting.newLines;
Expand Down
2 changes: 1 addition & 1 deletion src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ SdsString returns SdsString:
;

interface SdsReference extends SdsExpression {
target?: @SdsDeclaration
target: @SdsDeclaration
}

SdsReference returns SdsReference:
Expand Down
File renamed without changes.
19 changes: 19 additions & 0 deletions src/language/helpers/checks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { isSdsAttribute, isSdsClass, isSdsEnum, isSdsFunction, SdsClassMember, SdsImport } from '../generated/ast.js';

export const isStatic = (node: SdsClassMember): boolean => {
if (isSdsClass(node) || isSdsEnum(node)) {
return true;
} else if (isSdsAttribute(node)) {
return node.static;
} else if (isSdsFunction(node)) {
return node.static;
} else {
/* c8 ignore next 2 */
return false;
}
};

export const isWildcardImport = function (node: SdsImport): boolean {
const importedNamespace = node.importedNamespace ?? '';
return importedNamespace.endsWith('*');
};
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ export const literalsOrEmpty = function (node: SdsLiteralType | undefined): SdsL
return node?.literalList?.literals ?? [];
};

export const classMembersOrEmpty = function (node: SdsClass | undefined): SdsClassMember[] {
return node?.body?.members ?? [];
export const classMembersOrEmpty = function (
node: SdsClass | undefined,
filterFunction: (member: SdsClassMember) => boolean = () => true,
): SdsClassMember[] {
return node?.body?.members?.filter(filterFunction) ?? [];
};

export const enumVariantsOrEmpty = function (node: SdsEnum | undefined): SdsEnumVariant[] {
Expand Down
260 changes: 134 additions & 126 deletions src/language/scoping/safe-ds-scope-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
isSdsSegment,
isSdsStatement,
isSdsYield,
SdsDeclaration,
SdsExpression,
SdsMemberAccess,
SdsMemberType,
SdsNamedTypeDeclaration,
Expand All @@ -33,8 +35,16 @@ import {
SdsType,
SdsYield,
} from '../generated/ast.js';
import { assigneesOrEmpty, parametersOrEmpty, resultsOrEmpty, statementsOrEmpty } from '../ast/shortcuts.js';
import { isContainedIn } from '../ast/utils.js';
import {
assigneesOrEmpty,
classMembersOrEmpty,
enumVariantsOrEmpty,
parametersOrEmpty,
resultsOrEmpty,
statementsOrEmpty,
} from '../helpers/shortcuts.js';
import { isContainedIn } from '../helpers/ast.js';
import { isStatic } from '../helpers/checks.js';

export class SafeDsScopeProvider extends DefaultScopeProvider {
override getScope(context: ReferenceInfo): Scope {
Expand Down Expand Up @@ -80,31 +90,145 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
* Returns the unique declaration that is referenced by this type. If the type references none or multiple
* declarations, undefined is returned.
*
* @param type The type to get the referenced declaration for.
* @param node The type to get the referenced declaration for.
* @returns The referenced declaration or undefined.
*/
private getUniqueReferencedDeclarationForType(type: SdsType): SdsNamedTypeDeclaration | undefined {
if (isSdsNamedType(type)) {
return type.declaration.ref;
} else if (isSdsMemberType(type)) {
return type.member.declaration.ref;
private getUniqueReferencedDeclarationForType(node: SdsType): SdsNamedTypeDeclaration | undefined {
if (isSdsNamedType(node)) {
return node.declaration.ref;
} else if (isSdsMemberType(node)) {
return node.member.declaration.ref;
} else {
return undefined;
}
}

private getScopeForMemberAccessMember(_node: SdsMemberAccess): Scope {
return EMPTY_SCOPE;
private getScopeForMemberAccessMember(node: SdsMemberAccess): Scope {
let currentScope = EMPTY_SCOPE;

// Static access
const declaration = this.getUniqueReferencedDeclarationForExpression(node.receiver);
if (isSdsClass(declaration)) {
currentScope = this.createScopeForNodes(classMembersOrEmpty(declaration, isStatic));

// val superTypeMembers = receiverDeclaration.superClassMembers()
// .filter { it.isStatic() }
// .toList()
//
// return Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers))
} else if (isSdsEnum(declaration)) {
currentScope = this.createScopeForNodes(enumVariantsOrEmpty(declaration));
}

// // Call results
// var resultScope = IScope.NULLSCOPE
// if (receiver is SdsCall) {
// val results = receiver.resultsOrNull()
// when {
// results == null -> return IScope.NULLSCOPE
// results.size > 1 -> return Scopes.scopeFor(results)
// results.size == 1 -> resultScope = Scopes.scopeFor(results)
// }
// }
//
// // Members
// val type = (receiver.type() as? NamedType) ?: return resultScope
//
// return when {
// type.isNullable && !context.isNullSafe -> resultScope
// type is ClassType -> {
// val members = type.sdsClass.classMembersOrEmpty().filter { !it.isStatic() }
// val superTypeMembers = type.sdsClass.superClassMembers()
// .filter { !it.isStatic() }
// .toList()
//
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
// }
// type is EnumVariantType -> Scopes.scopeFor(type.sdsEnumVariant.parametersOrEmpty())
// else -> resultScope
// }

return currentScope;
}

/**
* Returns the unique declaration that is referenced by this expression. If the expression references none or
* multiple declarations, undefined is returned.
*
* @param node The expression to get the referenced declaration for.
* @returns The referenced declaration or undefined.
*/
private getUniqueReferencedDeclarationForExpression(node: SdsExpression): SdsDeclaration | undefined {
if (isSdsReference(node)) {
return node.target.ref;
} else if (isSdsMemberAccess(node)) {
return node.member.target.ref;
} else {
return undefined;
}
}

private getScopeForDirectReferenceTarget(node: SdsReference): Scope {
// val resource = context.eResource()
// val packageName = context.containingCompilationUnitOrNull()?.qualifiedNameOrNull()
//
// // Declarations in other files
// var result: IScope = FilteringScope(
// super.delegateGetScope(context, SafeDSPackage.Literals.SDS_REFERENCE__DECLARATION),
// ) {
// it.isReferencableExternalDeclaration(resource, packageName)
// }

// Declarations in this file
const currentScope = this.globalDeclarationsInSameFile(node, EMPTY_SCOPE);

// // Declarations in containing classes
// context.containingClassOrNull()?.let {
// result = classMembers(it, result)
// }
//

// Declarations in containing blocks
return this.localDeclarations(node, currentScope);
}

// private fun classMembers(context: SdsClass, parentScope: IScope): IScope {
// return when (val containingClassOrNull = context.containingClassOrNull()) {
// is SdsClass -> Scopes.scopeFor(
// context.classMembersOrEmpty(),
// classMembers(containingClassOrNull, parentScope),
// )
// else -> Scopes.scopeFor(context.classMembersOrEmpty(), parentScope)
// }
// }

// /**
// * Removes declarations in this [Resource], [SdsAnnotation]s, and internal [SdsStep]s located in other
// * [SdsCompilationUnit]s.
// */
// private fun IEObjectDescription?.isReferencableExternalDeclaration(
// fromResource: Resource,
// fromPackageWithQualifiedName: QualifiedName?,
// ): Boolean {
// // Resolution failed in delegate scope
// if (this == null) return false
//
// val obj = this.eObjectOrProxy
//
// // Local declarations are added later using custom scoping rules
// if (obj.eResource() == fromResource) return false
//
// // Annotations cannot be referenced
// if (obj is SdsAnnotation) return false
//
// // Internal steps in another package cannot be referenced
// return !(
// obj is SdsStep &&
// obj.visibility() == SdsVisibility.Internal &&
// obj.containingCompilationUnitOrNull()?.qualifiedNameOrNull() != fromPackageWithQualifiedName
// )
// }

private globalDeclarationsInSameFile(node: AstNode, outerScope: Scope): Scope {
const module = getContainerOfType(node, isSdsModule);
if (!module) {
Expand Down Expand Up @@ -167,122 +291,6 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
}
}

// private fun scopeForReferenceDeclaration(context: SdsReference): IScope {
// val resource = context.eResource()
// val packageName = context.containingCompilationUnitOrNull()?.qualifiedNameOrNull()
//
// // Declarations in other files
// var result: IScope = FilteringScope(
// super.delegateGetScope(context, SafeDSPackage.Literals.SDS_REFERENCE__DECLARATION),
// ) {
// it.isReferencableExternalDeclaration(resource, packageName)
// }
//
// // Declarations in this file
// result = declarationsInSameFile(resource, result)
//
// // Declarations in containing classes
// context.containingClassOrNull()?.let {
// result = classMembers(it, result)
// }
//
// // Declarations in containing blocks
// localDeclarations(context, result)
// }
// }
// }
//
// /**
// * Removes declarations in this [Resource], [SdsAnnotation]s, and internal [SdsStep]s located in other
// * [SdsCompilationUnit]s.
// */
// private fun IEObjectDescription?.isReferencableExternalDeclaration(
// fromResource: Resource,
// fromPackageWithQualifiedName: QualifiedName?,
// ): Boolean {
// // Resolution failed in delegate scope
// if (this == null) return false
//
// val obj = this.eObjectOrProxy
//
// // Local declarations are added later using custom scoping rules
// if (obj.eResource() == fromResource) return false
//
// // Annotations cannot be referenced
// if (obj is SdsAnnotation) return false
//
// // Internal steps in another package cannot be referenced
// return !(
// obj is SdsStep &&
// obj.visibility() == SdsVisibility.Internal &&
// obj.containingCompilationUnitOrNull()?.qualifiedNameOrNull() != fromPackageWithQualifiedName
// )
// }
//
// private fun scopeForMemberAccessDeclaration(context: SdsMemberAccess): IScope {
// val receiver = context.receiver
//
// // Static access
// val receiverDeclaration = when (receiver) {
// is SdsReference -> receiver.declaration
// is SdsMemberAccess -> receiver.member.declaration
// else -> null
// }
// if (receiverDeclaration != null) {
// when (receiverDeclaration) {
// is SdsClass -> {
// val members = receiverDeclaration.classMembersOrEmpty().filter { it.isStatic() }
// val superTypeMembers = receiverDeclaration.superClassMembers()
// .filter { it.isStatic() }
// .toList()
//
// return Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers))
// }
// is SdsEnum -> {
// return Scopes.scopeFor(receiverDeclaration.variantsOrEmpty())
// }
// }
// }
//
// // Call results
// var resultScope = IScope.NULLSCOPE
// if (receiver is SdsCall) {
// val results = receiver.resultsOrNull()
// when {
// results == null -> return IScope.NULLSCOPE
// results.size > 1 -> return Scopes.scopeFor(results)
// results.size == 1 -> resultScope = Scopes.scopeFor(results)
// }
// }
//
// // Members
// val type = (receiver.type() as? NamedType) ?: return resultScope
//
// return when {
// type.isNullable && !context.isNullSafe -> resultScope
// type is ClassType -> {
// val members = type.sdsClass.classMembersOrEmpty().filter { !it.isStatic() }
// val superTypeMembers = type.sdsClass.superClassMembers()
// .filter { !it.isStatic() }
// .toList()
//
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
// }
// type is EnumVariantType -> Scopes.scopeFor(type.sdsEnumVariant.parametersOrEmpty())
// else -> resultScope
// }
// }
//
// private fun classMembers(context: SdsClass, parentScope: IScope): IScope {
// return when (val containingClassOrNull = context.containingClassOrNull()) {
// is SdsClass -> Scopes.scopeFor(
// context.classMembersOrEmpty(),
// classMembers(containingClassOrNull, parentScope),
// )
// else -> Scopes.scopeFor(context.classMembersOrEmpty(), parentScope)
// }
// }

private getScopeForYieldResult(node: SdsYield): Scope {
const containingSegment = getContainerOfType(node, isSdsSegment);
if (!containingSegment) {
Expand Down
2 changes: 1 addition & 1 deletion src/language/validation/names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
resultsOrEmpty,
typeParametersOrEmpty,
enumVariantsOrEmpty,
} from '../ast/shortcuts.js';
} from '../helpers/shortcuts.js';

export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix';
export const CODE_NAME_CASING = 'name/casing';
Expand Down
2 changes: 1 addition & 1 deletion src/language/validation/other/imports.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ValidationAcceptor } from 'langium';
import { SdsImportAlias } from '../../generated/ast.js';
import { isWildcardImport } from '../../ast/checks.js';
import { isWildcardImport } from '../../helpers/checks.js';

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

Expand Down
2 changes: 1 addition & 1 deletion src/language/validation/other/modules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ValidationAcceptor } from 'langium';
import { isSdsDeclaration, isSdsPipeline, isSdsSegment, SdsModule } from '../../generated/ast.js';
import { isInPipelineFile, isInStubFile } from '../../constants/fileExtensions.js';
import { isInPipelineFile, isInStubFile } from '../../helpers/fileExtensions.js';

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

Expand Down
Loading

0 comments on commit 38afc07

Please sign in to comment.