@@ -38,6 +38,23 @@ func MaxConstantFoldIterations(limit int) ConstantFoldingOption {
3838 }
3939}
4040
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+
4158// NewConstantFoldingOptimizer creates an optimizer which inlines constant scalar an aggregate
4259// literal values within function calls and select statements with their evaluated result.
4360func NewConstantFoldingOptimizer (opts ... ConstantFoldingOption ) (ASTOptimizer , error ) {
@@ -56,6 +73,7 @@ func NewConstantFoldingOptimizer(opts ...ConstantFoldingOption) (ASTOptimizer, e
5673
5774type constantFoldingOptimizer struct {
5875 maxFoldIterations int
76+ knownValues Activation
5977}
6078
6179// 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)
6886 // Walk the list of foldable expression and continue to fold until there are no more folds left.
6987 // All of the fold candidates returned by the constantExprMatcher should succeed unless there's
7088 // 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 ) }
7290 foldableExprs := ast .MatchDescendants (root , constantExprMatcherCapture )
7391 foldCount := 0
7492 for len (foldableExprs ) != 0 && foldCount < opt .maxFoldIterations {
@@ -83,8 +101,10 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST)
83101 continue
84102 }
85103 // 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 {
88108 ctx .ReportErrorAtID (fold .ID (), "constant-folding evaluation failed: %v" , err .Error ())
89109 return a
90110 }
@@ -96,7 +116,7 @@ func (opt *constantFoldingOptimizer) Optimize(ctx *OptimizerContext, a *ast.AST)
96116 // one last time. In this case, there's no guarantee they'll run, so we only update the
97117 // target comprehension node with the literal value if the evaluation succeeds.
98118 for _ , compre := range ast .MatchDescendants (root , ast .KindMatcher (ast .ComprehensionKind )) {
99- tryFold (ctx , a , compre )
119+ opt . tryFold (ctx , a , compre )
100120 }
101121
102122 // 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)
126146//
127147// If the evaluation succeeds, the input expr value will be modified to become a literal, otherwise
128148// 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 {
130150 // Assume all context is needed to evaluate the expression.
131151 subAST := & Ast {
132152 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 {
135155 if err != nil {
136156 return err
137157 }
138- out , _ , err := prg .Eval (NoVars ())
158+ activation := opt .knownValues
159+ if activation == nil {
160+ activation = NoVars ()
161+ }
162+ out , _ , err := prg .Eval (activation )
139163 if err != nil {
140164 return err
141165 }
@@ -469,13 +493,15 @@ func adaptLiteral(ctx *OptimizerContext, val ref.Val) (ast.Expr, error) {
469493// Only comprehensions which are not nested are included as possible constant folds, and only
470494// if all variables referenced in the comprehension stack exist are only iteration or
471495// 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 {
473497 switch e .Kind () {
474498 case ast .CallKind :
475499 return constantCallMatcher (e )
476500 case ast .SelectKind :
477501 sel := e .AsSelect () // guaranteed to be a navigable value
478502 return constantMatcher (sel .Operand ().(ast.NavigableExpr ))
503+ case ast .IdentKind :
504+ return opt .knownValues != nil && a .ReferenceMap ()[e .ID ()] != nil
479505 case ast .ComprehensionKind :
480506 if isNestedComprehension (e ) {
481507 return false
0 commit comments