Skip to content

Commit

Permalink
feat: scoping for own members (#611)
Browse files Browse the repository at this point in the history
Closes partially #540

### Summary of Changes

Implement scoping for own instance members, namely to
* instance attributes of classes,
* instance methods of classes,
* parameters of enum variants.
  • Loading branch information
lars-reimann authored Oct 7, 2023
1 parent 2a3ef33 commit 43b276f
Show file tree
Hide file tree
Showing 20 changed files with 275 additions and 74 deletions.
4 changes: 2 additions & 2 deletions src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,10 @@ SdsChainedExpression returns SdsExpression:
{SdsCall.receiver=current}
argumentList=SdsCallArgumentList

| {SdsIndexedAccess.receiver=current}
| {SdsIndexedAccess.receiver=current}
'[' index=SdsExpression ']'

| {SdsMemberAccess.receiver=current}
| {SdsMemberAccess.receiver=current}
(isNullSafe?='?')?
'.'
member=SdsReference
Expand Down
51 changes: 26 additions & 25 deletions src/language/scoping/safe-ds-scope-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
import { SafeDsPackageManager } from '../workspace/safe-ds-package-manager.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import { ClassType, EnumVariantType } from '../typing/model.js';

export class SafeDsScopeProvider extends DefaultScopeProvider {
private readonly astReflection: AstReflection;
Expand Down Expand Up @@ -176,13 +177,13 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
// Static access
const declaration = this.getUniqueReferencedDeclarationForExpression(node.receiver);
if (isSdsClass(declaration)) {
return this.createScopeForNodes(classMembersOrEmpty(declaration, isStatic));

// val superTypeMembers = receiverDeclaration.superClassMembers()
// .filter { it.isStatic() }
// .toList()
const ownStaticMembers = classMembersOrEmpty(declaration, isStatic);
// val superTypeMembers = receiverDeclaration.superClassMembers()
// .filter { it.isStatic() }
// .toList()
//
// return Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers))
// return Scopes.scopeFor(ownStaticMembers, Scopes.scopeFor(superTypeMembers))
return this.createScopeForNodes(ownStaticMembers);
} else if (isSdsEnum(declaration)) {
return this.createScopeForNodes(enumVariantsOrEmpty(declaration));
}
Expand All @@ -193,9 +194,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
const callable = this.nodeMapper.callToCallableOrUndefined(node.receiver);
const results = abstractResultsOrEmpty(callable);

if (results.length === 0) {
return EMPTY_SCOPE;
} else if (results.length > 1) {
if (results.length > 1) {
return this.createScopeForNodes(results);
} else {
// If there is only one result, it can be accessed by name but members of the result with the same name
Expand All @@ -204,22 +203,24 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
}
}

// // 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
// }
// Members
let receiverType = this.typeComputer.computeType(node.receiver);
if (receiverType.isNullable && !node.isNullSafe) {
return resultScope;
}

if (receiverType instanceof ClassType) {
const ownInstanceMembers = classMembersOrEmpty(receiverType.sdsClass, (it) => !isStatic(it));
// val superTypeMembers = type.sdsClass.superClassMembers()
// .filter { !it.isStatic() }
// .toList()
//
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
return this.createScopeForNodes(ownInstanceMembers, resultScope);
} else if (receiverType instanceof EnumVariantType) {
const parameters = parametersOrEmpty(receiverType.sdsEnumVariant);
return this.createScopeForNodes(parameters, resultScope);
}

return resultScope;
}
Expand Down
15 changes: 15 additions & 0 deletions src/language/typing/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
export abstract class Type {
abstract isNullable: boolean;

unwrap(): Type {
return this;
}

abstract copyWithNullability(isNullable: boolean): Type;

abstract equals(other: Type): boolean;
Expand Down Expand Up @@ -110,6 +114,17 @@ export class NamedTupleType extends Type {
return this.entries.length;
}

/**
* If this only has one entry, returns its type. Otherwise, returns this.
*/
override unwrap(): Type {
if (this.entries.length === 1) {
return this.entries[0].type;
}

return this;
}

override copyWithNullability(_isNullable: boolean): NamedTupleType {
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion src/language/typing/safe-ds-type-computer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class SafeDsTypeComputer {
const documentUri = getDocument(node).uri.toString();
const nodePath = this.astNodeLocator.getAstNodePath(node);
const key = `${documentUri}~${nodePath}`;
return this.typeCache.get(key, () => this.doComputeType(node));
return this.typeCache.get(key, () => this.doComputeType(node).unwrap());
}

// fun SdsAbstractObject.hasPrimitiveType(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,57 @@ class MyClass {

class AnotherClass

fun nullableMyClass() -> result: MyClass?

pipeline myPipeline {
// $TEST$ references myInstanceAttribute
val myClass = MyClass();
myClass.»myInstanceAttribute«;


// $TEST$ references redeclaredAsInstanceAttribute
MyClass().»redeclaredAsInstanceAttribute«;

// $TEST$ references redeclaredAsStaticAttribute
MyClass().»redeclaredAsStaticAttribute«;

// $TEST$ references redeclaredAsNestedClass
MyClass().»redeclaredAsNestedClass«;

// $TEST$ references redeclaredAsNestedEnum
MyClass().»redeclaredAsNestedEnum«;

// $TEST$ references redeclaredAsInstanceMethod
MyClass().»redeclaredAsInstanceMethod«;

// $TEST$ references redeclaredAsStaticMethod
MyClass().»redeclaredAsStaticMethod«;

// $TEST$ references declaredPreviouslyAsStaticAttribute
MyClass().»declaredPreviouslyAsStaticAttribute«;

// $TEST$ references declaredPreviouslyAsNestedClass
MyClass().»declaredPreviouslyAsNestedClass«;

// $TEST$ references declaredPreviouslyAsNestedEnum
MyClass().»declaredPreviouslyAsNestedEnum«;

// $TEST$ references declaredPreviouslyAsStaticMethod
MyClass().»declaredPreviouslyAsStaticMethod«;

// $TEST$ references myInstanceAttribute
nullableMyClass()?.»myInstanceAttribute«;


// $TEST$ unresolved
MyClass.»myInstanceAttribute«;

// $TEST$ unresolved
AnotherClass().»myInstanceAttribute«;

// $TEST$ unresolved
nullableMyClass().»myInstanceAttribute«;

// $TEST$ unresolved
unresolved.»myInstanceAttribute«;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,57 @@ class MyClass {

class AnotherClass

fun nullableMyClass() -> result: MyClass?

pipeline myPipeline {
// $TEST$ references myInstanceMethod
val myClass = MyClass();
myClass.»myInstanceMethod«();


// $TEST$ references redeclaredAsInstanceAttribute
MyClass().»redeclaredAsInstanceAttribute«();

// $TEST$ references redeclaredAsStaticAttribute
MyClass().»redeclaredAsStaticAttribute«();

// $TEST$ references redeclaredAsNestedClass
MyClass().»redeclaredAsNestedClass«();

// $TEST$ references redeclaredAsNestedEnum
MyClass().»redeclaredAsNestedEnum«();

// $TEST$ references redeclaredAsInstanceMethod
MyClass().»redeclaredAsInstanceMethod«();

// $TEST$ references redeclaredAsStaticMethod
MyClass().»redeclaredAsStaticMethod«();

// $TEST$ references declaredPreviouslyAsStaticAttribute
MyClass().»declaredPreviouslyAsStaticAttribute«();

// $TEST$ references declaredPreviouslyAsNestedClass
MyClass().»declaredPreviouslyAsNestedClass«();

// $TEST$ references declaredPreviouslyAsNestedEnum
MyClass().»declaredPreviouslyAsNestedEnum«();

// $TEST$ references declaredPreviouslyAsStaticMethod
MyClass().»declaredPreviouslyAsStaticMethod«();

// $TEST$ references myInstanceMethod
nullableMyClass()?.»myInstanceMethod«();


// $TEST$ unresolved
MyClass.»myInstanceMethod«;

// $TEST$ unresolved
AnotherClass().»myInstanceMethod«;

// $TEST$ unresolved
nullableMyClass().»myInstanceAttribute«;

// $TEST$ unresolved
unresolved.»myInstanceMethod«;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package tests.scoping.memberAccesses.toParametersOfEnumVariants

enum MyEnum {
MyEnumVariant(
// $TEST$ target param
»param«: Int,

// $TEST$ target redeclared
»redeclared«: Int,
redeclared: Int,
)

MyOtherEnumVariant
}

enum MyOtherEnum {
MyEnumVariant
}

pipeline myPipeline {
// $TEST$ references param
MyEnum.MyEnumVariant().»param«;

// $TEST$ references redeclared
MyEnum.MyEnumVariant().»redeclared«;


// $TEST$ unresolved
MyOtherEnum.MyEnumVariant.»param«;

// $TEST$ unresolved
MyEnum.MyOtherEnumVariant().»param«;

// $TEST$ unresolved
MyOtherEnum.MyEnumVariant().»param«;

// $TEST$ unresolved
MyEnum.MyEnumVariant().»unresolved«;

// $TEST$ unresolved
MyEnum.unresolved().»param«;

// $TEST$ unresolved
unresolved.MyEnumVariant().»param«;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@ class MyClass() {
attr »result«: Int
}

enum MyEnum {
// $TEST$ target MyEnum_result
MyEnumVariant(»result«: Int)
}

pipeline myPipeline {
val lambdaWithOneResultWithIdenticalMember = () {
val f1 = () {
yield result = MyClass();
};

val f2 = () {
yield result = MyEnum.MyEnumVariant(0);
};

// $TEST$ references MyClass_result
lambdaWithOneResultWithIdenticalMember().»result«;
f1().»result«;

// $TEST$ references MyEnum_result
f2().»result«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package tests.scoping.memberAccesses.toResults.ofCallableTypes.matchingMember

class MyClass() {
// $TEST$ target MyClass_result
attr »result«: Int
}

enum MyEnum {
// $TEST$ target MyEnum_result
MyEnumVariant(»result«: Int)
}

segment mySegment(
f1: () -> result: MyClass,
f2: () -> result: MyEnum.MyEnumVariant,
) {

// $TEST$ references MyClass_result
f1().»result«;

// $TEST$ references MyEnum_result
f2().»result«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tests.scoping.memberAccesses.toResults.ofFunctions.matchingMember

class MyClass() {
// $TEST$ target MyClass_result
attr »result«: Int
}

enum MyEnum {
// $TEST$ target MyEnum_result
MyEnumVariant(»result«: Int)
}

fun f1() -> result: MyClass
fun f2() -> result: MyEnum.MyEnumVariant

pipeline myPipeline {
// $TEST$ references MyClass_result
f1().»result«;

// $TEST$ references MyEnum_result
f2().»result«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tests.scoping.memberAccesses.toResults.ofSegments.matchingMember

class MyClass() {
// $TEST$ target MyClass_result
attr »result«: Int
}

enum MyEnum {
// $TEST$ target MyEnum_result
MyEnumVariant(»result«: Int)
}

segment s1() -> result: MyClass {}
segment s2() -> result: MyEnum.MyEnumVariant {}

pipeline myPipeline {
// $TEST$ references MyClass_result
s1().»result«;

// $TEST$ references MyEnum_result
s2().»result«;
}
Loading

0 comments on commit 43b276f

Please sign in to comment.