Skip to content

Commit

Permalink
fix: handling of default values in entry point of call graph computat…
Browse files Browse the repository at this point in the history
…ion (#797)

### Summary of Changes

Consider the following code:

```
@pure fun default() -> r: Any

segment mySegment(param: () -> () = default) {
    »param()«;
}
```

When computing the call graph for the `param()` call, we were previously
always substituting `param` with its default value, since the parameter
substitutions were empty in the entry point. Because of this, we were
incorrectly considering the `param()` call as pure, just like the
segment `mySegment`.
  • Loading branch information
lars-reimann authored Nov 24, 2023
1 parent 5017759 commit a5db23c
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,7 @@ export class SafeDsCallGraphComputer {
}
}

private doGetCallGraph(
node: SdsCall | SdsCallable,
substitutions: ParameterSubstitutions = NO_SUBSTITUTIONS,
): CallGraph {
private doGetCallGraph(node: SdsCall | SdsCallable, substitutions: ParameterSubstitutions): CallGraph {
if (isSdsCall(node)) {
const call = this.createSyntheticCallForCall(node, substitutions);
return this.getCallGraphWithRecursionCheck(call, []);
Expand Down Expand Up @@ -266,7 +263,7 @@ export class SafeDsCallGraphComputer {
if (!callableOrParameter || isSdsAnnotation(callableOrParameter) || isSdsCallableType(callableOrParameter)) {
return undefined;
} else if (isSdsParameter(callableOrParameter)) {
// Parameter is set explicitly
// Parameter is set
const substitution = substitutions.get(callableOrParameter);
if (substitution) {
if (substitution instanceof EvaluatedCallable) {
Expand All @@ -277,11 +274,8 @@ export class SafeDsCallGraphComputer {
}
}

// Parameter might have a default value
if (!callableOrParameter.defaultValue) {
return new NamedCallable(callableOrParameter);
}
return this.getEvaluatedCallable(callableOrParameter.defaultValue, substitutions);
// Parameter is not set
return new NamedCallable(callableOrParameter);
} else if (isNamed(callableOrParameter)) {
return new NamedCallable(callableOrParameter);
} else if (isSdsBlockLambda(callableOrParameter)) {
Expand Down Expand Up @@ -314,14 +308,16 @@ export class SafeDsCallGraphComputer {
args: SdsArgument[],
substitutions: ParameterSubstitutions,
): ParameterSubstitutions {
// TODO: Use this in the partial evaluator too. Here (maybe) filter and keep only the substitutions that are
// callables.
if (!callable || isSdsParameter(callable.callable)) {
return NO_SUBSTITUTIONS;
}

// Substitutions on creation
const substitutionsOnCreation = callable.substitutionsOnCreation;

// Substitutions on call
// Substitutions on call via arguments
const parameters = getParameters(callable.callable);
const substitutionsOnCall = new Map(
args.flatMap((it) => {
Expand Down Expand Up @@ -350,7 +346,19 @@ export class SafeDsCallGraphComputer {
}),
);

return new Map([...substitutionsOnCreation, ...substitutionsOnCall]);
// Substitutions on call via default values
let result = new Map([...substitutionsOnCreation, ...substitutionsOnCall]);
for (const parameter of parameters) {
if (!result.has(parameter) && parameter.defaultValue) {
// Default values may depend on the values of previous parameters, so we have to evaluate them in order
const value = this.getEvaluatedCallable(parameter.defaultValue, result);
if (value) {
result = new Map([...result, [parameter, value]]);
}
}
}

return result;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tests.callGraph.blockLambdaCall.defaultValue.previousParameter

@Pure fun default() -> result: Any

pipeline myPipeline {
val lambda = (
f: () -> (result: Any) = default,
g: Any = f()
) {};

// $TEST$ ["$blockLambda", "default"]
»lambda()«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tests.callGraph.classCall.defaultValue.previousParameter

@Pure fun default() -> result: Any

class MyClass(
f: () -> (result: Any) = default,
g: Any = f()
)

pipeline myPipeline {
// $TEST$ ["MyClass", "default", "default"]
»MyClass()«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package tests.callGraph.defaultValueHandlingInEntryPoint

/*
* We must **not** assume that the default value is used just because no substitution is given for a parameter.
*/

@Pure fun default() -> r: Any

segment mySegment(param: () -> () = default) {
// $TEST$ ["param"]
»param()«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package tests.callGraph.enumVariantCall.defaultValue.previousParameter

@Pure fun default() -> result: Any

enum MyEnum {
MyVariant(
f: () -> (result: Any) = default,
g: Any = f()
)
}

pipeline myPipeline {
// $TEST$ ["MyVariant", "default", "default"]
»MyEnum.MyVariant()«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tests.callGraph.expressionLambdaCall.defaultValue.previousParameter

@Pure fun default() -> result: Any

pipeline myPipeline {
val lambda = (
f: () -> (result: Any) = default,
g: Any = f()
) -> 1;

// $TEST$ ["$expressionLambda", "default"]
»lambda()«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tests.callGraph.functionCall.defaultValue.previousParameter

@Pure fun default() -> result: Any

@Pure fun myFunction(
f: () -> (result: Any) = default,
g: Any = f()
)

pipeline myPipeline {
// $TEST$ ["myFunction", "default", "default"]
»myFunction()«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tests.callGraph.segmentCall.defaultValue.previousParameter

@Pure fun default() -> result: Any

segment mySegment(
f: () -> (result: Any) = default,
g: Any = f()
) {}

pipeline myPipeline {
// $TEST$ ["mySegment", "default"]
»mySegment()«;
}

0 comments on commit a5db23c

Please sign in to comment.