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: error if value assigned to constant parameters is not constant #646

Merged
merged 5 commits into from
Oct 17, 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
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