Skip to content

Commit a13e5b5

Browse files
feat: node mapper (#602)
### Summary of Changes Add a service that can map * arguments to parameters, * type arguments to type parameters, * calls to callables. --------- Co-authored-by: megalinter-bot <[email protected]>
1 parent 6b486cd commit a13e5b5

21 files changed

+934
-200
lines changed

src/language/helpers/checks.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,24 @@ import {
44
isSdsEnum,
55
isSdsFunction,
66
isSdsSegment,
7+
SdsArgument,
78
SdsClassMember,
89
SdsDeclaration,
10+
SdsTypeArgument,
911
} from '../generated/ast.js';
1012

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

17+
export const isNamedArgument = (node: SdsArgument): boolean => {
18+
return Boolean(node.parameter);
19+
};
20+
21+
export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => {
22+
return Boolean(node.typeParameter);
23+
};
24+
1525
export const isStatic = (node: SdsClassMember): boolean => {
1626
if (isSdsClass(node) || isSdsEnum(node)) {
1727
return true;
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { SafeDsServices } from '../safe-ds-module.js';
2+
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
3+
import {
4+
isSdsAbstractCall,
5+
isSdsAnnotationCall,
6+
isSdsCall,
7+
isSdsCallable,
8+
isSdsClass,
9+
isSdsEnumVariant,
10+
isSdsNamedType,
11+
isSdsType,
12+
SdsAbstractCall,
13+
SdsArgument,
14+
SdsCallable,
15+
SdsNamedTypeDeclaration,
16+
SdsParameter,
17+
SdsTypeArgument,
18+
SdsTypeParameter,
19+
} from '../generated/ast.js';
20+
import { CallableType, StaticType } from '../typing/model.js';
21+
import { getContainerOfType } from 'langium';
22+
import { argumentsOrEmpty, parametersOrEmpty, typeArgumentsOrEmpty, typeParametersOrEmpty } from './shortcuts.js';
23+
import { isNamedArgument, isNamedTypeArgument } from './checks.js';
24+
25+
export class SafeDsNodeMapper {
26+
private readonly typeComputer: SafeDsTypeComputer;
27+
28+
constructor(services: SafeDsServices) {
29+
this.typeComputer = new SafeDsTypeComputer(services);
30+
}
31+
32+
/**
33+
* Returns the callable that is called by the given call. If no callable can be found, returns undefined.
34+
*/
35+
callToCallableOrUndefined(node: SdsAbstractCall | undefined): SdsCallable | undefined {
36+
if (isSdsAnnotationCall(node)) {
37+
return node.annotation?.ref;
38+
} else if (isSdsCall(node)) {
39+
const receiverType = this.typeComputer.computeType(node.receiver);
40+
if (receiverType instanceof CallableType) {
41+
return receiverType.sdsCallable;
42+
} else if (receiverType instanceof StaticType) {
43+
const declaration = receiverType.instanceType.sdsDeclaration;
44+
if (isSdsCallable(declaration)) {
45+
return declaration;
46+
}
47+
}
48+
}
49+
50+
return undefined;
51+
}
52+
53+
/**
54+
* Returns the parameter that the argument is assigned to. If there is no matching parameter, returns undefined.
55+
*/
56+
argumentToParameterOrUndefined(node: SdsArgument | undefined): SdsParameter | undefined {
57+
if (!node) {
58+
return undefined;
59+
}
60+
61+
// Named argument
62+
if (node.parameter) {
63+
return node.parameter.ref;
64+
}
65+
66+
// Positional argument
67+
const containingAbstractCall = getContainerOfType(node, isSdsAbstractCall)!;
68+
const args = argumentsOrEmpty(containingAbstractCall);
69+
const argumentPosition = node.$containerIndex ?? -1;
70+
71+
// A prior argument is named
72+
for (let i = 0; i < argumentPosition; i++) {
73+
if (isNamedArgument(args[i])) {
74+
return undefined;
75+
}
76+
}
77+
78+
// Find parameter at the same position
79+
const callable = this.callToCallableOrUndefined(containingAbstractCall);
80+
const parameters = parametersOrEmpty(callable);
81+
if (argumentPosition < parameters.length) {
82+
return parameters[argumentPosition];
83+
}
84+
85+
// If no parameter is found, check if the last parameter is variadic
86+
const lastParameter = parameters[parameters.length - 1];
87+
if (lastParameter?.isVariadic) {
88+
return lastParameter;
89+
}
90+
91+
return undefined;
92+
}
93+
94+
/**
95+
* Returns the type parameter that the type argument is assigned to. If there is no matching type parameter, returns
96+
* undefined.
97+
*/
98+
typeArgumentToTypeParameterOrUndefined(node: SdsTypeArgument | undefined): SdsTypeParameter | undefined {
99+
if (!node) {
100+
return undefined;
101+
}
102+
103+
// Named type argument
104+
if (node.typeParameter) {
105+
return node.typeParameter.ref;
106+
}
107+
108+
// Positional type argument
109+
const containingType = getContainerOfType(node, isSdsType);
110+
if (!isSdsNamedType(containingType)) {
111+
return undefined;
112+
}
113+
114+
const typeArguments = typeArgumentsOrEmpty(containingType.typeArgumentList);
115+
const typeArgumentPosition = node.$containerIndex ?? -1;
116+
117+
// A prior type argument is named
118+
for (let i = 0; i < typeArgumentPosition; i++) {
119+
if (isNamedTypeArgument(typeArguments[i])) {
120+
return undefined;
121+
}
122+
}
123+
124+
// Find type parameter at the same position
125+
const namedTypeDeclaration = containingType.declaration.ref;
126+
const typeParameters = this.typeParametersOfNamedTypeDeclarationOrEmpty(namedTypeDeclaration);
127+
return typeParameters[typeArgumentPosition];
128+
}
129+
130+
private typeParametersOfNamedTypeDeclarationOrEmpty(node: SdsNamedTypeDeclaration | undefined): SdsTypeParameter[] {
131+
if (isSdsClass(node)) {
132+
return typeParametersOrEmpty(node.typeParameterList);
133+
} else if (isSdsEnumVariant(node)) {
134+
return typeParametersOrEmpty(node.typeParameterList);
135+
} else {
136+
return [];
137+
}
138+
}
139+
}

src/language/helpers/shortcuts.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import {
55
isSdsModule,
66
isSdsModuleMember,
77
isSdsPlaceholder,
8+
SdsAbstractCall,
89
SdsAnnotatedObject,
910
SdsAnnotationCall,
11+
SdsArgument,
1012
SdsAssignee,
1113
SdsAssignment,
1214
SdsBlock,
1315
SdsBlockLambda,
1416
SdsBlockLambdaResult,
17+
SdsCallable,
1518
SdsClass,
1619
SdsClassMember,
1720
SdsEnum,
@@ -23,7 +26,6 @@ import {
2326
SdsModule,
2427
SdsModuleMember,
2528
SdsParameter,
26-
SdsParameterList,
2729
SdsPlaceholder,
2830
SdsQualifiedImport,
2931
SdsResult,
@@ -50,6 +52,10 @@ export const annotationCallsOrEmpty = function (node: SdsAnnotatedObject | undef
5052
}
5153
};
5254

55+
export const argumentsOrEmpty = function (node: SdsAbstractCall | undefined): SdsArgument[] {
56+
return node?.argumentList?.arguments ?? [];
57+
};
58+
5359
export const assigneesOrEmpty = function (node: SdsAssignment | undefined): SdsAssignee[] {
5460
return node?.assigneeList?.assignees ?? [];
5561
};
@@ -89,12 +95,12 @@ export const moduleMembersOrEmpty = function (node: SdsModule | undefined): SdsM
8995
return node?.members?.filter(isSdsModuleMember) ?? [];
9096
};
9197

92-
export const packageNameOrNull = function (node: AstNode | undefined): string | null {
93-
return getContainerOfType(node, isSdsModule)?.name ?? null;
98+
export const packageNameOrUndefined = function (node: AstNode | undefined): string | undefined {
99+
return getContainerOfType(node, isSdsModule)?.name;
94100
};
95101

96-
export const parametersOrEmpty = function (node: SdsParameterList | undefined): SdsParameter[] {
97-
return node?.parameters ?? [];
102+
export const parametersOrEmpty = function (node: SdsCallable | undefined): SdsParameter[] {
103+
return node?.parameterList?.parameters ?? [];
98104
};
99105

100106
export const placeholdersOrEmpty = function (node: SdsBlock | undefined): SdsPlaceholder[] {

src/language/partialEvaluation/toConstantExpressionOrNull.ts renamed to src/language/partialEvaluation/toConstantExpressionOrUndefined.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import {
4545
/**
4646
* Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `null`.
4747
*/
48-
export const toConstantExpressionOrNull = (node: AstNode): SdsConstantExpression | null => {
48+
export const toConstantExpressionOrUndefined = (node: AstNode): SdsConstantExpression | null => {
4949
return toConstantExpressionOrNullImpl(node, new Map());
5050
};
5151

@@ -155,8 +155,8 @@ const simplifyInfixOperation = (
155155
_substitutions: ParameterSubstitutions,
156156
): SdsConstantExpression | null => {
157157
// // By design none of the operators are short-circuited
158-
// val constantLeft = leftOperand.toConstantExpressionOrNull(substitutions) ?: return null
159-
// val constantRight = rightOperand.toConstantExpressionOrNull(substitutions) ?: return null
158+
// val constantLeft = leftOperand.toConstantExpressionOrUndefined(substitutions) ?: return null
159+
// val constantRight = rightOperand.toConstantExpressionOrUndefined(substitutions) ?: return null
160160
//
161161
// return when (operator()) {
162162
// Or -> simplifyLogicalOp(constantLeft, Boolean::or, constantRight)
@@ -281,7 +281,7 @@ const simplifyPrefixOperation = (
281281
_node: SdsPrefixOperation,
282282
_substitutions: ParameterSubstitutions,
283283
): SdsConstantExpression | null => {
284-
// val constantOperand = operand.toConstantExpressionOrNull(substitutions) ?: return null
284+
// val constantOperand = operand.toConstantExpressionOrUndefined(substitutions) ?: return null
285285
//
286286
// return when (operator()) {
287287
// Not -> when (constantOperand) {

src/language/safe-ds-module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { SafeDsValueConverter } from './grammar/safe-ds-value-converter.js';
1919
import { SafeDsTypeComputer } from './typing/safe-ds-type-computer.js';
2020
import { SafeDsCoreClasses } from './builtins/safe-ds-core-classes.js';
2121
import { SafeDsPackageManager } from './workspace/safe-ds-package-manager.js';
22+
import { SafeDsNodeMapper } from './helpers/safe-ds-node-mapper.js';
2223

2324
/**
2425
* Declaration of custom services - add your own service classes here.
@@ -27,6 +28,9 @@ export type SafeDsAddedServices = {
2728
builtins: {
2829
CoreClasses: SafeDsCoreClasses;
2930
};
31+
helpers: {
32+
NodeMapper: SafeDsNodeMapper;
33+
};
3034
types: {
3135
TypeComputer: SafeDsTypeComputer;
3236
};
@@ -50,6 +54,9 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
5054
builtins: {
5155
CoreClasses: (services) => new SafeDsCoreClasses(services),
5256
},
57+
helpers: {
58+
NodeMapper: (services) => new SafeDsNodeMapper(services),
59+
},
5360
lsp: {
5461
Formatter: () => new SafeDsFormatter(),
5562
},

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

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ import {
1111
} from 'langium';
1212
import {
1313
isSdsAbstractCall,
14-
isSdsAnnotationCall,
1514
isSdsArgument,
1615
isSdsAssignment,
1716
isSdsBlock,
18-
isSdsCall,
1917
isSdsCallable,
2018
isSdsClass,
2119
isSdsEnum,
@@ -55,7 +53,7 @@ import {
5553
enumVariantsOrEmpty,
5654
importedDeclarationsOrEmpty,
5755
importsOrEmpty,
58-
packageNameOrNull,
56+
packageNameOrUndefined,
5957
parametersOrEmpty,
6058
resultsOrEmpty,
6159
statementsOrEmpty,
@@ -66,17 +64,19 @@ import { isStatic } from '../helpers/checks.js';
6664
import { SafeDsServices } from '../safe-ds-module.js';
6765
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
6866
import { SafeDsPackageManager } from '../workspace/safe-ds-package-manager.js';
69-
import { CallableType, StaticType } from '../typing/model.js';
67+
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
7068

7169
export class SafeDsScopeProvider extends DefaultScopeProvider {
7270
private readonly astReflection: AstReflection;
71+
private readonly nodeMapper: SafeDsNodeMapper;
7372
private readonly packageManager: SafeDsPackageManager;
7473
private readonly typeComputer: SafeDsTypeComputer;
7574

7675
constructor(services: SafeDsServices) {
7776
super(services);
7877

7978
this.astReflection = services.shared.AstReflection;
79+
this.nodeMapper = services.helpers.NodeMapper;
8080
this.packageManager = services.workspace.PackageManager;
8181
this.typeComputer = services.types.TypeComputer;
8282
}
@@ -111,35 +111,17 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
111111

112112
private getScopeForArgumentParameter(node: SdsArgument): Scope {
113113
const containingAbstractCall = getContainerOfType(node, isSdsAbstractCall);
114-
if (isSdsAnnotationCall(containingAbstractCall)) {
115-
const annotation = containingAbstractCall.annotation?.ref;
116-
if (!annotation) {
117-
return EMPTY_SCOPE;
118-
}
119-
120-
const parameters = parametersOrEmpty(annotation.parameterList);
121-
return this.createScopeForNodes(parameters);
122-
} else if (isSdsCall(containingAbstractCall)) {
123-
const receiverType = this.typeComputer.computeType(containingAbstractCall.receiver);
124-
if (receiverType instanceof CallableType) {
125-
const parameters = parametersOrEmpty(receiverType.sdsCallable.parameterList);
126-
return this.createScopeForNodes(parameters);
127-
} else if (receiverType instanceof StaticType) {
128-
const declaration = receiverType.instanceType.sdsDeclaration;
129-
if (isSdsCallable(declaration)) {
130-
const parameters = parametersOrEmpty(declaration.parameterList);
131-
return this.createScopeForNodes(parameters);
132-
}
133-
}
134-
135-
return EMPTY_SCOPE;
136-
} /* c8 ignore start */ else {
114+
const callable = this.nodeMapper.callToCallableOrUndefined(containingAbstractCall);
115+
if (!callable) {
137116
return EMPTY_SCOPE;
138-
} /* c8 ignore stop */
117+
}
118+
119+
const parameters = parametersOrEmpty(callable);
120+
return this.createScopeForNodes(parameters);
139121
}
140122

141123
private getScopeForImportedDeclarationDeclaration(node: SdsImportedDeclaration): Scope {
142-
const ownPackageName = packageNameOrNull(node);
124+
const ownPackageName = packageNameOrUndefined(node);
143125

144126
const containingQualifiedImport = getContainerOfType(node, isSdsQualifiedImport);
145127
if (!containingQualifiedImport) {
@@ -297,7 +279,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
297279
private localDeclarations(node: AstNode, outerScope: Scope): Scope {
298280
// Parameters
299281
const containingCallable = getContainerOfType(node.$container, isSdsCallable);
300-
const parameters = parametersOrEmpty(containingCallable?.parameterList);
282+
const parameters = parametersOrEmpty(containingCallable);
301283

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

377359
private getGlobalScopeForNode(referenceType: string, node: AstNode): Scope {
378-
const ownPackageName = packageNameOrNull(node);
360+
const ownPackageName = packageNameOrUndefined(node);
379361

380362
// Builtin declarations
381363
const builtinDeclarations = this.builtinDeclarations(referenceType);
@@ -397,7 +379,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
397379
});
398380
}
399381

400-
private declarationsInSamePackage(packageName: string | null, referenceType: string): AstNodeDescription[] {
382+
private declarationsInSamePackage(packageName: string | undefined, referenceType: string): AstNodeDescription[] {
401383
if (!packageName) {
402384
return [];
403385
}

0 commit comments

Comments
 (0)