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 inherited members #706

Merged
merged 3 commits into from
Oct 30, 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
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@ import {
getTypeParameters,
isStatic,
} from '../helpers/nodeProperties.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { ClassType, EnumVariantType } from '../typing/model.js';
import type { SafeDsClassHierarchy } from '../typing/safe-ds-class-hierarchy.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;
private readonly classHierarchy: SafeDsClassHierarchy;
private readonly nodeMapper: SafeDsNodeMapper;
private readonly packageManager: SafeDsPackageManager;
private readonly typeComputer: SafeDsTypeComputer;
Expand All @@ -79,6 +81,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
super(services);

this.astReflection = services.shared.AstReflection;
this.classHierarchy = services.types.ClassHierarchy;
this.nodeMapper = services.helpers.NodeMapper;
this.packageManager = services.workspace.PackageManager;
this.typeComputer = services.types.TypeComputer;
Expand Down Expand Up @@ -178,12 +181,9 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
const declaration = this.getUniqueReferencedDeclarationForExpression(node.receiver);
if (isSdsClass(declaration)) {
const ownStaticMembers = getMatchingClassMembers(declaration, isStatic);
// val superTypeMembers = receiverDeclaration.superClassMembers()
// .filter { it.isStatic() }
// .toList()
//
// return Scopes.scopeFor(ownStaticMembers, Scopes.scopeFor(superTypeMembers))
return this.createScopeForNodes(ownStaticMembers);
const superclassStaticMembers = this.classHierarchy.streamSuperclassMembers(declaration).filter(isStatic);

return this.createScopeForNodes(ownStaticMembers, this.createScopeForNodes(superclassStaticMembers));
} else if (isSdsEnum(declaration)) {
return this.createScopeForNodes(getEnumVariants(declaration));
}
Expand All @@ -208,12 +208,14 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {

if (receiverType instanceof ClassType) {
const ownInstanceMembers = getMatchingClassMembers(receiverType.declaration, (it) => !isStatic(it));
// val superTypeMembers = type.sdsClass.superClassMembers()
// .filter { !it.isStatic() }
// .toList()
//
// Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope))
return this.createScopeForNodes(ownInstanceMembers, resultScope);
const superclassInstanceMembers = this.classHierarchy
.streamSuperclassMembers(receiverType.declaration)
.filter((it) => !isStatic(it));

return this.createScopeForNodes(
ownInstanceMembers,
this.createScopeForNodes(superclassInstanceMembers, resultScope),
);
} else if (receiverType instanceof EnumVariantType) {
const parameters = getParameters(receiverType.declaration);
return this.createScopeForNodes(parameters, resultScope);
Expand Down
22 changes: 11 additions & 11 deletions packages/safe-ds-lang/src/language/typing/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,6 @@ export class NamedTupleType<T extends SdsDeclaration> extends Type {
return this.entries[index]?.type ?? UnknownType;
}

/**
* 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 equals(other: Type): boolean {
if (other === this) {
return true;
Expand All @@ -164,6 +153,17 @@ export class NamedTupleType<T extends SdsDeclaration> extends Type {
override toString(): string {
return `(${this.entries.join(', ')})`;
}

/**
* 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;
}
}

export class NamedTupleEntry<T extends SdsDeclaration> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsClasses } from '../builtins/safe-ds-classes.js';
import { SdsClass } from '../generated/ast.js';
import { EMPTY_STREAM, stream, Stream } from 'langium';
import { getParentTypes } from '../helpers/nodeProperties.js';
import { SafeDsTypeComputer } from './safe-ds-type-computer.js';
import { SafeDsClasses } from '../builtins/safe-ds-classes.js';
import { SdsClass, type SdsClassMember } from '../generated/ast.js';
import { getMatchingClassMembers, getParentTypes } from '../helpers/nodeProperties.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { ClassType } from './model.js';
import { SafeDsTypeComputer } from './safe-ds-type-computer.js';

export class SafeDsClassHierarchy {
private readonly builtinClasses: SafeDsClasses;
Expand Down Expand Up @@ -60,6 +60,14 @@ export class SafeDsClassHierarchy {
}
}

streamSuperclassMembers(node: SdsClass | undefined): Stream<SdsClassMember> {
if (!node) {
return EMPTY_STREAM;
}

return this.streamSuperclasses(node).flatMap(getMatchingClassMembers);
}

/**
* Returns the parent class of the given class, or undefined if there is no parent class. Only the first parent
* type is considered, i.e. multiple inheritance is not supported.
Expand All @@ -74,22 +82,3 @@ export class SafeDsClassHierarchy {
return undefined;
}
}

// fun SdsClass.superClassMembers() =
// this.superClasses().flatMap { it.classMembersOrEmpty().asSequence() }
//
// // TODO only static methods can be hidden
// fun SdsFunction.hiddenFunction(): SdsFunction? {
// val containingClassOrInterface = closestAncestorOrNull<SdsClass>() ?: return null
// return containingClassOrInterface.superClassMembers()
// .filterIsInstance<SdsFunction>()
// .firstOrNull { it.name == name }
// }
//
// fun SdsClass?.inheritedNonStaticMembersOrEmpty(): Set<SdsAbstractDeclaration> {
// return this?.parentClassesOrEmpty()
// ?.flatMap { it.classMembersOrEmpty() }
// ?.filter { it is SdsAttribute && !it.isStatic || it is SdsFunction && !it.isStatic }
// ?.toSet()
// .orEmpty()
// }
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createSafeDsServices } from '../../../src/language/safe-ds-module.js';
import { NodeFileSystem } from 'langium/node';
import { clearDocuments } from 'langium/test';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { isSdsClass, SdsClass } from '../../../src/language/generated/ast.js';
import { createSafeDsServices } from '../../../src/language/index.js';
import { getNodeOfType } from '../../helpers/nodeFinder.js';

const services = createSafeDsServices(NodeFileSystem).SafeDs;
Expand Down Expand Up @@ -131,4 +131,73 @@ describe('SafeDsClassHierarchy', async () => {
expect(superclassNames(firstClass)).toStrictEqual(expected);
});
});

describe('streamSuperclassMembers', () => {
const superclassMemberNames = (node: SdsClass | undefined) =>
classHierarchy
.streamSuperclassMembers(node)
.map((member) => member.name)
.toArray();

it('should return an empty stream if passed undefined', () => {
expect(superclassMemberNames(undefined)).toStrictEqual([]);
});

const testCases = [
{
testName: 'should return the members of the parent type',
code: `
class A {
attr a: Int
fun f()
}

class B sub A
`,
index: 1,
expected: ['a', 'f'],
},
{
testName: 'should only consider members of the first parent type',
code: `
class A {
attr a: Int
fun f()
}

class B {
attr b: Int
fun g()
}

class C sub A, B
`,
index: 2,
expected: ['a', 'f'],
},
{
testName: 'should return members of all superclasses',
code: `
class A {
attr a: Int
fun f()
}

class B sub A {
attr b: Int
fun g()
}

class C sub B
`,
index: 2,
expected: ['b', 'g', 'a', 'f'],
},
];

it.each(testCases)('$testName', async ({ code, index, expected }) => {
const firstClass = await getNodeOfType(services, code, isSdsClass, index);
expect(superclassMemberNames(firstClass)).toStrictEqual(expected);
});
});
});
Loading