Skip to content

Commit

Permalink
feat: error if value assigned to constant parameters is not constant (#…
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 17, 2023
1 parent dcc05ce commit 097764d
Show file tree
Hide file tree
Showing 15 changed files with 404 additions and 65 deletions.
4 changes: 2 additions & 2 deletions src/language/builtins/safe-ds-annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { resourceNameToUri } from '../../helpers/resources.js';
import { URI } from 'langium';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import { toConstantExpressionOrUndefined } from '../partialEvaluation/toConstantExpressionOrUndefined.js';
import { toConstantExpression } from '../partialEvaluation/toConstantExpression.js';
import { ConstantExpression, ConstantString } from '../partialEvaluation/model.js';

const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub');
Expand Down Expand Up @@ -97,6 +97,6 @@ export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
const expression = argumentsOrEmpty(annotationCall).find(
(it) => this.nodeMapper.argumentToParameterOrUndefined(it)?.name === parameterName,
)?.value;
return toConstantExpressionOrUndefined(expression);
return toConstantExpression(expression);
}
}
18 changes: 18 additions & 0 deletions src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import {
isSdsAnnotation,
isSdsAssignment,
isSdsAttribute,
isSdsBlockLambda,
isSdsBlockLambdaResult,
isSdsCallable,
isSdsCallableType,
isSdsClass,
isSdsDeclaration,
isSdsEnum,
isSdsEnumVariant,
isSdsFunction,
isSdsLambda,
isSdsModule,
isSdsModuleMember,
isSdsPlaceholder,
Expand Down Expand Up @@ -80,6 +83,21 @@ export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => {
return Boolean(node.typeParameter);
};

export const isConstantParameter = (node: SdsParameter | undefined): boolean => {
if (!node) {
return false;
}

const containingCallable = getContainerOfType(node, isSdsCallable);

// In those cases, the const modifier is not applicable
if (isSdsCallableType(containingCallable) || isSdsLambda(containingCallable)) {
return false;
}

return isSdsAnnotation(containingCallable) || node.isConstant;
};

export const isRequiredParameter = (node: SdsParameter): boolean => {
return !node.defaultValue;
};
Expand Down
55 changes: 55 additions & 0 deletions src/language/partialEvaluation/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,49 @@ export class ConstantInt extends ConstantNumber {
}
}

export class ConstantList extends ConstantExpression {
constructor(readonly elements: ConstantExpression[]) {
super();
}

equals(other: ConstantExpression): boolean {
return other instanceof ConstantList && this.elements.every((e, i) => e.equals(other.elements[i]));
}

toString(): string {
return `[${this.elements.join(', ')}]`;
}
}

export class ConstantMap extends ConstantExpression {
constructor(readonly entries: ConstantMapEntry[]) {
super();
}

equals(other: ConstantExpression): boolean {
return other instanceof ConstantMap && this.entries.every((e, i) => e.equals(other.entries[i]));
}

toString(): string {
return `{${this.entries.join(', ')}}`;
}
}

export class ConstantMapEntry {
constructor(
readonly key: ConstantExpression,
readonly value: ConstantExpression,
) {}

equals(other: ConstantMapEntry): boolean {
return this.key.equals(other.key) && this.value.equals(other.value);
}

toString(): string {
return `${this.key}: ${this.value}`;
}
}

class ConstantNullClass extends ConstantExpression {
equals(other: ConstantExpression): boolean {
return other instanceof ConstantNullClass;
Expand Down Expand Up @@ -209,4 +252,16 @@ export class ConstantString extends ConstantExpression {
}
}

class UnknownValueClass extends ConstantExpression {
override equals(other: ConstantExpression): boolean {
return other instanceof UnknownValueClass;
}

toString(): string {
return '$unknown';
}
}

export const UnknownValue = new UnknownValueClass();

/* c8 ignore stop */
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {
ParameterSubstitutions,
ConstantBoolean,
ConstantExpression,
ConstantFloat,
ConstantInt,
ConstantList,
ConstantMap,
ConstantNull,
ConstantString,
IntermediateBlockLambda,
IntermediateExpressionLambda,
ParameterSubstitutions,
SimplifiedExpression,
UnknownValue,
} from './model.js';
import { AstNode } from 'langium';
import {
Expand All @@ -22,6 +23,8 @@ import {
isSdsIndexedAccess,
isSdsInfixOperation,
isSdsInt,
isSdsList,
isSdsMap,
isSdsMemberAccess,
isSdsNull,
isSdsParenthesizedExpression,
Expand All @@ -45,32 +48,29 @@ import {

/* c8 ignore start */
/**
* Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `undefined`.
* Tries to evaluate this expression.
*/
export const toConstantExpressionOrUndefined = (node: AstNode | undefined): ConstantExpression | undefined => {
export const toConstantExpression = (node: AstNode | undefined): ConstantExpression => {
if (!node) {
return undefined;
return UnknownValue;
}

return toConstantExpressionOrUndefinedImpl(node, new Map());
return toConstantExpressionImpl(node, new Map());
};

const toConstantExpressionOrUndefinedImpl = (
node: AstNode,
substitutions: ParameterSubstitutions,
): ConstantExpression | undefined => {
const toConstantExpressionImpl = (node: AstNode, substitutions: ParameterSubstitutions): ConstantExpression => {
const simplifiedExpression = simplify(node, substitutions)?.unwrap();
if (simplifiedExpression instanceof ConstantExpression) {
return simplifiedExpression;
} else {
return undefined;
return UnknownValue;
}
};

const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => {
const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression => {
// Only expressions have a value
if (!isSdsExpression(node)) {
return undefined;
return UnknownValue;
}

// Base cases
Expand Down Expand Up @@ -101,6 +101,10 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): Simplif
return simplify(node.value, substitutions);
} else if (isSdsInfixOperation(node)) {
return simplifyInfixOperation(node, substitutions);
} else if (isSdsList(node)) {
return new ConstantList([]);
} else if (isSdsMap(node)) {
return new ConstantMap([]);
} else if (isSdsParenthesizedExpression(node)) {
return simplify(node.expression, substitutions);
} else if (isSdsPrefixOperation(node)) {
Expand All @@ -125,10 +129,7 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): Simplif
throw new Error(`Missing case to handle ${node.$type}.`);
};

const simplifyBlockLambda = (
_node: SdsBlockLambda,
_substitutions: ParameterSubstitutions,
): IntermediateBlockLambda | undefined => {
const simplifyBlockLambda = (_node: SdsBlockLambda, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
// return when {
// callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateBlockLambda(
// parameters = parametersOrEmpty(),
Expand All @@ -137,13 +138,13 @@ const simplifyBlockLambda = (
// )
// else -> undefined
// }
return undefined;
return UnknownValue;
};

const simplifyExpressionLambda = (
_node: SdsExpressionLambda,
_substitutions: ParameterSubstitutions,
): IntermediateExpressionLambda | undefined => {
): SimplifiedExpression => {
// return when {
// callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateExpressionLambda(
// parameters = parametersOrEmpty(),
Expand All @@ -152,19 +153,23 @@ const simplifyExpressionLambda = (
// )
// else -> undefined
// }
return undefined;
return UnknownValue;
};

const simplifyInfixOperation = (
node: SdsInfixOperation,
substitutions: ParameterSubstitutions,
): ConstantExpression | undefined => {
): SimplifiedExpression => {
// By design none of the operators are short-circuited
const constantLeft = toConstantExpressionOrUndefinedImpl(node.leftOperand, substitutions);
if (!constantLeft) return;
const constantLeft = toConstantExpressionImpl(node.leftOperand, substitutions);
if (constantLeft === UnknownValue) {
return UnknownValue;
}

const constantRight = toConstantExpressionOrUndefinedImpl(node.rightOperand, substitutions);
if (!constantRight) return;
const constantRight = toConstantExpressionImpl(node.rightOperand, substitutions);
if (constantRight === UnknownValue) {
return UnknownValue;
}

// return when (operator()) {
// Or -> simplifyLogicalOp(constantLeft, Boolean::or, constantRight)
Expand Down Expand Up @@ -232,7 +237,7 @@ const simplifyInfixOperation = (
// else -> constantLeft
// }
// }
return undefined;
return UnknownValue;
};

// private fun simplifyLogicalOp(
Expand Down Expand Up @@ -288,9 +293,11 @@ const simplifyInfixOperation = (
const simplifyPrefixOperation = (
node: SdsPrefixOperation,
substitutions: ParameterSubstitutions,
): ConstantExpression | undefined => {
const constantOperand = toConstantExpressionOrUndefinedImpl(node.operand, substitutions);
if (!constantOperand) return;
): SimplifiedExpression => {
const constantOperand = toConstantExpressionImpl(node.operand, substitutions);
if (constantOperand === UnknownValue) {
return UnknownValue;
}

if (node.operator === 'not') {
if (constantOperand instanceof ConstantBoolean) {
Expand All @@ -304,22 +311,22 @@ const simplifyPrefixOperation = (
}
}

return undefined;
return UnknownValue;
};

const simplifyTemplateString = (
node: SdsTemplateString,
substitutions: ParameterSubstitutions,
): ConstantExpression | undefined => {
const constantExpressions = node.expressions.map((it) => toConstantExpressionOrUndefinedImpl(it, substitutions));
if (constantExpressions.some((it) => it === undefined)) {
return undefined;
): SimplifiedExpression => {
const constantExpressions = node.expressions.map((it) => toConstantExpressionImpl(it, substitutions));
if (constantExpressions.some((it) => it === UnknownValue)) {
return UnknownValue;
}

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

const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => {
const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
// val simpleReceiver = simplifyReceiver(substitutions) ?: return undefined
// val newSubstitutions = buildNewSubstitutions(simpleReceiver, substitutions)
//
Expand All @@ -340,7 +347,7 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S
// )
// }
// }
return undefined;
return UnknownValue;
};

// private fun SdsCall.simplifyReceiver(substitutions: ParameterSubstitutions): SdsIntermediateCallable? {
Expand Down Expand Up @@ -387,19 +394,16 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S
const simplifyIndexedAccess = (
_node: SdsIndexedAccess,
_substitutions: ParameterSubstitutions,
): SimplifiedExpression | undefined => {
): SimplifiedExpression => {
// val simpleReceiver = receiver.simplify(substitutions) as? SdsIntermediateVariadicArguments ?: return undefined
// val simpleIndex = index.simplify(substitutions) as? SdsConstantInt ?: return undefined
//
// return simpleReceiver.getArgumentByIndexOrNull(simpleIndex.value)
// }
return undefined;
return UnknownValue;
};

const simplifyMemberAccess = (
_node: SdsMemberAccess,
_substitutions: ParameterSubstitutions,
): SimplifiedExpression | undefined => {
const simplifyMemberAccess = (_node: SdsMemberAccess, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
// private fun SdsMemberAccess.simplifyMemberAccess(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? {
// if (member.declaration is SdsEnumVariant) {
// return member.simplifyReference(substitutions)
Expand All @@ -413,13 +417,10 @@ const simplifyMemberAccess = (
// is SdsIntermediateRecord -> simpleReceiver.getSubstitutionByReferenceOrNull(member)
// else -> undefined
// }
return undefined;
return UnknownValue;
};

const simplifyReference = (
_node: SdsReference,
_substitutions: ParameterSubstitutions,
): SimplifiedExpression | undefined => {
const simplifyReference = (_node: SdsReference, _substitutions: ParameterSubstitutions): SimplifiedExpression => {
// return when (val declaration = this.declaration) {
// is SdsEnumVariant -> when {
// declaration.parametersOrEmpty().isEmpty() -> SdsConstantEnumVariant(declaration)
Expand All @@ -430,7 +431,7 @@ const simplifyReference = (
// is SdsStep -> declaration.simplifyStep()
// else -> undefined
// }
return undefined;
return UnknownValue;
};

// private fun SdsAbstractAssignee.simplifyAssignee(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? {
Expand Down
Loading

0 comments on commit 097764d

Please sign in to comment.