Skip to content

Commit 43b276f

Browse files
authored
feat: scoping for own members (#611)
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.
1 parent 2a3ef33 commit 43b276f

File tree

20 files changed

+275
-74
lines changed

20 files changed

+275
-74
lines changed

src/language/grammar/safe-ds.langium

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -685,10 +685,10 @@ SdsChainedExpression returns SdsExpression:
685685
{SdsCall.receiver=current}
686686
argumentList=SdsCallArgumentList
687687

688-
| {SdsIndexedAccess.receiver=current}
688+
| {SdsIndexedAccess.receiver=current}
689689
'[' index=SdsExpression ']'
690690

691-
| {SdsMemberAccess.receiver=current}
691+
| {SdsMemberAccess.receiver=current}
692692
(isNullSafe?='?')?
693693
'.'
694694
member=SdsReference

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

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { SafeDsServices } from '../safe-ds-module.js';
6767
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
6868
import { SafeDsPackageManager } from '../workspace/safe-ds-package-manager.js';
6969
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
70+
import { ClassType, EnumVariantType } from '../typing/model.js';
7071

7172
export class SafeDsScopeProvider extends DefaultScopeProvider {
7273
private readonly astReflection: AstReflection;
@@ -176,13 +177,13 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
176177
// Static access
177178
const declaration = this.getUniqueReferencedDeclarationForExpression(node.receiver);
178179
if (isSdsClass(declaration)) {
179-
return this.createScopeForNodes(classMembersOrEmpty(declaration, isStatic));
180-
181-
// val superTypeMembers = receiverDeclaration.superClassMembers()
182-
// .filter { it.isStatic() }
183-
// .toList()
180+
const ownStaticMembers = classMembersOrEmpty(declaration, isStatic);
181+
// val superTypeMembers = receiverDeclaration.superClassMembers()
182+
// .filter { it.isStatic() }
183+
// .toList()
184184
//
185-
// return Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers))
185+
// return Scopes.scopeFor(ownStaticMembers, Scopes.scopeFor(superTypeMembers))
186+
return this.createScopeForNodes(ownStaticMembers);
186187
} else if (isSdsEnum(declaration)) {
187188
return this.createScopeForNodes(enumVariantsOrEmpty(declaration));
188189
}
@@ -193,9 +194,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
193194
const callable = this.nodeMapper.callToCallableOrUndefined(node.receiver);
194195
const results = abstractResultsOrEmpty(callable);
195196

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

207-
// // Members
208-
// val type = (receiver.type() as? NamedType) ?: return resultScope
209-
//
210-
// return when {
211-
// type.isNullable && !context.isNullSafe -> resultScope
212-
// type is ClassType -> {
213-
// val members = type.sdsClass.classMembersOrEmpty().filter { !it.isStatic() }
214-
// val superTypeMembers = type.sdsClass.superClassMembers()
215-
// .filter { !it.isStatic() }
216-
// .toList()
217-
//
218-
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
219-
// }
220-
// type is EnumVariantType -> Scopes.scopeFor(type.sdsEnumVariant.parametersOrEmpty())
221-
// else -> resultScope
222-
// }
206+
// Members
207+
let receiverType = this.typeComputer.computeType(node.receiver);
208+
if (receiverType.isNullable && !node.isNullSafe) {
209+
return resultScope;
210+
}
211+
212+
if (receiverType instanceof ClassType) {
213+
const ownInstanceMembers = classMembersOrEmpty(receiverType.sdsClass, (it) => !isStatic(it));
214+
// val superTypeMembers = type.sdsClass.superClassMembers()
215+
// .filter { !it.isStatic() }
216+
// .toList()
217+
//
218+
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
219+
return this.createScopeForNodes(ownInstanceMembers, resultScope);
220+
} else if (receiverType instanceof EnumVariantType) {
221+
const parameters = parametersOrEmpty(receiverType.sdsEnumVariant);
222+
return this.createScopeForNodes(parameters, resultScope);
223+
}
223224

224225
return resultScope;
225226
}

src/language/typing/model.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import {
1212
export abstract class Type {
1313
abstract isNullable: boolean;
1414

15+
unwrap(): Type {
16+
return this;
17+
}
18+
1519
abstract copyWithNullability(isNullable: boolean): Type;
1620

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

117+
/**
118+
* If this only has one entry, returns its type. Otherwise, returns this.
119+
*/
120+
override unwrap(): Type {
121+
if (this.entries.length === 1) {
122+
return this.entries[0].type;
123+
}
124+
125+
return this;
126+
}
127+
113128
override copyWithNullability(_isNullable: boolean): NamedTupleType {
114129
return this;
115130
}

src/language/typing/safe-ds-type-computer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class SafeDsTypeComputer {
104104
const documentUri = getDocument(node).uri.toString();
105105
const nodePath = this.astNodeLocator.getAstNodePath(node);
106106
const key = `${documentUri}~${nodePath}`;
107-
return this.typeCache.get(key, () => this.doComputeType(node));
107+
return this.typeCache.get(key, () => this.doComputeType(node).unwrap());
108108
}
109109

110110
// fun SdsAbstractObject.hasPrimitiveType(): Boolean {

tests/resources/scoping/member accesses/to class members/instance attributes/main.sdstest

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,57 @@ class MyClass {
4949

5050
class AnotherClass
5151

52+
fun nullableMyClass() -> result: MyClass?
53+
5254
pipeline myPipeline {
55+
// $TEST$ references myInstanceAttribute
56+
val myClass = MyClass();
57+
myClass.»myInstanceAttribute«;
58+
59+
60+
// $TEST$ references redeclaredAsInstanceAttribute
61+
MyClass().»redeclaredAsInstanceAttribute«;
62+
63+
// $TEST$ references redeclaredAsStaticAttribute
64+
MyClass().»redeclaredAsStaticAttribute«;
65+
66+
// $TEST$ references redeclaredAsNestedClass
67+
MyClass().»redeclaredAsNestedClass«;
68+
69+
// $TEST$ references redeclaredAsNestedEnum
70+
MyClass().»redeclaredAsNestedEnum«;
71+
72+
// $TEST$ references redeclaredAsInstanceMethod
73+
MyClass().»redeclaredAsInstanceMethod«;
74+
75+
// $TEST$ references redeclaredAsStaticMethod
76+
MyClass().»redeclaredAsStaticMethod«;
77+
78+
// $TEST$ references declaredPreviouslyAsStaticAttribute
79+
MyClass().»declaredPreviouslyAsStaticAttribute«;
80+
81+
// $TEST$ references declaredPreviouslyAsNestedClass
82+
MyClass().»declaredPreviouslyAsNestedClass«;
83+
84+
// $TEST$ references declaredPreviouslyAsNestedEnum
85+
MyClass().»declaredPreviouslyAsNestedEnum«;
86+
87+
// $TEST$ references declaredPreviouslyAsStaticMethod
88+
MyClass().»declaredPreviouslyAsStaticMethod«;
89+
90+
// $TEST$ references myInstanceAttribute
91+
nullableMyClass()?.»myInstanceAttribute«;
92+
93+
5394
// $TEST$ unresolved
5495
MyClass.»myInstanceAttribute«;
5596

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

100+
// $TEST$ unresolved
101+
nullableMyClass().»myInstanceAttribute«;
102+
59103
// $TEST$ unresolved
60104
unresolved.»myInstanceAttribute«;
61105

tests/resources/scoping/member accesses/to class members/instance methods/main.sdstest

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,57 @@ class MyClass {
4949

5050
class AnotherClass
5151

52+
fun nullableMyClass() -> result: MyClass?
53+
5254
pipeline myPipeline {
55+
// $TEST$ references myInstanceMethod
56+
val myClass = MyClass();
57+
myClass.»myInstanceMethod«();
58+
59+
60+
// $TEST$ references redeclaredAsInstanceAttribute
61+
MyClass().»redeclaredAsInstanceAttribute«();
62+
63+
// $TEST$ references redeclaredAsStaticAttribute
64+
MyClass().»redeclaredAsStaticAttribute«();
65+
66+
// $TEST$ references redeclaredAsNestedClass
67+
MyClass().»redeclaredAsNestedClass«();
68+
69+
// $TEST$ references redeclaredAsNestedEnum
70+
MyClass().»redeclaredAsNestedEnum«();
71+
72+
// $TEST$ references redeclaredAsInstanceMethod
73+
MyClass().»redeclaredAsInstanceMethod«();
74+
75+
// $TEST$ references redeclaredAsStaticMethod
76+
MyClass().»redeclaredAsStaticMethod«();
77+
78+
// $TEST$ references declaredPreviouslyAsStaticAttribute
79+
MyClass().»declaredPreviouslyAsStaticAttribute«();
80+
81+
// $TEST$ references declaredPreviouslyAsNestedClass
82+
MyClass().»declaredPreviouslyAsNestedClass«();
83+
84+
// $TEST$ references declaredPreviouslyAsNestedEnum
85+
MyClass().»declaredPreviouslyAsNestedEnum«();
86+
87+
// $TEST$ references declaredPreviouslyAsStaticMethod
88+
MyClass().»declaredPreviouslyAsStaticMethod«();
89+
90+
// $TEST$ references myInstanceMethod
91+
nullableMyClass()?.»myInstanceMethod«();
92+
93+
5394
// $TEST$ unresolved
5495
MyClass.»myInstanceMethod«;
5596

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

100+
// $TEST$ unresolved
101+
nullableMyClass().»myInstanceAttribute«;
102+
59103
// $TEST$ unresolved
60104
unresolved.»myInstanceMethod«;
61105

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package tests.scoping.memberAccesses.toParametersOfEnumVariants
2+
3+
enum MyEnum {
4+
MyEnumVariant(
5+
// $TEST$ target param
6+
»param«: Int,
7+
8+
// $TEST$ target redeclared
9+
»redeclared«: Int,
10+
redeclared: Int,
11+
)
12+
13+
MyOtherEnumVariant
14+
}
15+
16+
enum MyOtherEnum {
17+
MyEnumVariant
18+
}
19+
20+
pipeline myPipeline {
21+
// $TEST$ references param
22+
MyEnum.MyEnumVariant().»param«;
23+
24+
// $TEST$ references redeclared
25+
MyEnum.MyEnumVariant().»redeclared«;
26+
27+
28+
// $TEST$ unresolved
29+
MyOtherEnum.MyEnumVariant.»param«;
30+
31+
// $TEST$ unresolved
32+
MyEnum.MyOtherEnumVariant().»param«;
33+
34+
// $TEST$ unresolved
35+
MyOtherEnum.MyEnumVariant().»param«;
36+
37+
// $TEST$ unresolved
38+
MyEnum.MyEnumVariant().»unresolved«;
39+
40+
// $TEST$ unresolved
41+
MyEnum.unresolved().»param«;
42+
43+
// $TEST$ unresolved
44+
unresolved.MyEnumVariant().»param«;
45+
}
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,23 @@ class MyClass() {
55
attr »result«: Int
66
}
77

8+
enum MyEnum {
9+
// $TEST$ target MyEnum_result
10+
MyEnumVariant(»result«: Int)
11+
}
12+
813
pipeline myPipeline {
9-
val lambdaWithOneResultWithIdenticalMember = () {
14+
val f1 = () {
1015
yield result = MyClass();
1116
};
1217

18+
val f2 = () {
19+
yield result = MyEnum.MyEnumVariant(0);
20+
};
21+
1322
// $TEST$ references MyClass_result
14-
lambdaWithOneResultWithIdenticalMember().»result«;
23+
f1().»result«;
24+
25+
// $TEST$ references MyEnum_result
26+
f2().»result«;
1527
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package tests.scoping.memberAccesses.toResults.ofCallableTypes.matchingMember
2+
3+
class MyClass() {
4+
// $TEST$ target MyClass_result
5+
attr »result«: Int
6+
}
7+
8+
enum MyEnum {
9+
// $TEST$ target MyEnum_result
10+
MyEnumVariant(»result«: Int)
11+
}
12+
13+
segment mySegment(
14+
f1: () -> result: MyClass,
15+
f2: () -> result: MyEnum.MyEnumVariant,
16+
) {
17+
18+
// $TEST$ references MyClass_result
19+
f1().»result«;
20+
21+
// $TEST$ references MyEnum_result
22+
f2().»result«;
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tests.scoping.memberAccesses.toResults.ofFunctions.matchingMember
2+
3+
class MyClass() {
4+
// $TEST$ target MyClass_result
5+
attr »result«: Int
6+
}
7+
8+
enum MyEnum {
9+
// $TEST$ target MyEnum_result
10+
MyEnumVariant(»result«: Int)
11+
}
12+
13+
fun f1() -> result: MyClass
14+
fun f2() -> result: MyEnum.MyEnumVariant
15+
16+
pipeline myPipeline {
17+
// $TEST$ references MyClass_result
18+
f1().»result«;
19+
20+
// $TEST$ references MyEnum_result
21+
f2().»result«;
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tests.scoping.memberAccesses.toResults.ofSegments.matchingMember
2+
3+
class MyClass() {
4+
// $TEST$ target MyClass_result
5+
attr »result«: Int
6+
}
7+
8+
enum MyEnum {
9+
// $TEST$ target MyEnum_result
10+
MyEnumVariant(»result«: Int)
11+
}
12+
13+
segment s1() -> result: MyClass {}
14+
segment s2() -> result: MyEnum.MyEnumVariant {}
15+
16+
pipeline myPipeline {
17+
// $TEST$ references MyClass_result
18+
s1().»result«;
19+
20+
// $TEST$ references MyEnum_result
21+
s2().»result«;
22+
}

0 commit comments

Comments
 (0)