Skip to content

Commit

Permalink
feat: scoping of annotation calls, type parameter constraints & yields (
Browse files Browse the repository at this point in the history
#561)

Closes partially #540

### Summary of Changes

* Implement scoping for
  * annotation calls (_annotation_)
  * type parameter constraints (_left operand_) 
  * yields (_result_)
  • Loading branch information
lars-reimann authored Sep 18, 2023
1 parent 4bde898 commit a510f2b
Show file tree
Hide file tree
Showing 30 changed files with 537 additions and 58 deletions.
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 @@ -20,7 +20,7 @@ const newLinesWithIndent = function (count: number, options?: FormattingActionOp
};
};

export class SafeDSFormatter extends AbstractFormatter {
export class SafeDsFormatter extends AbstractFormatter {
protected override format(node: AstNode): void {
// -----------------------------------------------------------------------------
// Module
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 @@ -373,7 +373,7 @@ SdsTypeParameterConstraintOperator returns string:
// -----------------------------------------------------------------------------

interface SdsCallable extends SdsObject {
parameterList: SdsParameterList
parameterList?: SdsParameterList
}

interface SdsParameterList extends SdsObject {
Expand Down
6 changes: 5 additions & 1 deletion src/language/helpers/astShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
SdsAnnotatedObject,
SdsAnnotationCall,
SdsLiteral,
SdsLiteralType,
SdsLiteralType, SdsResult, SdsResultList,
SdsTypeArgument,
SdsTypeArgumentList,
} from '../generated/ast.js';
Expand All @@ -21,6 +21,10 @@ export const literalsOrEmpty = function (node: SdsLiteralType | undefined): SdsL
return node?.literalList?.literals ?? [];
};

export const resultsOrEmpty = function (node: SdsResultList | undefined): SdsResult[] {
return node?.results ?? [];
}

export const typeArgumentsOrEmpty = function (node: SdsTypeArgumentList | undefined): SdsTypeArgument[] {
return node?.typeArguments ?? [];
};
10 changes: 8 additions & 2 deletions src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import {
} from 'langium';
import { SafeDsGeneratedModule, SafeDsGeneratedSharedModule } from './generated/module.js';
import { SafeDsValidator, registerValidationChecks } from './validation/safe-ds-validator.js';
import { SafeDSFormatter } from './formatting/safe-ds-formatter.js';
import { SafeDsFormatter } from './formatting/safe-ds-formatter.js';
import { SafeDsWorkspaceManager } from './builtins/workspaceManager.js';
import {SafeDsScopeComputation} from "./scoping/safe-ds-scope-computation.js";
import {SafeDsScopeProvider} from "./scoping/safe-ds-scope-provider.js";

/**
* Declaration of custom services - add your own service classes here.
Expand All @@ -36,7 +38,11 @@ export type SafeDsServices = LangiumServices & SafeDsAddedServices;
*/
export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeDsAddedServices> = {
lsp: {
Formatter: () => new SafeDSFormatter(),
Formatter: () => new SafeDsFormatter(),
},
references: {
ScopeComputation: (services) => new SafeDsScopeComputation(services),
ScopeProvider: (services) => new SafeDsScopeProvider(services),
},
validation: {
SafeDsValidator: () => new SafeDsValidator(),
Expand Down
66 changes: 66 additions & 0 deletions src/language/scoping/safe-ds-scope-computation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
AstNode,
AstNodeDescription,
DefaultScopeComputation,
getContainerOfType,
LangiumDocument,
PrecomputedScopes,
} from 'langium';
import {
isSdsClass,
isSdsEnumVariant,
isSdsFunction,
isSdsTypeParameter,
isSdsTypeParameterList,
SdsTypeParameter,
} from '../generated/ast.js';

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

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

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

if (isSdsClass(containingDeclaration)) {
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.parameterList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.constraintList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.body, description);
} else if (isSdsEnumVariant(containingDeclaration)) {
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.parameterList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.constraintList, description);
} else if (isSdsFunction(containingDeclaration)) {
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.parameterList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.resultList, description);
this.addToScopesIfKeyIsDefined(scopes, containingDeclaration.constraintList, description);
}
}

/**
* Adds the key/value pair to the scopes if the key is defined.
*
* @param scopes The scopes to add the key/value pair to.
* @param key The key.
* @param value The value.
*/
addToScopesIfKeyIsDefined(scopes: PrecomputedScopes, key: AstNode | undefined, value: AstNodeDescription): void {
if (key) {
scopes.add(key, value);
}
}
}
22 changes: 22 additions & 0 deletions src/language/scoping/safe-ds-scope-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { DefaultScopeProvider, EMPTY_SCOPE, getContainerOfType, ReferenceInfo, Scope } from 'langium';
import { isSdsSegment, isSdsYield } 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);
} else {
return super.getScope(context);
}
}

getScopeForYieldResult(context: ReferenceInfo): Scope {
const containingSegment = getContainerOfType(context.container, isSdsSegment);
if (!containingSegment) {
return EMPTY_SCOPE;
}

return this.createScopeForNodes(resultsOrEmpty(containingSegment.resultList));
}
}
43 changes: 0 additions & 43 deletions src/resources/builtins/safeds/lang/schemaEffects.sdsstub

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.scoping.annotationCalls.onAnnotation

// $TEST$ target before
annotation »Before«

// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
annotation MyAnnotation

// $TEST$ target after
annotation »After«
17 changes: 17 additions & 0 deletions tests/resources/scoping/annotation calls/on attribute/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package test.scoping.annotationCalls.onAttribute

// $TEST$ target before
annotation »Before«

class MyClass {
// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
attr myAttribute: Int
}

// $TEST$ target after
annotation »After«
15 changes: 15 additions & 0 deletions tests/resources/scoping/annotation calls/on class/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.scoping.annotationCalls.onClass

// $TEST$ target before
annotation »Before«

// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
class MyClass

// $TEST$ target after
annotation »After«
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package test.scoping.annotationCalls.onEnumVariant

// $TEST$ target before
annotation »Before«

enum MyEnum {
// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
MyEnumVariant
}

// $TEST$ target after
annotation »After«
15 changes: 15 additions & 0 deletions tests/resources/scoping/annotation calls/on enum/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.scoping.annotationCalls.onEnum

// $TEST$ target before
annotation »Before«

// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
enum MyEnum

// $TEST$ target after
annotation »After«
15 changes: 15 additions & 0 deletions tests/resources/scoping/annotation calls/on function/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.scoping.annotationCalls.onFunction

// $TEST$ target before
annotation »Before«

// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
fun myFunction()

// $TEST$ target after
annotation »After«
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// $TEST$ references annotation
@»MyAnnotation«
// $TEST$ unresolved
@»Unresolved«

package test.scoping.annotationCalls.onModule

// $TEST$ target annotation
annotation »MyAnnotation«
17 changes: 17 additions & 0 deletions tests/resources/scoping/annotation calls/on parameter/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package test.scoping.annotationCalls.onParameter

// $TEST$ target before
annotation »Before«

fun myFunction(
// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
myParameter: Int
)

// $TEST$ target after
annotation »After«
15 changes: 15 additions & 0 deletions tests/resources/scoping/annotation calls/on pipeline/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.scoping.annotationCalls.onPipeline

// $TEST$ target before
annotation »Before«

// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
pipeline myPipeline {}

// $TEST$ target after
annotation »After«
17 changes: 17 additions & 0 deletions tests/resources/scoping/annotation calls/on result/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package test.scoping.annotationCalls.onResult

// $TEST$ target before
annotation »Before«

fun myFunction() -> (
// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
myResult: Int
)

// $TEST$ target after
annotation »After«
15 changes: 15 additions & 0 deletions tests/resources/scoping/annotation calls/on segment/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.scoping.annotationCalls.onSegment

// $TEST$ target before
annotation »Before«

// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
segment mySegment() {}

// $TEST$ target after
annotation »After«
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package test.scoping.annotationCalls.onTypeParameter

// $TEST$ target before
annotation »Before«

fun myFunction<
// $TEST$ references before
@»Before«
// $TEST$ references after
@»After«
// $TEST$ unresolved
@»Unresolved«
MyTypeParameter
>()

// $TEST$ target after
annotation »After«
Loading

0 comments on commit a510f2b

Please sign in to comment.