github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/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  	"errors"
    20  
    21  	"github.com/joomcode/cue/internal/core/adt"
    22  )
    23  
    24  // A Dependency is a reference and the node that reference resolves to.
    25  type Dependency struct {
    26  	// Node is the referenced node.
    27  	Node *adt.Vertex
    28  
    29  	// Reference is the expression that referenced the node.
    30  	Reference adt.Resolver
    31  
    32  	top bool
    33  }
    34  
    35  // Import returns the import reference or nil if the reference was within
    36  // the same package as the visited Vertex.
    37  func (d *Dependency) Import() *adt.ImportReference {
    38  	x, _ := d.Reference.(adt.Expr)
    39  	return importRef(x)
    40  }
    41  
    42  // IsRoot reports whether the dependency is referenced by the root of the
    43  // original Vertex passed to any of the Visit* functions, and not one of its
    44  // descendent arcs. This always returns true for Visit().
    45  func (d *Dependency) IsRoot() bool {
    46  	return d.top
    47  }
    48  
    49  func (d *Dependency) Path() []adt.Feature {
    50  	return nil
    51  }
    52  
    53  func importRef(r adt.Expr) *adt.ImportReference {
    54  	switch x := r.(type) {
    55  	case *adt.ImportReference:
    56  		return x
    57  	case *adt.SelectorExpr:
    58  		return importRef(x.X)
    59  	case *adt.IndexExpr:
    60  		return importRef(x.X)
    61  	}
    62  	return nil
    63  }
    64  
    65  // VisitFunc is used for reporting dependencies.
    66  type VisitFunc func(Dependency) error
    67  
    68  // Visit calls f for all vertices referenced by the conjuncts of n without
    69  // descending into the elements of list or fields of structs. Only references
    70  // that do not refer to the conjuncts of n itself are reported.
    71  func Visit(c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
    72  	return visit(c, n, f, false, true)
    73  }
    74  
    75  // VisitAll calls f for all vertices referenced by the conjuncts of n including
    76  // those of descendant fields and elements. Only references that do not refer to
    77  // the conjuncts of n itself are reported.
    78  func VisitAll(c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
    79  	return visit(c, n, f, true, true)
    80  }
    81  
    82  // VisitFields calls f for n and all its descendent arcs that have a conjunct
    83  // that originates from a conjunct in n. Only the conjuncts of n that ended up
    84  // as a conjunct in an actual field are visited and they are visited for each
    85  // field in which the occurs.
    86  func VisitFields(c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
    87  	m := marked{}
    88  
    89  	m.markExpr(n)
    90  
    91  	dynamic(c, n, f, m, true)
    92  	return nil
    93  }
    94  
    95  var empty *adt.Vertex
    96  
    97  func init() {
    98  	// TODO: Consider setting a non-nil BaseValue.
    99  	empty = &adt.Vertex{}
   100  	empty.UpdateStatus(adt.Finalized)
   101  }
   102  
   103  func visit(c *adt.OpContext, n *adt.Vertex, f VisitFunc, all, top bool) (err error) {
   104  	if c == nil {
   105  		panic("nil context")
   106  	}
   107  	v := visitor{
   108  		ctxt:  c,
   109  		visit: f,
   110  		node:  n,
   111  		all:   all,
   112  		top:   top,
   113  	}
   114  
   115  	defer func() {
   116  		switch x := recover(); x {
   117  		case nil:
   118  		case aborted:
   119  			err = v.err
   120  		default:
   121  			panic(x)
   122  		}
   123  	}()
   124  
   125  	for _, x := range n.Conjuncts {
   126  		v.markExpr(x.Env, x.Elem())
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  var aborted = errors.New("aborted")
   133  
   134  type visitor struct {
   135  	ctxt  *adt.OpContext
   136  	visit VisitFunc
   137  	node  *adt.Vertex
   138  	err   error
   139  	all   bool
   140  	top   bool
   141  }
   142  
   143  // TODO: factor out the below logic as either a low-level dependency analyzer or
   144  // some walk functionality.
   145  
   146  // markExpr visits all nodes in an expression to mark dependencies.
   147  func (c *visitor) markExpr(env *adt.Environment, expr adt.Elem) {
   148  	switch x := expr.(type) {
   149  	case nil:
   150  	case adt.Resolver:
   151  		c.markResolver(env, x)
   152  
   153  	case *adt.BinaryExpr:
   154  		c.markExpr(env, x.X)
   155  		c.markExpr(env, x.Y)
   156  
   157  	case *adt.UnaryExpr:
   158  		c.markExpr(env, x.X)
   159  
   160  	case *adt.Interpolation:
   161  		for i := 1; i < len(x.Parts); i += 2 {
   162  			c.markExpr(env, x.Parts[i])
   163  		}
   164  
   165  	case *adt.BoundExpr:
   166  		c.markExpr(env, x.Expr)
   167  
   168  	case *adt.CallExpr:
   169  		c.markExpr(env, x.Fun)
   170  		saved := c.all
   171  		c.all = true
   172  		for _, a := range x.Args {
   173  			c.markExpr(env, a)
   174  		}
   175  		c.all = saved
   176  
   177  	case *adt.DisjunctionExpr:
   178  		for _, d := range x.Values {
   179  			c.markExpr(env, d.Val)
   180  		}
   181  
   182  	case *adt.SliceExpr:
   183  		c.markExpr(env, x.X)
   184  		c.markExpr(env, x.Lo)
   185  		c.markExpr(env, x.Hi)
   186  		c.markExpr(env, x.Stride)
   187  
   188  	case *adt.ListLit:
   189  		env := &adt.Environment{Up: env, Vertex: empty}
   190  		for _, e := range x.Elems {
   191  			switch x := e.(type) {
   192  			case *adt.Comprehension:
   193  				c.markComprehension(env, x)
   194  
   195  			case adt.Expr:
   196  				c.markSubExpr(env, x)
   197  
   198  			case *adt.Ellipsis:
   199  				if x.Value != nil {
   200  					c.markSubExpr(env, x.Value)
   201  				}
   202  			}
   203  		}
   204  
   205  	case *adt.StructLit:
   206  		env := &adt.Environment{Up: env, Vertex: empty}
   207  		for _, e := range x.Decls {
   208  			c.markDecl(env, e)
   209  		}
   210  	}
   211  }
   212  
   213  // markResolve resolves dependencies.
   214  func (c *visitor) markResolver(env *adt.Environment, r adt.Resolver) {
   215  	switch x := r.(type) {
   216  	case nil:
   217  	case *adt.LetReference:
   218  		saved := c.ctxt.PushState(env, nil)
   219  		env := c.ctxt.Env(x.UpCount)
   220  		c.markExpr(env, x.X)
   221  		c.ctxt.PopState(saved)
   222  		return
   223  	}
   224  
   225  	if ref, _ := c.ctxt.Resolve(env, r); ref != nil {
   226  		if ref != c.node && ref != empty {
   227  			d := Dependency{
   228  				Node:      ref,
   229  				Reference: r,
   230  				top:       c.top,
   231  			}
   232  			if err := c.visit(d); err != nil {
   233  				c.err = err
   234  				panic(aborted)
   235  			}
   236  		}
   237  
   238  		return
   239  	}
   240  
   241  	// It is possible that a reference cannot be resolved because it is
   242  	// incomplete. In this case, we should check whether subexpressions of the
   243  	// reference can be resolved to mark those dependencies. For instance,
   244  	// prefix paths of selectors and the value or index of an index experssion
   245  	// may independently resolve to a valid dependency.
   246  
   247  	switch x := r.(type) {
   248  	case *adt.NodeLink:
   249  		panic("unreachable")
   250  
   251  	case *adt.IndexExpr:
   252  		c.markExpr(env, x.X)
   253  		c.markExpr(env, x.Index)
   254  
   255  	case *adt.SelectorExpr:
   256  		c.markExpr(env, x.X)
   257  	}
   258  }
   259  
   260  func (c *visitor) markSubExpr(env *adt.Environment, x adt.Expr) {
   261  	if c.all {
   262  		saved := c.top
   263  		c.top = false
   264  		c.markExpr(env, x)
   265  		c.top = saved
   266  	}
   267  }
   268  
   269  func (c *visitor) markDecl(env *adt.Environment, d adt.Decl) {
   270  	switch x := d.(type) {
   271  	case *adt.Field:
   272  		c.markSubExpr(env, x.Value)
   273  
   274  	case *adt.OptionalField:
   275  		// when dynamic, only continue if there is evidence of
   276  		// the field in the parallel actual evaluation.
   277  		c.markSubExpr(env, x.Value)
   278  
   279  	case *adt.BulkOptionalField:
   280  		c.markExpr(env, x.Filter)
   281  		// when dynamic, only continue if there is evidence of
   282  		// the field in the parallel actual evaluation.
   283  		c.markSubExpr(env, x.Value)
   284  
   285  	case *adt.DynamicField:
   286  		c.markExpr(env, x.Key)
   287  		// when dynamic, only continue if there is evidence of
   288  		// a matching field in the parallel actual evaluation.
   289  		c.markSubExpr(env, x.Value)
   290  
   291  	case *adt.Comprehension:
   292  		c.markComprehension(env, x)
   293  
   294  	case adt.Expr:
   295  		c.markExpr(env, x)
   296  
   297  	case *adt.Ellipsis:
   298  		if x.Value != nil {
   299  			c.markSubExpr(env, x.Value)
   300  		}
   301  	}
   302  }
   303  
   304  func (c *visitor) markComprehension(env *adt.Environment, y *adt.Comprehension) {
   305  	env = c.markYielder(env, y.Clauses)
   306  	c.markExpr(env, y.Value)
   307  }
   308  
   309  func (c *visitor) markYielder(env *adt.Environment, y adt.Yielder) *adt.Environment {
   310  	switch x := y.(type) {
   311  	case *adt.ForClause:
   312  		c.markExpr(env, x.Src)
   313  		env = &adt.Environment{Up: env, Vertex: empty}
   314  		env = c.markYielder(env, x.Dst)
   315  		// In dynamic mode, iterate over all actual value and
   316  		// evaluate.
   317  
   318  	case *adt.LetClause:
   319  		c.markExpr(env, x.Expr)
   320  		env = &adt.Environment{Up: env, Vertex: empty}
   321  		env = c.markYielder(env, x.Dst)
   322  
   323  	case *adt.IfClause:
   324  		c.markExpr(env, x.Condition)
   325  		// In dynamic mode, only continue if condition is true.
   326  		env = c.markYielder(env, x.Dst)
   327  	}
   328  	return env
   329  }