Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: scoping for own members #611

Merged
merged 3 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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