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: node mapper #602

Merged
merged 7 commits into from
Oct 5, 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
10 changes: 10 additions & 0 deletions src/language/helpers/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@ import {
isSdsEnum,
isSdsFunction,
isSdsSegment,
SdsArgument,
SdsClassMember,
SdsDeclaration,
SdsTypeArgument,
} from '../generated/ast.js';

export const isInternal = (node: SdsDeclaration): boolean => {
return isSdsSegment(node) && node.visibility === 'internal';
};

export const isNamedArgument = (node: SdsArgument): boolean => {
return Boolean(node.parameter);
};

export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => {
return Boolean(node.typeParameter);
};

export const isStatic = (node: SdsClassMember): boolean => {
if (isSdsClass(node) || isSdsEnum(node)) {
return true;
Expand Down
139 changes: 139 additions & 0 deletions src/language/helpers/safe-ds-node-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
import {
isSdsAbstractCall,
isSdsAnnotationCall,
isSdsCall,
isSdsCallable,
isSdsClass,
isSdsEnumVariant,
isSdsNamedType,
isSdsType,
SdsAbstractCall,
SdsArgument,
SdsCallable,
SdsNamedTypeDeclaration,
SdsParameter,
SdsTypeArgument,
SdsTypeParameter,
} from '../generated/ast.js';
import { CallableType, StaticType } from '../typing/model.js';
import { getContainerOfType } from 'langium';
import { argumentsOrEmpty, parametersOrEmpty, typeArgumentsOrEmpty, typeParametersOrEmpty } from './shortcuts.js';
import { isNamedArgument, isNamedTypeArgument } from './checks.js';

export class SafeDsNodeMapper {
private readonly typeComputer: SafeDsTypeComputer;

constructor(services: SafeDsServices) {
this.typeComputer = new SafeDsTypeComputer(services);
}

/**
* Returns the callable that is called by the given call. If no callable can be found, returns undefined.
*/
callToCallableOrUndefined(node: SdsAbstractCall | undefined): SdsCallable | undefined {
if (isSdsAnnotationCall(node)) {
return node.annotation?.ref;
} else if (isSdsCall(node)) {
const receiverType = this.typeComputer.computeType(node.receiver);
if (receiverType instanceof CallableType) {
return receiverType.sdsCallable;
} else if (receiverType instanceof StaticType) {
const declaration = receiverType.instanceType.sdsDeclaration;
if (isSdsCallable(declaration)) {
return declaration;
}
}
}

return undefined;
}

/**
* Returns the parameter that the argument is assigned to. If there is no matching parameter, returns undefined.
*/
argumentToParameterOrUndefined(node: SdsArgument | undefined): SdsParameter | undefined {
if (!node) {
return undefined;
}

// Named argument
if (node.parameter) {
return node.parameter.ref;
}

// Positional argument
const containingAbstractCall = getContainerOfType(node, isSdsAbstractCall)!;
const args = argumentsOrEmpty(containingAbstractCall);
const argumentPosition = node.$containerIndex ?? -1;

// A prior argument is named
for (let i = 0; i < argumentPosition; i++) {
if (isNamedArgument(args[i])) {
return undefined;
}
}

// Find parameter at the same position
const callable = this.callToCallableOrUndefined(containingAbstractCall);
const parameters = parametersOrEmpty(callable);
if (argumentPosition < parameters.length) {
return parameters[argumentPosition];
}

// If no parameter is found, check if the last parameter is variadic
const lastParameter = parameters[parameters.length - 1];
if (lastParameter?.isVariadic) {
return lastParameter;
}

return undefined;
}

/**
* Returns the type parameter that the type argument is assigned to. If there is no matching type parameter, returns
* undefined.
*/
typeArgumentToTypeParameterOrUndefined(node: SdsTypeArgument | undefined): SdsTypeParameter | undefined {
if (!node) {
return undefined;
}

// Named type argument
if (node.typeParameter) {
return node.typeParameter.ref;
}

// Positional type argument
const containingType = getContainerOfType(node, isSdsType);
if (!isSdsNamedType(containingType)) {
return undefined;
}

const typeArguments = typeArgumentsOrEmpty(containingType.typeArgumentList);
const typeArgumentPosition = node.$containerIndex ?? -1;

// A prior type argument is named
for (let i = 0; i < typeArgumentPosition; i++) {
if (isNamedTypeArgument(typeArguments[i])) {
return undefined;
}
}

// Find type parameter at the same position
const namedTypeDeclaration = containingType.declaration.ref;
const typeParameters = this.typeParametersOfNamedTypeDeclarationOrEmpty(namedTypeDeclaration);
return typeParameters[typeArgumentPosition];
}

private typeParametersOfNamedTypeDeclarationOrEmpty(node: SdsNamedTypeDeclaration | undefined): SdsTypeParameter[] {
if (isSdsClass(node)) {
return typeParametersOrEmpty(node.typeParameterList);
} else if (isSdsEnumVariant(node)) {
return typeParametersOrEmpty(node.typeParameterList);
} else {
return [];
}
}
}
16 changes: 11 additions & 5 deletions src/language/helpers/shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {
isSdsModule,
isSdsModuleMember,
isSdsPlaceholder,
SdsAbstractCall,
SdsAnnotatedObject,
SdsAnnotationCall,
SdsArgument,
SdsAssignee,
SdsAssignment,
SdsBlock,
SdsBlockLambda,
SdsBlockLambdaResult,
SdsCallable,
SdsClass,
SdsClassMember,
SdsEnum,
Expand All @@ -23,7 +26,6 @@ import {
SdsModule,
SdsModuleMember,
SdsParameter,
SdsParameterList,
SdsPlaceholder,
SdsQualifiedImport,
SdsResult,
Expand All @@ -50,6 +52,10 @@ export const annotationCallsOrEmpty = function (node: SdsAnnotatedObject | undef
}
};

export const argumentsOrEmpty = function (node: SdsAbstractCall | undefined): SdsArgument[] {
return node?.argumentList?.arguments ?? [];
};

export const assigneesOrEmpty = function (node: SdsAssignment | undefined): SdsAssignee[] {
return node?.assigneeList?.assignees ?? [];
};
Expand Down Expand Up @@ -89,12 +95,12 @@ export const moduleMembersOrEmpty = function (node: SdsModule | undefined): SdsM
return node?.members?.filter(isSdsModuleMember) ?? [];
};

export const packageNameOrNull = function (node: AstNode | undefined): string | null {
return getContainerOfType(node, isSdsModule)?.name ?? null;
export const packageNameOrUndefined = function (node: AstNode | undefined): string | undefined {
return getContainerOfType(node, isSdsModule)?.name;
};

export const parametersOrEmpty = function (node: SdsParameterList | undefined): SdsParameter[] {
return node?.parameters ?? [];
export const parametersOrEmpty = function (node: SdsCallable | undefined): SdsParameter[] {
return node?.parameterList?.parameters ?? [];
};

export const placeholdersOrEmpty = function (node: SdsBlock | undefined): SdsPlaceholder[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
/**
* Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `null`.
*/
export const toConstantExpressionOrNull = (node: AstNode): SdsConstantExpression | null => {
export const toConstantExpressionOrUndefined = (node: AstNode): SdsConstantExpression | null => {
return toConstantExpressionOrNullImpl(node, new Map());
};

Expand Down Expand Up @@ -155,8 +155,8 @@ const simplifyInfixOperation = (
_substitutions: ParameterSubstitutions,
): SdsConstantExpression | null => {
// // By design none of the operators are short-circuited
// val constantLeft = leftOperand.toConstantExpressionOrNull(substitutions) ?: return null
// val constantRight = rightOperand.toConstantExpressionOrNull(substitutions) ?: return null
// val constantLeft = leftOperand.toConstantExpressionOrUndefined(substitutions) ?: return null
// val constantRight = rightOperand.toConstantExpressionOrUndefined(substitutions) ?: return null
//
// return when (operator()) {
// Or -> simplifyLogicalOp(constantLeft, Boolean::or, constantRight)
Expand Down Expand Up @@ -281,7 +281,7 @@ const simplifyPrefixOperation = (
_node: SdsPrefixOperation,
_substitutions: ParameterSubstitutions,
): SdsConstantExpression | null => {
// val constantOperand = operand.toConstantExpressionOrNull(substitutions) ?: return null
// val constantOperand = operand.toConstantExpressionOrUndefined(substitutions) ?: return null
//
// return when (operator()) {
// Not -> when (constantOperand) {
Expand Down
7 changes: 7 additions & 0 deletions src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { SafeDsValueConverter } from './grammar/safe-ds-value-converter.js';
import { SafeDsTypeComputer } from './typing/safe-ds-type-computer.js';
import { SafeDsCoreClasses } from './builtins/safe-ds-core-classes.js';
import { SafeDsPackageManager } from './workspace/safe-ds-package-manager.js';
import { SafeDsNodeMapper } from './helpers/safe-ds-node-mapper.js';

/**
* Declaration of custom services - add your own service classes here.
Expand All @@ -27,6 +28,9 @@ export type SafeDsAddedServices = {
builtins: {
CoreClasses: SafeDsCoreClasses;
};
helpers: {
NodeMapper: SafeDsNodeMapper;
};
types: {
TypeComputer: SafeDsTypeComputer;
};
Expand All @@ -50,6 +54,9 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
builtins: {
CoreClasses: (services) => new SafeDsCoreClasses(services),
},
helpers: {
NodeMapper: (services) => new SafeDsNodeMapper(services),
},
lsp: {
Formatter: () => new SafeDsFormatter(),
},
Expand Down
46 changes: 14 additions & 32 deletions src/language/scoping/safe-ds-scope-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import {
} from 'langium';
import {
isSdsAbstractCall,
isSdsAnnotationCall,
isSdsArgument,
isSdsAssignment,
isSdsBlock,
isSdsCall,
isSdsCallable,
isSdsClass,
isSdsEnum,
Expand Down Expand Up @@ -55,7 +53,7 @@ import {
enumVariantsOrEmpty,
importedDeclarationsOrEmpty,
importsOrEmpty,
packageNameOrNull,
packageNameOrUndefined,
parametersOrEmpty,
resultsOrEmpty,
statementsOrEmpty,
Expand All @@ -66,17 +64,19 @@ import { isStatic } from '../helpers/checks.js';
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 { CallableType, StaticType } from '../typing/model.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';

export class SafeDsScopeProvider extends DefaultScopeProvider {
private readonly astReflection: AstReflection;
private readonly nodeMapper: SafeDsNodeMapper;
private readonly packageManager: SafeDsPackageManager;
private readonly typeComputer: SafeDsTypeComputer;

constructor(services: SafeDsServices) {
super(services);

this.astReflection = services.shared.AstReflection;
this.nodeMapper = services.helpers.NodeMapper;
this.packageManager = services.workspace.PackageManager;
this.typeComputer = services.types.TypeComputer;
}
Expand Down Expand Up @@ -111,35 +111,17 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {

private getScopeForArgumentParameter(node: SdsArgument): Scope {
const containingAbstractCall = getContainerOfType(node, isSdsAbstractCall);
if (isSdsAnnotationCall(containingAbstractCall)) {
const annotation = containingAbstractCall.annotation?.ref;
if (!annotation) {
return EMPTY_SCOPE;
}

const parameters = parametersOrEmpty(annotation.parameterList);
return this.createScopeForNodes(parameters);
} else if (isSdsCall(containingAbstractCall)) {
const receiverType = this.typeComputer.computeType(containingAbstractCall.receiver);
if (receiverType instanceof CallableType) {
const parameters = parametersOrEmpty(receiverType.sdsCallable.parameterList);
return this.createScopeForNodes(parameters);
} else if (receiverType instanceof StaticType) {
const declaration = receiverType.instanceType.sdsDeclaration;
if (isSdsCallable(declaration)) {
const parameters = parametersOrEmpty(declaration.parameterList);
return this.createScopeForNodes(parameters);
}
}

return EMPTY_SCOPE;
} /* c8 ignore start */ else {
const callable = this.nodeMapper.callToCallableOrUndefined(containingAbstractCall);
if (!callable) {
return EMPTY_SCOPE;
} /* c8 ignore stop */
}

const parameters = parametersOrEmpty(callable);
return this.createScopeForNodes(parameters);
}

private getScopeForImportedDeclarationDeclaration(node: SdsImportedDeclaration): Scope {
const ownPackageName = packageNameOrNull(node);
const ownPackageName = packageNameOrUndefined(node);

const containingQualifiedImport = getContainerOfType(node, isSdsQualifiedImport);
if (!containingQualifiedImport) {
Expand Down Expand Up @@ -297,7 +279,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
private localDeclarations(node: AstNode, outerScope: Scope): Scope {
// Parameters
const containingCallable = getContainerOfType(node.$container, isSdsCallable);
const parameters = parametersOrEmpty(containingCallable?.parameterList);
const parameters = parametersOrEmpty(containingCallable);

// Placeholders up to the containing statement
const containingStatement = getContainerOfType(node.$container, isSdsStatement);
Expand Down Expand Up @@ -375,7 +357,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
}

private getGlobalScopeForNode(referenceType: string, node: AstNode): Scope {
const ownPackageName = packageNameOrNull(node);
const ownPackageName = packageNameOrUndefined(node);

// Builtin declarations
const builtinDeclarations = this.builtinDeclarations(referenceType);
Expand All @@ -397,7 +379,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
});
}

private declarationsInSamePackage(packageName: string | null, referenceType: string): AstNodeDescription[] {
private declarationsInSamePackage(packageName: string | undefined, referenceType: string): AstNodeDescription[] {
if (!packageName) {
return [];
}
Expand Down
Loading