Skip to content

Commit 83720e3

Browse files
committed
Adapt constant folding to ignore functions returning Unknown
Some functions are intended to have side effects and therefore should not be folded even if all parameters are constants. Currently the constant folding optimizer throws an error if such a function does not provide an implementation. This change allows functions with side effects to return Unknown.
1 parent d8351df commit 83720e3

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

cel/folding.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ func tryFold(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) error {
134134
if err != nil {
135135
return err
136136
}
137+
// Don't update the expression if it's an unknown value.
138+
if out.Type() == types.UnknownType {
139+
return nil
140+
}
137141
// Update the fold expression to be a literal.
138142
ctx.UpdateExpr(expr, ctx.NewLiteral(out))
139143
return nil

cel/folding_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ package cel
1717
import (
1818
"reflect"
1919
"sort"
20+
"strings"
2021
"testing"
2122

2223
"google.golang.org/protobuf/encoding/prototext"
2324
"google.golang.org/protobuf/proto"
2425

2526
"github.com/google/cel-go/common/ast"
27+
"github.com/google/cel-go/common/types"
28+
"github.com/google/cel-go/common/types/ref"
2629

2730
proto3pb "github.com/google/cel-go/test/proto3pb"
2831
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
@@ -313,6 +316,83 @@ func TestConstantFoldingOptimizer(t *testing.T) {
313316
}
314317
}
315318

319+
func TestConstantFoldingCallsWithSideEffects(t *testing.T) {
320+
tests := []struct {
321+
expr string
322+
folded string
323+
error string
324+
}{
325+
{
326+
expr: `noSideEffect(3)`,
327+
folded: `3`,
328+
},
329+
{
330+
expr: `withSideEffect(3)`,
331+
folded: `withSideEffect(3)`,
332+
},
333+
{
334+
expr: `noImpl(3)`,
335+
error: `constant-folding evaluation failed: no such overload: noImpl`,
336+
},
337+
}
338+
e, err := NewEnv(
339+
OptionalTypes(),
340+
EnableMacroCallTracking(),
341+
Function("noSideEffect",
342+
Overload("noSideEffect_int_int",
343+
[]*Type{IntType},
344+
IntType, FunctionBinding(func(args ...ref.Val) ref.Val {
345+
return args[0]
346+
}))),
347+
Function("withSideEffect",
348+
Overload("withSideEffect_int_int",
349+
[]*Type{IntType},
350+
IntType, FunctionBinding(func(args ...ref.Val) ref.Val {
351+
return &types.Unknown{}
352+
}))),
353+
Function("noImpl",
354+
Overload("noImpl_int_int",
355+
[]*Type{IntType},
356+
IntType)),
357+
)
358+
if err != nil {
359+
t.Fatalf("NewEnv() failed: %v", err)
360+
}
361+
for _, tst := range tests {
362+
tc := tst
363+
t.Run(tc.expr, func(t *testing.T) {
364+
checked, iss := e.Compile(tc.expr)
365+
if iss.Err() != nil {
366+
t.Fatalf("Compile() failed: %v", iss.Err())
367+
}
368+
folder, err := NewConstantFoldingOptimizer()
369+
if err != nil {
370+
t.Fatalf("NewConstantFoldingOptimizer() failed: %v", err)
371+
}
372+
opt := NewStaticOptimizer(folder)
373+
optimized, iss := opt.Optimize(e, checked)
374+
if tc.error != "" {
375+
if iss.Err() == nil {
376+
t.Errorf("got nil, wanted error containing %q", tc.error)
377+
} else if !strings.Contains(iss.Err().Error(), tc.error) {
378+
t.Errorf("got %q, wanted error containing %q", iss.Err().Error(), tc.error)
379+
}
380+
return
381+
}
382+
if iss.Err() != nil {
383+
t.Fatalf("Optimize() generated an invalid AST: %v", iss.Err())
384+
}
385+
folded, err := AstToString(optimized)
386+
if err != nil {
387+
t.Fatalf("AstToString() failed: %v", err)
388+
}
389+
if folded != tc.folded {
390+
t.Errorf("got %q, wanted %q", folded, tc.folded)
391+
}
392+
})
393+
}
394+
}
395+
316396
func TestConstantFoldingOptimizerMacroElimination(t *testing.T) {
317397
tests := []struct {
318398
expr string

0 commit comments

Comments
 (0)