Skip to content

Commit 097764d

Browse files
feat: error if value assigned to constant parameters is not constant (#646)
Closes partially #543 ### Summary of Changes Show an error if default values of constant parameters or arguments assigned to constant parameters are not constant. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
1 parent dcc05ce commit 097764d

File tree

15 files changed

+404
-65
lines changed

15 files changed

+404
-65
lines changed

src/language/builtins/safe-ds-annotations.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { resourceNameToUri } from '../../helpers/resources.js';
55
import { URI } from 'langium';
66
import { SafeDsServices } from '../safe-ds-module.js';
77
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
8-
import { toConstantExpressionOrUndefined } from '../partialEvaluation/toConstantExpressionOrUndefined.js';
8+
import { toConstantExpression } from '../partialEvaluation/toConstantExpression.js';
99
import { ConstantExpression, ConstantString } from '../partialEvaluation/model.js';
1010

1111
const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub');
@@ -97,6 +97,6 @@ export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
9797
const expression = argumentsOrEmpty(annotationCall).find(
9898
(it) => this.nodeMapper.argumentToParameterOrUndefined(it)?.name === parameterName,
9999
)?.value;
100-
return toConstantExpressionOrUndefined(expression);
100+
return toConstantExpression(expression);
101101
}
102102
}

src/language/helpers/nodeProperties.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import {
2+
isSdsAnnotation,
23
isSdsAssignment,
34
isSdsAttribute,
45
isSdsBlockLambda,
56
isSdsBlockLambdaResult,
7+
isSdsCallable,
68
isSdsCallableType,
79
isSdsClass,
810
isSdsDeclaration,
911
isSdsEnum,
1012
isSdsEnumVariant,
1113
isSdsFunction,
14+
isSdsLambda,
1215
isSdsModule,
1316
isSdsModuleMember,
1417
isSdsPlaceholder,
@@ -80,6 +83,21 @@ export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => {
8083
return Boolean(node.typeParameter);
8184
};
8285

86+
export const isConstantParameter = (node: SdsParameter | undefined): boolean => {
87+
if (!node) {
88+
return false;
89+
}
90+
91+
const containingCallable = getContainerOfType(node, isSdsCallable);
92+
93+
// In those cases, the const modifier is not applicable
94+
if (isSdsCallableType(containingCallable) || isSdsLambda(containingCallable)) {
95+
return false;
96+
}
97+
98+
return isSdsAnnotation(containingCallable) || node.isConstant;
99+
};
100+
83101
export const isRequiredParameter = (node: SdsParameter): boolean => {
84102
return !node.defaultValue;
85103
};

src/language/partialEvaluation/model.ts

+55
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,49 @@ export class ConstantInt extends ConstantNumber {
179179
}
180180
}
181181

182+
export class ConstantList extends ConstantExpression {
183+
constructor(readonly elements: ConstantExpression[]) {
184+
super();
185+
}
186+
187+
equals(other: ConstantExpression): boolean {
188+
return other instanceof ConstantList && this.elements.every((e, i) => e.equals(other.elements[i]));
189+
}
190+
191+
toString(): string {
192+
return `[${this.elements.join(', ')}]`;
193+
}
194+
}
195+
196+
export class ConstantMap extends ConstantExpression {
197+
constructor(readonly entries: ConstantMapEntry[]) {
198+
super();
199+
}
200+
201+
equals(other: ConstantExpression): boolean {
202+
return other instanceof ConstantMap && this.entries.every((e, i) => e.equals(other.entries[i]));
203+
}
204+
205+
toString(): string {
206+
return `{${this.entries.join(', ')}}`;
207+
}
208+
}
209+
210+
export class ConstantMapEntry {
211+
constructor(
212+
readonly key: ConstantExpression,
213+
readonly value: ConstantExpression,
214+
) {}
215+
216+
equals(other: ConstantMapEntry): boolean {
217+
return this.key.equals(other.key) && this.value.equals(other.value);
218+
}
219+
220+
toString(): string {
221+
return `${this.key}: ${this.value}`;
222+
}
223+
}
224+
182225
class ConstantNullClass extends ConstantExpression {
183226
equals(other: ConstantExpression): boolean {
184227
return other instanceof ConstantNullClass;
@@ -209,4 +252,16 @@ export class ConstantString extends ConstantExpression {
209252
}
210253
}
211254

255+
class UnknownValueClass extends ConstantExpression {
256+
override equals(other: ConstantExpression): boolean {
257+
return other instanceof UnknownValueClass;
258+
}
259+
260+
toString(): string {
261+
return '$unknown';
262+
}
263+
}
264+
265+
export const UnknownValue = new UnknownValueClass();
266+
212267
/* c8 ignore stop */

src/language/partialEvaluation/toConstantExpressionOrUndefined.ts src/language/partialEvaluation/toConstantExpression.ts

+50-49
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import {
2-
ParameterSubstitutions,
32
ConstantBoolean,
43
ConstantExpression,
54
ConstantFloat,
65
ConstantInt,
6+
ConstantList,
7+
ConstantMap,
78
ConstantNull,
89
ConstantString,
9-
IntermediateBlockLambda,
10-
IntermediateExpressionLambda,
10+
ParameterSubstitutions,
1111
SimplifiedExpression,
12+
UnknownValue,
1213
} from './model.js';
1314
import { AstNode } from 'langium';
1415
import {
@@ -22,6 +23,8 @@ import {
2223
isSdsIndexedAccess,
2324
isSdsInfixOperation,
2425
isSdsInt,
26+
isSdsList,
27+
isSdsMap,
2528
isSdsMemberAccess,
2629
isSdsNull,
2730
isSdsParenthesizedExpression,
@@ -45,32 +48,29 @@ import {
4548

4649
/* c8 ignore start */
4750
/**
48-
* Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `undefined`.
51+
* Tries to evaluate this expression.
4952
*/
50-
export const toConstantExpressionOrUndefined = (node: AstNode | undefined): ConstantExpression | undefined => {
53+
export const toConstantExpression = (node: AstNode | undefined): ConstantExpression => {
5154
if (!node) {
52-
return undefined;
55+
return UnknownValue;
5356
}
5457

55-
return toConstantExpressionOrUndefinedImpl(node, new Map());
58+
return toConstantExpressionImpl(node, new Map());
5659
};
5760

58-
const toConstantExpressionOrUndefinedImpl = (
59-
node: AstNode,
60-
substitutions: ParameterSubstitutions,
61-
): ConstantExpression | undefined => {
61+
const toConstantExpressionImpl = (node: AstNode, substitutions: ParameterSubstitutions): ConstantExpression => {
6262
const simplifiedExpression = simplify(node, substitutions)?.unwrap();
6363
if (simplifiedExpression instanceof ConstantExpression) {
6464
return simplifiedExpression;
6565
} else {
66-
return undefined;
66+
return UnknownValue;
6767
}
6868
};
6969

70-
const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => {
70+
const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression => {
7171
// Only expressions have a value
7272
if (!isSdsExpression(node)) {
73-
return undefined;
73+
return UnknownValue;
7474
}
7575

7676
// Base cases
@@ -101,6 +101,10 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): Simplif
101101
return simplify(node.value, substitutions);
102102
} else if (isSdsInfixOperation(node)) {
103103
return simplifyInfixOperation(node, substitutions);
104+
} else if (isSdsList(node)) {
105+
return new ConstantList([]);
106+
} else if (isSdsMap(node)) {
107+
return new ConstantMap([]);
104108
} else if (isSdsParenthesizedExpression(node)) {
105109
return simplify(node.expression, substitutions);
106110
} else if (isSdsPrefixOperation(node)) {
@@ -125,10 +129,7 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): Simplif
125129
throw new Error(`Missing case to handle ${node.$type}.`);
126130
};
127131

128-
const simplifyBlockLambda = (
129-
_node: SdsBlockLambda,
130-
_substitutions: ParameterSubstitutions,
131-
): IntermediateBlockLambda | undefined => {
132+
const simplifyBlockLambda = (_node: SdsBlockLambda, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
132133
// return when {
133134
// callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateBlockLambda(
134135
// parameters = parametersOrEmpty(),
@@ -137,13 +138,13 @@ const simplifyBlockLambda = (
137138
// )
138139
// else -> undefined
139140
// }
140-
return undefined;
141+
return UnknownValue;
141142
};
142143

143144
const simplifyExpressionLambda = (
144145
_node: SdsExpressionLambda,
145146
_substitutions: ParameterSubstitutions,
146-
): IntermediateExpressionLambda | undefined => {
147+
): SimplifiedExpression => {
147148
// return when {
148149
// callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateExpressionLambda(
149150
// parameters = parametersOrEmpty(),
@@ -152,19 +153,23 @@ const simplifyExpressionLambda = (
152153
// )
153154
// else -> undefined
154155
// }
155-
return undefined;
156+
return UnknownValue;
156157
};
157158

158159
const simplifyInfixOperation = (
159160
node: SdsInfixOperation,
160161
substitutions: ParameterSubstitutions,
161-
): ConstantExpression | undefined => {
162+
): SimplifiedExpression => {
162163
// By design none of the operators are short-circuited
163-
const constantLeft = toConstantExpressionOrUndefinedImpl(node.leftOperand, substitutions);
164-
if (!constantLeft) return;
164+
const constantLeft = toConstantExpressionImpl(node.leftOperand, substitutions);
165+
if (constantLeft === UnknownValue) {
166+
return UnknownValue;
167+
}
165168

166-
const constantRight = toConstantExpressionOrUndefinedImpl(node.rightOperand, substitutions);
167-
if (!constantRight) return;
169+
const constantRight = toConstantExpressionImpl(node.rightOperand, substitutions);
170+
if (constantRight === UnknownValue) {
171+
return UnknownValue;
172+
}
168173

169174
// return when (operator()) {
170175
// Or -> simplifyLogicalOp(constantLeft, Boolean::or, constantRight)
@@ -232,7 +237,7 @@ const simplifyInfixOperation = (
232237
// else -> constantLeft
233238
// }
234239
// }
235-
return undefined;
240+
return UnknownValue;
236241
};
237242

238243
// private fun simplifyLogicalOp(
@@ -288,9 +293,11 @@ const simplifyInfixOperation = (
288293
const simplifyPrefixOperation = (
289294
node: SdsPrefixOperation,
290295
substitutions: ParameterSubstitutions,
291-
): ConstantExpression | undefined => {
292-
const constantOperand = toConstantExpressionOrUndefinedImpl(node.operand, substitutions);
293-
if (!constantOperand) return;
296+
): SimplifiedExpression => {
297+
const constantOperand = toConstantExpressionImpl(node.operand, substitutions);
298+
if (constantOperand === UnknownValue) {
299+
return UnknownValue;
300+
}
294301

295302
if (node.operator === 'not') {
296303
if (constantOperand instanceof ConstantBoolean) {
@@ -304,22 +311,22 @@ const simplifyPrefixOperation = (
304311
}
305312
}
306313

307-
return undefined;
314+
return UnknownValue;
308315
};
309316

310317
const simplifyTemplateString = (
311318
node: SdsTemplateString,
312319
substitutions: ParameterSubstitutions,
313-
): ConstantExpression | undefined => {
314-
const constantExpressions = node.expressions.map((it) => toConstantExpressionOrUndefinedImpl(it, substitutions));
315-
if (constantExpressions.some((it) => it === undefined)) {
316-
return undefined;
320+
): SimplifiedExpression => {
321+
const constantExpressions = node.expressions.map((it) => toConstantExpressionImpl(it, substitutions));
322+
if (constantExpressions.some((it) => it === UnknownValue)) {
323+
return UnknownValue;
317324
}
318325

319326
return new ConstantString(constantExpressions.map((it) => it!.toInterpolationString()).join(''));
320327
};
321328

322-
const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => {
329+
const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
323330
// val simpleReceiver = simplifyReceiver(substitutions) ?: return undefined
324331
// val newSubstitutions = buildNewSubstitutions(simpleReceiver, substitutions)
325332
//
@@ -340,7 +347,7 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S
340347
// )
341348
// }
342349
// }
343-
return undefined;
350+
return UnknownValue;
344351
};
345352

346353
// private fun SdsCall.simplifyReceiver(substitutions: ParameterSubstitutions): SdsIntermediateCallable? {
@@ -387,19 +394,16 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S
387394
const simplifyIndexedAccess = (
388395
_node: SdsIndexedAccess,
389396
_substitutions: ParameterSubstitutions,
390-
): SimplifiedExpression | undefined => {
397+
): SimplifiedExpression => {
391398
// val simpleReceiver = receiver.simplify(substitutions) as? SdsIntermediateVariadicArguments ?: return undefined
392399
// val simpleIndex = index.simplify(substitutions) as? SdsConstantInt ?: return undefined
393400
//
394401
// return simpleReceiver.getArgumentByIndexOrNull(simpleIndex.value)
395402
// }
396-
return undefined;
403+
return UnknownValue;
397404
};
398405

399-
const simplifyMemberAccess = (
400-
_node: SdsMemberAccess,
401-
_substitutions: ParameterSubstitutions,
402-
): SimplifiedExpression | undefined => {
406+
const simplifyMemberAccess = (_node: SdsMemberAccess, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
403407
// private fun SdsMemberAccess.simplifyMemberAccess(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? {
404408
// if (member.declaration is SdsEnumVariant) {
405409
// return member.simplifyReference(substitutions)
@@ -413,13 +417,10 @@ const simplifyMemberAccess = (
413417
// is SdsIntermediateRecord -> simpleReceiver.getSubstitutionByReferenceOrNull(member)
414418
// else -> undefined
415419
// }
416-
return undefined;
420+
return UnknownValue;
417421
};
418422

419-
const simplifyReference = (
420-
_node: SdsReference,
421-
_substitutions: ParameterSubstitutions,
422-
): SimplifiedExpression | undefined => {
423+
const simplifyReference = (_node: SdsReference, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
423424
// return when (val declaration = this.declaration) {
424425
// is SdsEnumVariant -> when {
425426
// declaration.parametersOrEmpty().isEmpty() -> SdsConstantEnumVariant(declaration)
@@ -430,7 +431,7 @@ const simplifyReference = (
430431
// is SdsStep -> declaration.simplifyStep()
431432
// else -> undefined
432433
// }
433-
return undefined;
434+
return UnknownValue;
434435
};
435436

436437
// private fun SdsAbstractAssignee.simplifyAssignee(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? {

0 commit comments

Comments
 (0)