Skip to content

Commit

Permalink
feat: scoping for named types (#563)
Browse files Browse the repository at this point in the history
Closes partially #540

### Summary of Changes

* Implement scoping for named types

---------

Co-authored-by: megalinter-bot <[email protected]>
  • Loading branch information
lars-reimann and megalinter-bot authored Sep 19, 2023
1 parent a510f2b commit a877f4c
Show file tree
Hide file tree
Showing 37 changed files with 873 additions and 8 deletions.
51 changes: 50 additions & 1 deletion src/language/scoping/safe-ds-scope-computation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,82 @@ import {
} from 'langium';
import {
isSdsClass,
isSdsDeclaration,
isSdsEnum,
isSdsEnumVariant,
isSdsFunction,
isSdsModule,
isSdsTypeParameter,
isSdsTypeParameterList,
SdsClass,
SdsEnum,
SdsTypeParameter,
} from '../generated/ast.js';

export class SafeDsScopeComputation extends DefaultScopeComputation {
override processNode(node: AstNode, document: LangiumDocument, scopes: PrecomputedScopes): void {
if (isSdsTypeParameter(node)) {
if (isSdsClass(node)) {
this.processSdsClass(node, document, scopes);
} else if (isSdsEnum(node)) {
this.processSdsEnum(node, document, scopes);
} else if (isSdsTypeParameter(node)) {
this.processSdsTypeParameter(node, document, scopes);
} else {
super.processNode(node, document, scopes);
}
}

private processSdsClass(node: SdsClass, document: LangiumDocument, scopes: PrecomputedScopes): void {
const name = this.nameProvider.getName(node);
if (!name) {
return;
}

const description = this.descriptions.createDescription(node, name, document);

this.addToScopesIfKeyIsDefined(scopes, node.parameterList, description);
this.addToScopesIfKeyIsDefined(scopes, node.constraintList, description);
this.addToScopesIfKeyIsDefined(scopes, node.body, description);

const containingDeclaration = getContainerOfType(node.$container, isSdsDeclaration);
if (isSdsModule(containingDeclaration)) {
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration, description);
}
}

private processSdsEnum(node: SdsEnum, document: LangiumDocument, scopes: PrecomputedScopes): void {
const name = this.nameProvider.getName(node);
if (!name) {
return;
}

const description = this.descriptions.createDescription(node, name, document);

this.addToScopesIfKeyIsDefined(scopes, node.body, description);

const containingDeclaration = getContainerOfType(node.$container, isSdsDeclaration);
if (isSdsModule(containingDeclaration)) {
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration, description);
}
}

private processSdsTypeParameter(
node: SdsTypeParameter,
document: LangiumDocument,
scopes: PrecomputedScopes,
): void {
const containingDeclaration = getContainerOfType(node, isSdsTypeParameterList)?.$container;
if (!containingDeclaration) {
/* c8 ignore next 2 */
return;
}

const name = this.nameProvider.getName(node);
if (!name) {
/* c8 ignore next 2 */
return;
}

const description = this.descriptions.createDescription(node, name, document);

if (isSdsClass(containingDeclaration)) {
Expand Down
64 changes: 59 additions & 5 deletions src/language/scoping/safe-ds-scope-provider.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,72 @@
import { DefaultScopeProvider, EMPTY_SCOPE, getContainerOfType, ReferenceInfo, Scope } from 'langium';
import { isSdsSegment, isSdsYield } from '../generated/ast.js';
import {
isSdsClass,
isSdsEnum,
isSdsMemberType,
isSdsNamedType,
isSdsNamedTypeDeclaration,
isSdsSegment,
isSdsYield,
SdsMemberType,
SdsNamedTypeDeclaration,
SdsType,
SdsYield,
} from '../generated/ast.js';
import { resultsOrEmpty } from '../helpers/astShortcuts.js';

export class SafeDsScopeProvider extends DefaultScopeProvider {
override getScope(context: ReferenceInfo): Scope {
if (isSdsYield(context.container) && context.property === 'result') {
return this.getScopeForYieldResult(context);
if (isSdsNamedType(context.container) && context.property === 'declaration') {
const node = context.container;

if (isSdsMemberType(node.$container) && node.$containerProperty === 'member') {
return this.getScopeForMemberTypeMember(node.$container);
} else {
return super.getScope(context);
}
} else if (isSdsYield(context.container) && context.property === 'result') {
return this.getScopeForYieldResult(context.container);
} else {
return super.getScope(context);
}
}

getScopeForYieldResult(context: ReferenceInfo): Scope {
const containingSegment = getContainerOfType(context.container, isSdsSegment);
private getScopeForMemberTypeMember(node: SdsMemberType): Scope {
const declaration = this.getUniqueReferencedDeclarationForType(node.receiver);
if (!declaration) {
return EMPTY_SCOPE;
}

if (isSdsClass(declaration)) {
const members = declaration.body?.members ?? [];
return this.createScopeForNodes(members.filter(isSdsNamedTypeDeclaration));
} else if (isSdsEnum(declaration)) {
const variants = declaration.body?.variants ?? [];
return this.createScopeForNodes(variants);
} else {
return EMPTY_SCOPE;
}
}

/**
* 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.
* @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;
} else {
return undefined;
}
}

private getScopeForYieldResult(node: SdsYield): Scope {
const containingSegment = getContainerOfType(node, isSdsSegment);
if (!containingSegment) {
return EMPTY_SCOPE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ annotation »Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
annotation MyAnnotation

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ class MyClass {
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
attr myAttribute: Int
}

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ annotation »Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
class MyClass

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ enum MyEnum {
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
MyEnumVariant
}

// $TEST$ target after
annotation »After«

class NotAnAnnotation
4 changes: 4 additions & 0 deletions tests/resources/scoping/annotation calls/on enum/main.sdstest
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ annotation »Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
enum MyEnum

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ annotation »Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
fun myFunction()

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// $TEST$ references annotation
@»MyAnnotation«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«

package test.scoping.annotationCalls.onModule

// $TEST$ target annotation
annotation »MyAnnotation«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ fun myFunction(
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
myParameter: Int
)

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ annotation »Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
pipeline myPipeline {}

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ fun myFunction() -> (
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
myResult: Int
)

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ annotation »Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
segment mySegment() {}

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ fun myFunction<
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»NotAnAnnotation«
// $TEST$ unresolved
@»Unresolved«
MyTypeParameter
>()

// $TEST$ target after
annotation »After«

class NotAnAnnotation
Loading

0 comments on commit a877f4c

Please sign in to comment.