@@ -38,6 +38,23 @@ func MaxConstantFoldIterations(limit int) ConstantFoldingOption {
38
38
}
39
39
}
40
40
41
+ // Adds an Activation which provides known values for the folding evaluator
42
+ //
43
+ // Any values the activation provides will be used by the constant folder and turned into
44
+ // literals in the AST.
45
+ //
46
+ // Defaults to the NoVars() Activation
47
+ func FoldKnownValues (knownValues Activation ) ConstantFoldingOption {
48
+ return func (opt * constantFoldingOptimizer ) (* constantFoldingOptimizer , error ) {
49
+ if knownValues != nil {
50
+ opt .knownValues = knownValues
51
+ } else {
52
+ opt .knownValues = NoVars ()
53
+ }
54
+ return opt , nil
55
+ }
56
+ }
57
+
41
58
// NewConstantFoldingOptimizer creates an optimizer which inlines constant scalar an aggregate
42
59
// literal values within function calls and select statements with their evaluated result.
43
60
func NewConstantFoldingOptimizer (opts ... ConstantFoldingOption ) (ASTOptimizer , error ) {
@@ -56,6 +73,7 @@ func NewConstantFoldingOptimizer(opts ...ConstantFoldingOption) (ASTOptimizer, e
56
73
57
74
type constantFoldingOptimizer struct {
58
75
maxFoldIterations int
76
+ knownValues Activation
59
77
}
60
78
61
79
// Optimize queries the expression graph for scalar and aggregate literal expressions within call and
@@ -68,7 +86,7 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST)
68
86
// Walk the list of foldable expression and continue to fold until there are no more folds left.
69
87
// All of the fold candidates returned by the constantExprMatcher should succeed unless there's
70
88
// a logic bug with the selection of expressions.
71
- constantExprMatcherCapture := func (e ast.NavigableExpr ) bool { return constantExprMatcher (ctx , a , e ) }
89
+ constantExprMatcherCapture := func (e ast.NavigableExpr ) bool { return opt . constantExprMatcher (ctx , a , e ) }
72
90
foldableExprs := ast .MatchDescendants (root , constantExprMatcherCapture )
73
91
foldCount := 0
74
92
for len (foldableExprs ) != 0 && foldCount < opt .maxFoldIterations {
@@ -83,8 +101,10 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST)
83
101
continue
84
102
}
85
103
// Otherwise, assume all context is needed to evaluate the expression.
86
- err := tryFold (ctx , a , fold )
87
- if err != nil {
104
+ err := opt .tryFold (ctx , a , fold )
105
+ // Ignore errors for identifiers, since there is no guarantee that the environment
106
+ // has a value for them.
107
+ if err != nil && fold .Kind () != ast .IdentKind {
88
108
ctx .ReportErrorAtID (fold .ID (), "constant-folding evaluation failed: %v" , err .Error ())
89
109
return a
90
110
}
@@ -96,7 +116,7 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST)
96
116
// one last time. In this case, there's no guarantee they'll run, so we only update the
97
117
// target comprehension node with the literal value if the evaluation succeeds.
98
118
for _ , compre := range ast .MatchDescendants (root , ast .KindMatcher (ast .ComprehensionKind )) {
99
- tryFold (ctx , a , compre )
119
+ opt . tryFold (ctx , a , compre )
100
120
}
101
121
102
122
// If the output is a list, map, or struct which contains optional entries, then prune it
@@ -126,7 +146,7 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST)
126
146
//
127
147
// If the evaluation succeeds, the input expr value will be modified to become a literal, otherwise
128
148
// the method will return an error.
129
- func tryFold (ctx * OptimizerContext , a * ast.AST , expr ast.Expr ) error {
149
+ func ( opt * constantFoldingOptimizer ) tryFold (ctx * OptimizerContext , a * ast.AST , expr ast.Expr ) error {
130
150
// Assume all context is needed to evaluate the expression.
131
151
subAST := & Ast {
132
152
impl : ast .NewCheckedAST (ast .NewAST (expr , a .SourceInfo ()), a .TypeMap (), a .ReferenceMap ()),
@@ -135,7 +155,11 @@ func tryFold(ctx *OptimizerContext, a *ast.AST, expr ast.Expr) error {
135
155
if err != nil {
136
156
return err
137
157
}
138
- out , _ , err := prg .Eval (NoVars ())
158
+ activation := opt .knownValues
159
+ if activation == nil {
160
+ activation = NoVars ()
161
+ }
162
+ out , _ , err := prg .Eval (activation )
139
163
if err != nil {
140
164
return err
141
165
}
@@ -469,13 +493,15 @@ func adaptLiteral(ctx *OptimizerContext, val ref.Val) (ast.Expr, error) {
469
493
// Only comprehensions which are not nested are included as possible constant folds, and only
470
494
// if all variables referenced in the comprehension stack exist are only iteration or
471
495
// accumulation variables.
472
- func constantExprMatcher (ctx * OptimizerContext , a * ast.AST , e ast.NavigableExpr ) bool {
496
+ func ( opt * constantFoldingOptimizer ) constantExprMatcher (ctx * OptimizerContext , a * ast.AST , e ast.NavigableExpr ) bool {
473
497
switch e .Kind () {
474
498
case ast .CallKind :
475
499
return constantCallMatcher (e )
476
500
case ast .SelectKind :
477
501
sel := e .AsSelect () // guaranteed to be a navigable value
478
502
return constantMatcher (sel .Operand ().(ast.NavigableExpr ))
503
+ case ast .IdentKind :
504
+ return opt .knownValues != nil && a .ReferenceMap ()[e .ID ()] != nil
479
505
case ast .ComprehensionKind :
480
506
if isNestedComprehension (e ) {
481
507
return false
0 commit comments