cuelang.org/go@v0.13.0/internal/core/dep/dep.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package dep analyzes dependencies between values.
    16  package dep
    17  
    18  import (
    19  	"cuelang.org/go/cue/errors"
    20  	"cuelang.org/go/internal"
    21  	"cuelang.org/go/internal/core/adt"
    22  )
    23  
    24  // Dependencies
    25  //
    26  // A dependency is a reference relation from one Vertex to another. A Vertex
    27  // has multiple Conjuncts, each of which is associated with an expression.
    28  // Each expression, in turn, may have multiple references, each representing
    29  // a single dependency.
    30  //
    31  // A reference that occurs in a node will point to another node. A reference
    32  // `x.y` may point to a node `x.y` as well as `x`. By default, only the most
    33  // precise node is reported, which is `x.y` if it exists, or `x` otherwise.
    34  // In the latter case, a path is associated with the reference to indicate
    35  // the specific non-existing path that is needed for that dependency. (TODO)
    36  //
    37  // A single reference may point to multiple nodes. For instance,
    38  // (a & b).z may point to both `a.z` and `b.z`. This has to be taken into
    39  // account if dep is used for substitutions.
    40  //
    41  //
    42  //   field: Conjunct
    43  //             |
    44  //           Expr                       Conjunct Expression
    45  //             |- Reference             A reference to led to a target
    46  //             |-    \- Target Node     Pointed to by Reference
    47  //             |-         \- UsedPath   The sole path used within Node
    48  
    49  // TODO: verify that these concepts are correctly reflected in the API:
    50  // Source:
    51  //     The CUE value for which dependencies are analyzed.
    52  //     This may differ per dependency for dynamic and transitive analysis.
    53  // Target:
    54  //     The field to which the found reference resolves.
    55  // Reference:
    56  //     The reference that resolved to the dependency.
    57  //     Replacing this reference in the conjuncts of the source vertex with a
    58  //     link to the target vertex yields the same result if there only a single
    59  //     dependency matching this reference.
    60  // Conjunct:
    61  //     The conjunct in which the Reference was found.
    62  // Used Path:
    63  //     The target vertex may be a parent of the actual, more precise,
    64  //     dependency, if the latter does not yet exist. The target path is the path
    65  //     from the target vertex to the actual dependency.
    66  // Trace:
    67  //     A sequence of dependencies leading to the result in case of transitive
    68  //     dependencies.
    69  
    70  // TODO: for a public API, a better approach seems to be to have a single
    71  // Visit method, with a configuration to set a bunch of orthogonal options.
    72  // Here are some examples of the options:
    73  //   - Dynamic:    evaluate and descend into computed fields.
    74  //   - Recurse:    evaluate dependencies of subfields as well.
    75  //   - Inner:      report dependencies within the root being visited.
    76  //   - RootLess:   report dependencies that do not have a path to the root.
    77  //   - Transitive: get all dependencies, not just the direct ones.
    78  //   - Substitute: do not get precise dependencies, but rather keep them
    79  //         such that each expression needs to be replaced with at most
    80  //         one dependency. Could be a method on Dependency.
    81  //   - ContinueOnError:  continue visiting even if there are errors.
    82  //   [add more as they come up]
    83  //
    84  
    85  type Config struct {
    86  	// Dynamic enables evaluting dependencies Vertex Arcs, recursively
    87  	Dynamic bool
    88  
    89  	// Descend enables recursively descending into fields. This option is
    90  	// implied by Dynamic.
    91  	Descend bool
    92  
    93  	// Cycles allows a Node to reported more than once. This includes the node
    94  	// passed to Visit, which is otherwise never reported. This option can be
    95  	// used to disable cycle checking. TODO: this is not yet implemented.
    96  	AllowCycles bool
    97  
    98  	// Rootless enables reporting nodes that do not have a path from the root.
    99  	// This includes variables of comprehensions and fields of composite literal
   100  	// values that are part of expressions, such as {out: v}.out.
   101  	Rootless bool
   102  
   103  	// TODO:
   104  	// ContinueOnError indicates whether to continue finding dependencies
   105  	// even when there are errors.
   106  	// ContinueOnError bool
   107  
   108  	//  pkg indicates the main package for which the analyzer is configured,
   109  	// which is used for reporting purposes.
   110  	Pkg *adt.ImportReference
   111  }
   112  
   113  // A Dependency is a reference and the node that reference resolves to.
   114  type Dependency struct {
   115  	// Node is the referenced node.
   116  	Node *adt.Vertex
   117  
   118  	// Reference is the expression that referenced the node.
   119  	Reference adt.Resolver
   120  
   121  	pkg *adt.ImportReference
   122  
   123  	top bool
   124  
   125  	visitor *visitor
   126  }
   127  
   128  // Recurse visits the dependencies of d.Node, using the same visit function as
   129  // the original.
   130  func (d *Dependency) Recurse() {
   131  	savedAll := d.visitor.all
   132  	savedTop := d.visitor.top
   133  	savedMarked := d.visitor.marked
   134  	d.visitor.all = d.visitor.recurse
   135  	d.visitor.top = true
   136  	d.visitor.marked = nil
   137  
   138  	d.visitor.visitReusingVisitor(d.Node, false)
   139  
   140  	d.visitor.all = savedAll
   141  	d.visitor.top = savedTop
   142  	d.visitor.marked = savedMarked
   143  }
   144  
   145  // Import returns the import reference or nil if the reference was within
   146  // the same package as the visited Vertex.
   147  func (d *Dependency) Import() *adt.ImportReference {
   148  	return d.pkg
   149  }
   150  
   151  // IsRoot reports whether the dependency is referenced by the root of the
   152  // original Vertex passed to any of the Visit* functions, and not one of its
   153  // descendent arcs. This always returns true for [Visit].
   154  func (d *Dependency) IsRoot() bool {
   155  	return d.top
   156  }
   157  
   158  func importRef(r adt.Expr) *adt.ImportReference {
   159  	switch x := r.(type) {
   160  	case *adt.ImportReference:
   161  		return x
   162  	case *adt.SelectorExpr:
   163  		return importRef(x.X)
   164  	case *adt.IndexExpr:
   165  		return importRef(x.X)
   166  	}
   167  	return nil
   168  }
   169  
   170  // VisitFunc is used for reporting dependencies.
   171  type VisitFunc func(Dependency) error
   172  
   173  var empty *adt.Vertex
   174  
   175  func init() {
   176  	// TODO: Consider setting a non-nil BaseValue.
   177  	empty = &adt.Vertex{}
   178  	empty.ForceDone()
   179  }
   180  
   181  var zeroConfig = &Config{}
   182  
   183  // Visit calls f for the dependencies of n as determined by the given
   184  // configuration.
   185  func Visit(cfg *Config, c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
   186  	if cfg == nil {
   187  		cfg = zeroConfig
   188  	}
   189  	if c == nil {
   190  		panic("nil context")
   191  	}
   192  	v := visitor{
   193  		ctxt:       c,
   194  		fn:         f,
   195  		pkg:        cfg.Pkg,
   196  		recurse:    cfg.Descend,
   197  		all:        cfg.Descend,
   198  		top:        true,
   199  		cfgDynamic: cfg.Dynamic,
   200  	}
   201  	return v.visitReusingVisitor(n, true)
   202  }
   203  
   204  // visitReusingVisitor is factored out of Visit so that we may reuse visitor.
   205  func (v *visitor) visitReusingVisitor(n *adt.Vertex, top bool) error {
   206  	if v.cfgDynamic {
   207  		if v.marked == nil {
   208  			v.marked = marked{}
   209  		}
   210  		v.marked.markExpr(n)
   211  
   212  		v.dynamic(n, top)
   213  	} else {
   214  		v.visit(n, top)
   215  	}
   216  	return v.err
   217  }
   218  
   219  func (v *visitor) visit(n *adt.Vertex, top bool) (err error) {
   220  	savedNode := v.node
   221  	savedTop := v.top
   222  
   223  	v.node = n
   224  	v.top = top
   225  
   226  	defer func() {
   227  		v.node = savedNode
   228  		v.top = savedTop
   229  
   230  		switch x := recover(); x {
   231  		case nil:
   232  		case aborted:
   233  			err = v.err
   234  		default:
   235  			panic(x)
   236  		}
   237  	}()
   238  
   239  	n.VisitLeafConjuncts(func(x adt.Conjunct) bool {
   240  		v.markExpr(x.Env, x.Elem())
   241  		return true
   242  	})
   243  
   244  	return nil
   245  }
   246  
   247  var aborted = errors.New("aborted")
   248  
   249  type visitor struct {
   250  	ctxt *adt.OpContext
   251  	fn   VisitFunc
   252  	node *adt.Vertex
   253  	err  error
   254  	pkg  *adt.ImportReference
   255  
   256  	// recurse indicates whether, during static analysis, to process references
   257  	// that will be unified into different fields.
   258  	recurse bool
   259  	// all indicates wether to process references that would be unified into
   260  	// different fields. This similar to recurse, but sometimes gets temporarily
   261  	// overridden to deal with special cases.
   262  	all       bool
   263  	top       bool
   264  	topRef    adt.Resolver
   265  	pathStack []refEntry
   266  	numRefs   int // count of reported dependencies
   267  
   268  	// cfgDynamic is kept from the original config.
   269  	cfgDynamic bool
   270  
   271  	marked marked
   272  }
   273  
   274  type refEntry struct {
   275  	env *adt.Environment
   276  	ref adt.Resolver
   277  }
   278  
   279  // TODO: factor out the below logic as either a low-level dependency analyzer or
   280  // some walk functionality.
   281  
   282  // markExpr visits all nodes in an expression to mark dependencies.
   283  func (c *visitor) markExpr(env *adt.Environment, expr adt.Elem) {
   284  	if expr, ok := expr.(adt.Resolver); ok {
   285  		c.markResolver(env, expr)
   286  		return
   287  	}
   288  
   289  	saved := c.topRef
   290  	c.topRef = nil
   291  	defer func() { c.topRef = saved }()
   292  
   293  	switch x := expr.(type) {
   294  	case nil:
   295  	case *adt.BinaryExpr:
   296  		c.markExpr(env, x.X)
   297  		c.markExpr(env, x.Y)
   298  
   299  	case *adt.UnaryExpr:
   300  		c.markExpr(env, x.X)
   301  
   302  	case *adt.Interpolation:
   303  		for i := 1; i < len(x.Parts); i += 2 {
   304  			c.markExpr(env, x.Parts[i])
   305  		}
   306  
   307  	case *adt.BoundExpr:
   308  		c.markExpr(env, x.Expr)
   309  
   310  	case *adt.CallExpr:
   311  		c.markExpr(env, x.Fun)
   312  		saved := c.all
   313  		c.all = true
   314  		for _, a := range x.Args {
   315  			c.markExpr(env, a)
   316  		}
   317  		c.all = saved
   318  
   319  	case *adt.DisjunctionExpr:
   320  		for _, d := range x.Values {
   321  			c.markExpr(env, d.Val)
   322  		}
   323  
   324  	case *adt.SliceExpr:
   325  		c.markExpr(env, x.X)
   326  		c.markExpr(env, x.Lo)
   327  		c.markExpr(env, x.Hi)
   328  		c.markExpr(env, x.Stride)
   329  
   330  	case *adt.ListLit:
   331  		env := &adt.Environment{Up: env, Vertex: empty}
   332  		for _, e := range x.Elems {
   333  			switch x := e.(type) {
   334  			case *adt.Comprehension:
   335  				c.markComprehension(env, x)
   336  
   337  			case adt.Expr:
   338  				c.markSubExpr(env, x)
   339  
   340  			case *adt.Ellipsis:
   341  				if x.Value != nil {
   342  					c.markSubExpr(env, x.Value)
   343  				}
   344  			}
   345  		}
   346  
   347  	case *adt.StructLit:
   348  		env := &adt.Environment{Up: env, Vertex: empty}
   349  		for _, e := range x.Decls {
   350  			c.markDecl(env, e)
   351  		}
   352  
   353  	case *adt.Comprehension:
   354  		c.markComprehension(env, x)
   355  	}
   356  }
   357  
   358  // markResolve resolves dependencies.
   359  func (c *visitor) markResolver(env *adt.Environment, r adt.Resolver) {
   360  	// Note: it is okay to pass an empty CloseInfo{} here as we assume that
   361  	// all nodes are finalized already and we need neither closedness nor cycle
   362  	// checks.
   363  	ref, _ := c.ctxt.Resolve(adt.MakeConjunct(env, r, adt.CloseInfo{}), r)
   364  
   365  	// TODO: consider the case where an inlined composite literal does not
   366  	// resolve, but has references. For instance, {a: k, ref}.b would result
   367  	// in a failure during evaluation if b is not defined within ref. However,
   368  	// ref might still specialize to allow b.
   369  
   370  	if ref != nil {
   371  		c.reportDependency(env, r, ref)
   372  		return
   373  	}
   374  
   375  	// It is possible that a reference cannot be resolved because it is
   376  	// incomplete. In this case, we should check whether subexpressions of the
   377  	// reference can be resolved to mark those dependencies. For instance,
   378  	// prefix paths of selectors and the value or index of an index expression
   379  	// may independently resolve to a valid dependency.
   380  
   381  	switch x := r.(type) {
   382  	case *adt.NodeLink:
   383  		panic("unreachable")
   384  
   385  	case *adt.IndexExpr:
   386  		c.markExpr(env, x.X)
   387  		c.markExpr(env, x.Index)
   388  
   389  	case *adt.SelectorExpr:
   390  		c.markExpr(env, x.X)
   391  	}
   392  }
   393  
   394  // reportDependency reports a dependency from r to v.
   395  // v must be the value that is obtained after resolving r.
   396  func (c *visitor) reportDependency(env *adt.Environment, ref adt.Resolver, v *adt.Vertex) {
   397  	if v == c.node || v == empty {
   398  		return
   399  	}
   400  
   401  	reference := ref
   402  	if c.topRef == nil && len(c.pathStack) == 0 {
   403  		saved := c.topRef
   404  		c.topRef = ref
   405  		defer func() { c.topRef = saved }()
   406  	}
   407  
   408  	// TODO: in "All" mode we still report the latest reference used, instead
   409  	// of the reference at the start of the traversal, as the self-contained
   410  	// algorithm (its only user) depends on it.
   411  	// However, if the stack is non-nil, the reference will not correctly
   412  	// reflect the substituted value, so we use the top reference instead.
   413  	if !c.recurse && len(c.pathStack) == 0 && c.topRef != nil {
   414  		reference = c.topRef
   415  	}
   416  
   417  	inspect := false
   418  
   419  	if c.ctxt.Version == internal.DevVersion {
   420  		inspect = v.IsDetached() || !v.MayAttach()
   421  	} else {
   422  		inspect = !v.Rooted()
   423  	}
   424  
   425  	if inspect {
   426  		// TODO: there is currently no way to inspect where a non-rooted node
   427  		// originated from. As of EvalV3, we allow non-rooted nodes to be
   428  		// structure shared. This makes them effectively rooted, with the
   429  		// difference that there is an indirection in BaseValue for the
   430  		// structure sharing. Nonetheless, this information is lost in the
   431  		// internal API when traversing.
   432  
   433  		// As an alternative we now do not skip processing the node if we
   434  		// an inlined, non-rooted node is associated with another node than
   435  		// the one we are currently processing.
   436  
   437  		// If a node is internal, we need to further investigate any references.
   438  		// If there are any, reference, even if it is otherwise not reported,
   439  		// we report this reference.
   440  		before := c.numRefs
   441  		c.markInternalResolvers(env, ref, v)
   442  		// TODO: this logic could probably be simplified if we let clients
   443  		// explicitly mark whether to visit rootless nodes. Visiting these
   444  		// may be necessary when substituting values.
   445  		switch _, ok := ref.(*adt.FieldReference); {
   446  		case !ok && c.isLocal(env, ref):
   447  			// 	Do not report rootless nodes for selectors.
   448  			return
   449  		case c.numRefs > before:
   450  			// For FieldReferences that resolve to something we do not need
   451  			// to report anything intermediate.
   452  			return
   453  		}
   454  	}
   455  	if hasLetParent(v) {
   456  		return
   457  	}
   458  
   459  	// Expand path.
   460  	altRef := reference
   461  	for i := len(c.pathStack) - 1; i >= 0; i-- {
   462  		x := c.pathStack[i]
   463  		var w *adt.Vertex
   464  		// TODO: instead of setting the reference, the proper thing to do is
   465  		// to record a path that still needs to be selected into the recorded
   466  		// dependency. See the Target Path definition at the top of the file.
   467  		if f := c.feature(x.env, x.ref); f != 0 {
   468  			w = v.Lookup(f)
   469  		}
   470  		if w == nil {
   471  			break
   472  		}
   473  		altRef = x.ref
   474  		if i == 0 && c.topRef != nil {
   475  			altRef = c.topRef
   476  		}
   477  		v = w
   478  	}
   479  	if inspect && len(c.pathStack) == 0 && c.topRef != nil {
   480  		altRef = c.topRef
   481  	}
   482  
   483  	// All resolvers are expressions.
   484  	if p := importRef(ref.(adt.Expr)); p != nil {
   485  		savedPkg := c.pkg
   486  		c.pkg = p
   487  		defer func() { c.pkg = savedPkg }()
   488  	}
   489  
   490  	c.numRefs++
   491  
   492  	if c.ctxt.Version == internal.DevVersion {
   493  		v.Finalize(c.ctxt)
   494  	}
   495  
   496  	d := Dependency{
   497  		Node:      v,
   498  		Reference: altRef,
   499  		pkg:       c.pkg,
   500  		top:       c.top,
   501  		visitor:   c,
   502  	}
   503  	if err := c.fn(d); err != nil {
   504  		c.err = err
   505  		panic(aborted)
   506  	}
   507  }
   508  
   509  // isLocal reports whether a non-rooted struct is an internal node or not.
   510  // If it is not, we need to further investigate any references.
   511  func (c *visitor) isLocal(env *adt.Environment, r adt.Resolver) bool {
   512  	for {
   513  		switch x := r.(type) {
   514  		case *adt.FieldReference:
   515  			for i := 0; i < int(x.UpCount); i++ {
   516  				env = env.Up
   517  			}
   518  			return env.Vertex == empty
   519  		case *adt.SelectorExpr:
   520  			r, _ = x.X.(adt.Resolver)
   521  		case *adt.IndexExpr:
   522  			r, _ = x.X.(adt.Resolver)
   523  		default:
   524  			return env.Vertex == empty
   525  		}
   526  	}
   527  }
   528  
   529  // TODO(perf): make this available as a property of vertices to avoid doing
   530  // work repeatedly.
   531  func hasLetParent(v *adt.Vertex) bool {
   532  	for ; v != nil; v = v.Parent {
   533  		if v.Label.IsLet() {
   534  			return true
   535  		}
   536  	}
   537  	return false
   538  }
   539  
   540  // markConjuncts transitively marks all reference of the current node.
   541  func (c *visitor) markConjuncts(v *adt.Vertex) {
   542  	v.VisitLeafConjuncts(func(x adt.Conjunct) bool {
   543  		// Use Elem instead of Expr to preserve the Comprehension to, in turn,
   544  		// ensure an Environment is inserted for the Value clause.
   545  		c.markExpr(x.Env, x.Elem())
   546  		return true
   547  	})
   548  }
   549  
   550  // markInternalResolvers marks dependencies for rootless nodes. As these
   551  // nodes may not be visited during normal traversal, we need to be more
   552  // proactive. For selectors and indices this means we need to evaluate their
   553  // objects to see exactly what the selector or index refers to.
   554  func (c *visitor) markInternalResolvers(env *adt.Environment, r adt.Resolver, v *adt.Vertex) {
   555  	saved := c.all // recursive traversal already done by this function.
   556  
   557  	// As lets have no path and we otherwise will not process them, we set
   558  	// processing all to true.
   559  	if c.marked != nil && hasLetParent(v) {
   560  		v.VisitLeafConjuncts(func(x adt.Conjunct) bool {
   561  			c.marked.markExpr(x.Expr())
   562  			return true
   563  		})
   564  	}
   565  
   566  	c.markConjuncts(v)
   567  
   568  	// evaluateInner will already process all values recursively, so disable
   569  	// while processing in this case.
   570  	c.all = false
   571  
   572  	switch r := r.(type) {
   573  	case *adt.SelectorExpr:
   574  		c.evaluateInner(env, r.X, r)
   575  	case *adt.IndexExpr:
   576  		c.evaluateInner(env, r.X, r)
   577  	}
   578  
   579  	c.all = saved
   580  }
   581  
   582  // evaluateInner evaluates the LHS of the given selector or index expression,
   583  // and marks all its conjuncts. The reference is pushed on a stack to mark
   584  // the field or index that needs to be selected for any dependencies that are
   585  // subsequently encountered. This is handled by reportDependency.
   586  func (c *visitor) evaluateInner(env *adt.Environment, x adt.Expr, r adt.Resolver) {
   587  	value, _ := c.ctxt.Evaluate(env, x)
   588  	v, _ := value.(*adt.Vertex)
   589  	if v == nil {
   590  		return
   591  	}
   592  	// TODO(perf): one level of  evaluation would suffice.
   593  	v.Finalize(c.ctxt)
   594  
   595  	saved := len(c.pathStack)
   596  	c.pathStack = append(c.pathStack, refEntry{env, r})
   597  	c.markConjuncts(v)
   598  	c.pathStack = c.pathStack[:saved]
   599  }
   600  
   601  func (c *visitor) feature(env *adt.Environment, r adt.Resolver) adt.Feature {
   602  	switch r := r.(type) {
   603  	case *adt.SelectorExpr:
   604  		return r.Sel
   605  	case *adt.IndexExpr:
   606  		v, _ := c.ctxt.Evaluate(env, r.Index)
   607  		v = adt.Unwrap(v)
   608  		return adt.LabelFromValue(c.ctxt, r.Index, v)
   609  	default:
   610  		return adt.InvalidLabel
   611  	}
   612  }
   613  
   614  func (c *visitor) markSubExpr(env *adt.Environment, x adt.Expr) {
   615  	if c.all {
   616  		saved := c.top
   617  		c.top = false
   618  		c.markExpr(env, x)
   619  		c.top = saved
   620  	}
   621  }
   622  
   623  func (c *visitor) markDecl(env *adt.Environment, d adt.Decl) {
   624  	switch x := d.(type) {
   625  	case *adt.Field:
   626  		c.markSubExpr(env, x.Value)
   627  
   628  	case *adt.BulkOptionalField:
   629  		c.markExpr(env, x.Filter)
   630  		// when dynamic, only continue if there is evidence of
   631  		// the field in the parallel actual evaluation.
   632  		c.markSubExpr(env, x.Value)
   633  
   634  	case *adt.DynamicField:
   635  		c.markExpr(env, x.Key)
   636  		// when dynamic, only continue if there is evidence of
   637  		// a matching field in the parallel actual evaluation.
   638  		c.markSubExpr(env, x.Value)
   639  
   640  	case *adt.Comprehension:
   641  		c.markComprehension(env, x)
   642  
   643  	case adt.Expr:
   644  		c.markExpr(env, x)
   645  
   646  	case *adt.Ellipsis:
   647  		if x.Value != nil {
   648  			c.markSubExpr(env, x.Value)
   649  		}
   650  	}
   651  }
   652  
   653  func (c *visitor) markComprehension(env *adt.Environment, y *adt.Comprehension) {
   654  	env = c.markClauses(env, y.Clauses)
   655  
   656  	// Use "live" environments if we have them. This is important if
   657  	// dependencies are computed on a partially evaluated value where a pushed
   658  	// down comprehension is defined outside the root of the dependency
   659  	// analysis. For instance, when analyzing dependencies at path a.b in:
   660  	//
   661  	//  a: {
   662  	//      for value in { test: 1 } {
   663  	//          b: bar: value
   664  	//      }
   665  	//  }
   666  	//
   667  	if envs := y.Envs(); len(envs) > 0 {
   668  		// We use the Environment to get access to the parent chain. It
   669  		// suffices to take any Environment (in this case the first), as all
   670  		// will have the same parent chain.
   671  		env = envs[0]
   672  	}
   673  	for i := y.Nest(); i > 0; i-- {
   674  		env = &adt.Environment{Up: env, Vertex: empty}
   675  	}
   676  	// TODO: consider using adt.EnvExpr and remove the above loop.
   677  	c.markExpr(env, adt.ToExpr(y.Value))
   678  }
   679  
   680  func (c *visitor) markClauses(env *adt.Environment, a []adt.Yielder) *adt.Environment {
   681  	for _, y := range a {
   682  		switch x := y.(type) {
   683  		case *adt.ForClause:
   684  			c.markExpr(env, x.Src)
   685  			env = &adt.Environment{Up: env, Vertex: empty}
   686  			// In dynamic mode, iterate over all actual value and
   687  			// evaluate.
   688  
   689  		case *adt.LetClause:
   690  			c.markExpr(env, x.Expr)
   691  			env = &adt.Environment{Up: env, Vertex: empty}
   692  
   693  		case *adt.IfClause:
   694  			c.markExpr(env, x.Condition)
   695  			// In dynamic mode, only continue if condition is true.
   696  
   697  		case *adt.ValueClause:
   698  			env = &adt.Environment{Up: env, Vertex: empty}
   699  		}
   700  	}
   701  	return env
   702  }