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

     1  // Copyright 2021 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 adt
    16  
    17  // Comprehension algorithm
    18  //
    19  // Comprehensions are expanded for, if, and let clauses that yield 0 or more
    20  // structs to be embedded in the enclosing list or struct.
    21  //
    22  // CUE allows cascading of insertions, as in:
    23  //
    24  //     a?: int
    25  //     b?: int
    26  //     if a != _|_ {
    27  //         b: 2
    28  //     }
    29  //     if b != _|_ {
    30  //         c: 3
    31  //         d: 4
    32  //     }
    33  //
    34  // even though CUE does not allow the result of a comprehension to depend
    35  // on another comprehension within a single struct. The way this works is that
    36  // for fields with a fixed prefix path in a comprehension value, the
    37  // comprehension is assigned to these respective fields.
    38  //
    39  // More concretely, the above example is rewritten to:
    40  //
    41  //    a?: int
    42  //    b: if a != _|_ { 2 }
    43  //    c: if b != _|_ { 3 }
    44  //    d: if b != _|_ { 4 }
    45  //
    46  // where the fields with if clause are only inserted if their condition
    47  // resolves to true. (Note that this is not valid CUE; it may be in the future.)
    48  //
    49  // With this rewrite, any dependencies in comprehension expressions will follow
    50  // the same rules, more or less, as with normal evaluation.
    51  //
    52  // Note that a single comprehension may be distributed across multiple fields.
    53  // The evaluator will ensure, however, that a comprehension is only evaluated
    54  // once.
    55  //
    56  //
    57  // Closedness
    58  //
    59  // The comprehension algorithm uses the usual closedness mechanism for marking
    60  // fields that belong to a struct: it adds the StructLit associated with the
    61  // comprehension value to the respective arc.
    62  //
    63  // One noteworthy point is that the fields of a struct are only legitimate for
    64  // actual results. For instance, if an if clause evaluates to false, the
    65  // value is not embedded.
    66  //
    67  // To account for this, the comprehension algorithm relies on the fact that
    68  // the closedness information is computed as a separate step. So even if
    69  // the StructLit is added early, its fields will only count once it is
    70  // initialized, which is only done when at least one result is added.
    71  //
    72  
    73  // envComprehension caches the result of a single comprehension.
    74  type envComprehension struct {
    75  	comp   *Comprehension
    76  	vertex *Vertex // The Vertex from which the comprehension originates.
    77  
    78  	// runtime-related fields
    79  
    80  	err *Bottom
    81  
    82  	// envs holds all the environments that define a single "yield" result in
    83  	// combination with the comprehension struct.
    84  	envs []*Environment // nil: unprocessed, non-nil: done.
    85  	done bool           // true once the comprehension has been evaluated
    86  
    87  	// StructLits to Init (activate for closedness check)
    88  	// when at least one value is yielded.
    89  	structs []*StructLit
    90  }
    91  
    92  // envYield defines a comprehension for a specific field within a comprehension
    93  // value. Multiple envYields can be associated with a single envComprehension.
    94  // An envComprehension only needs to be evaluated once for multiple envYields.
    95  type envYield struct {
    96  	*envComprehension                // The original comprehension.
    97  	leaf              *Comprehension // The leaf Comprehension
    98  
    99  	// Values specific to the field corresponding to this envYield
   100  
   101  	// This envYield was added to selfComprehensions
   102  	self bool
   103  	// This envYield was successfully executed and the resulting conjuncts were
   104  	// added.
   105  	inserted bool
   106  
   107  	env  *Environment // The adjusted Environment.
   108  	id   CloseInfo    // CloseInfo for the field.
   109  	expr Node         // The adjusted expression.
   110  }
   111  
   112  // ValueClause represents a wrapper Environment in a chained clause list
   113  // to account for the unwrapped struct. It is never created by the compiler
   114  // and serves as a dynamic element only.
   115  type ValueClause struct {
   116  	Node
   117  
   118  	// The node in which to resolve lookups in the comprehension's value struct.
   119  	arc *Vertex
   120  }
   121  
   122  func (v *ValueClause) yield(s *compState) {
   123  	s.yield(s.ctx.spawn(v.arc))
   124  }
   125  
   126  // insertComprehension registers a comprehension with a node, possibly pushing
   127  // down its evaluation to the node's children. It will only evaluate one level
   128  // of fields at a time.
   129  func (n *nodeContext) insertComprehension(
   130  	env *Environment,
   131  	c *Comprehension,
   132  	ci CloseInfo,
   133  ) {
   134  	// TODO(perf): this implementation causes the parent's clauses
   135  	// to be evaluated for each nested comprehension. It would be
   136  	// possible to simply store the envComprehension of the parent's
   137  	// result and have each subcomprehension reuse those. This would
   138  	// also avoid the below allocation and would probably allow us
   139  	// to get rid of the ValueClause type.
   140  
   141  	ec := c.comp
   142  	if ec == nil {
   143  		ec = &envComprehension{
   144  			comp:   c,
   145  			vertex: n.node,
   146  		}
   147  	}
   148  
   149  	x := c.Value
   150  
   151  	if !n.ctx.isDevVersion() {
   152  		ci = ci.SpawnEmbed(c)
   153  		ci.closeInfo.span |= ComprehensionSpan
   154  	}
   155  
   156  	node := n.node.DerefDisjunct()
   157  
   158  	var decls []Decl
   159  	switch v := ToExpr(x).(type) {
   160  	case *StructLit:
   161  		kind := TopKind
   162  		numFixed := 0
   163  		var fields []Decl
   164  		for _, d := range v.Decls {
   165  			switch f := d.(type) {
   166  			case *Field:
   167  				numFixed++
   168  
   169  				if f.Label.IsInt() {
   170  					kind &= ListKind
   171  				} else if f.Label.IsString() {
   172  					kind &= StructKind
   173  				}
   174  
   175  				// Create partial comprehension
   176  				c := &Comprehension{
   177  					Syntax:  c.Syntax,
   178  					Clauses: c.Clauses,
   179  					Value:   f,
   180  					arcType: f.ArcType, // TODO: can be derived, remove this field.
   181  
   182  					comp:   ec,
   183  					parent: c,
   184  					arc:    node,
   185  				}
   186  
   187  				conjunct := MakeConjunct(env, c, ci)
   188  				if n.ctx.isDevVersion() {
   189  					n.assertInitialized()
   190  					n.insertArc(f.Label, ArcPending, conjunct, conjunct.CloseInfo, false)
   191  				} else {
   192  					n.insertFieldUnchecked(f.Label, ArcPending, conjunct)
   193  				}
   194  
   195  				fields = append(fields, f)
   196  
   197  			case *LetField:
   198  				// TODO: consider merging this case with the LetField case.
   199  
   200  				numFixed++
   201  
   202  				// Create partial comprehension
   203  				c := &Comprehension{
   204  					Syntax:  c.Syntax,
   205  					Clauses: c.Clauses,
   206  					Value:   f,
   207  
   208  					comp:   ec,
   209  					parent: c,
   210  					arc:    node,
   211  				}
   212  
   213  				conjunct := MakeConjunct(env, c, ci)
   214  				n.assertInitialized()
   215  				arc := n.insertFieldUnchecked(f.Label, ArcMember, conjunct)
   216  				if n.ctx.isDevVersion() {
   217  					arc.MultiLet = true
   218  				} else {
   219  					arc.MultiLet = f.IsMulti
   220  				}
   221  
   222  				fields = append(fields, f)
   223  
   224  			default:
   225  				decls = append(decls, d)
   226  			}
   227  		}
   228  
   229  		if len(fields) > 0 {
   230  			// Create a stripped struct that only includes fixed fields.
   231  			// TODO(perf): this StructLit may be inserted more than once in
   232  			// the same vertex: once taking the StructLit of the referred node
   233  			// and once for inserting the Conjunct of the original node.
   234  			// Is this necessary (given closedness rules), and is this posing
   235  			// a performance problem?
   236  			st := v
   237  			if len(fields) < len(v.Decls) {
   238  				st = &StructLit{
   239  					Src:   v.Src,
   240  					Decls: fields,
   241  				}
   242  			}
   243  			node.AddStruct(st, env, ci)
   244  			switch {
   245  			case !ec.done:
   246  				ec.structs = append(ec.structs, st)
   247  			case len(ec.envs) > 0:
   248  				st.Init(n.ctx)
   249  				if kind == StructKind || kind == ListKind {
   250  					n.updateNodeType(kind, st, ci)
   251  				}
   252  			}
   253  		}
   254  
   255  		c.kind = kind
   256  
   257  		switch numFixed {
   258  		case 0:
   259  			// Add comprehension as is.
   260  
   261  		case len(v.Decls):
   262  			// No comprehension to add at this level.
   263  			// The should be considered a struct if it has only non-regular
   264  			// fields (like definitions), and no embeddings.
   265  			if kind == TopKind {
   266  				c.kind = StructKind
   267  			}
   268  			return
   269  
   270  		default:
   271  			// Create a new StructLit with only the fields that need to be
   272  			// added at this level.
   273  			x = &StructLit{Decls: decls}
   274  		}
   275  	}
   276  
   277  	if n.ctx.isDevVersion() {
   278  		t := n.scheduleTask(handleComprehension, env, x, ci)
   279  		t.comp = ec
   280  		t.leaf = c
   281  	} else {
   282  		n.comprehensions = append(n.comprehensions, envYield{
   283  			envComprehension: ec,
   284  			leaf:             c,
   285  			env:              env,
   286  			id:               ci,
   287  			expr:             x,
   288  		})
   289  	}
   290  }
   291  
   292  type compState struct {
   293  	ctx   *OpContext
   294  	comp  *Comprehension
   295  	i     int
   296  	f     YieldFunc
   297  	state vertexStatus
   298  }
   299  
   300  // yield evaluates a Comprehension within the given Environment and calls
   301  // f for each result.
   302  func (c *OpContext) yield(
   303  	node *Vertex, // errors are associated with this node
   304  	env *Environment, // env for field for which this yield is called
   305  	comp *Comprehension,
   306  	state combinedFlags,
   307  	f YieldFunc, // called for every result
   308  ) *Bottom {
   309  	s := &compState{
   310  		ctx:   c,
   311  		comp:  comp,
   312  		f:     f,
   313  		state: state.vertexStatus(),
   314  	}
   315  	y := comp.Clauses[0]
   316  
   317  	saved := c.PushState(env, y.Source())
   318  	if node != nil {
   319  		defer c.PopArc(c.PushArc(node))
   320  	}
   321  
   322  	s.i++
   323  	y.yield(s)
   324  	s.i--
   325  
   326  	return c.PopState(saved)
   327  }
   328  
   329  func (s *compState) yield(env *Environment) (ok bool) {
   330  	c := s.ctx
   331  	if s.i >= len(s.comp.Clauses) {
   332  		s.f(env)
   333  		return true
   334  	}
   335  	dst := s.comp.Clauses[s.i]
   336  	saved := c.PushState(env, dst.Source())
   337  
   338  	s.i++
   339  	dst.yield(s)
   340  	s.i--
   341  
   342  	if b := c.PopState(saved); b != nil {
   343  		c.AddBottom(b)
   344  		return false
   345  	}
   346  	return !c.HasErr()
   347  }
   348  
   349  // injectComprehension evaluates and inserts embeddings. It first evaluates all
   350  // embeddings before inserting the results to ensure that the order of
   351  // evaluation does not matter.
   352  func (n *nodeContext) injectComprehensions(state vertexStatus) (progress bool) {
   353  	unreachableForDev(n.ctx)
   354  
   355  	workRemaining := false
   356  
   357  	// We use variables, instead of range, as the list may grow dynamically.
   358  	for i := 0; i < len(n.comprehensions); i++ {
   359  		d := &n.comprehensions[i]
   360  		if d.self || d.inserted {
   361  			continue
   362  		}
   363  		if err := n.processComprehension(d, state); err != nil {
   364  			// TODO:  Detect that the nodes are actually equal
   365  			if err.ForCycle && err.Value == n.node {
   366  				n.selfComprehensions = append(n.selfComprehensions, *d)
   367  				progress = true
   368  				d.self = true
   369  				return
   370  			}
   371  
   372  			d.err = err
   373  			workRemaining = true
   374  
   375  			continue
   376  
   377  			// TODO: add this when it can be done without breaking other
   378  			// things.
   379  			//
   380  			// // Add comprehension to ensure incomplete error is inserted.
   381  			// // This ensures that the error is reported in the Vertex
   382  			// // where the comprehension was defined, and not just in the
   383  			// // node below. This, in turn, is necessary to support
   384  			// // certain logic, like export, that expects to be able to
   385  			// // detect an "incomplete" error at the first level where it
   386  			// // is necessary.
   387  			// n := d.node.getNodeContext(ctx)
   388  			// n.addBottom(err)
   389  
   390  		}
   391  		progress = true
   392  	}
   393  
   394  	if !workRemaining {
   395  		n.comprehensions = n.comprehensions[:0] // Signal that all work is done.
   396  	}
   397  
   398  	return progress
   399  }
   400  
   401  // injectSelfComprehensions processes comprehensions that were earlier marked
   402  // as iterating over the node in which they are defined. Such comprehensions
   403  // are legal as long as they do not modify the arc set of the node.
   404  func (n *nodeContext) injectSelfComprehensions(state vertexStatus) {
   405  	unreachableForDev(n.ctx)
   406  
   407  	// We use variables, instead of range, as the list may grow dynamically.
   408  	for i := 0; i < len(n.selfComprehensions); i++ {
   409  		n.processComprehension(&n.selfComprehensions[i], state)
   410  	}
   411  	n.selfComprehensions = n.selfComprehensions[:0] // Signal that all work is done.
   412  }
   413  
   414  // processComprehension processes a single Comprehension conjunct.
   415  // It returns an incomplete error if there was one. Fatal errors are
   416  // processed as a "successfully" completed computation.
   417  func (n *nodeContext) processComprehension(d *envYield, state vertexStatus) *Bottom {
   418  	ctx := n.ctx
   419  
   420  	// Compute environments, if needed.
   421  	if !d.done {
   422  		var envs []*Environment
   423  		f := func(env *Environment) {
   424  			envs = append(envs, env)
   425  		}
   426  
   427  		if err := ctx.yield(d.vertex, d.env, d.comp, oldOnly(state), f); err != nil {
   428  			if err.IsIncomplete() {
   429  				return err
   430  			}
   431  
   432  			// continue to collect other errors.
   433  			d.done = true
   434  			d.inserted = true
   435  			if d.vertex != nil {
   436  				d.vertex.state.addBottom(err)
   437  				ctx.PopArc(d.vertex)
   438  			}
   439  			return nil
   440  		}
   441  
   442  		d.envs = envs
   443  
   444  		if len(d.envs) > 0 {
   445  			for _, s := range d.structs {
   446  				s.Init(n.ctx)
   447  			}
   448  		}
   449  		d.structs = nil
   450  		d.done = true
   451  	}
   452  
   453  	d.inserted = true
   454  
   455  	if len(d.envs) == 0 {
   456  		n.node.updateArcType(ArcNotPresent)
   457  		return nil
   458  	}
   459  
   460  	v := n.node
   461  	for c := d.leaf; c.parent != nil; c = c.parent {
   462  		v = n.ctx.deref(v)
   463  		v.updateArcType(c.arcType)
   464  		if v.ArcType == ArcNotPresent {
   465  			parent := v.Parent
   466  			b := parent.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), v.Label)
   467  			d.envComprehension.vertex.state.addBottom(b)
   468  			ctx.current().err = b
   469  			ctx.current().state = taskFAILED
   470  			return nil
   471  		}
   472  		if k := c.kind; k == StructKind || k == ListKind {
   473  			v := v.DerefDisjunct()
   474  			if s := v.getBareState(n.ctx); s != nil {
   475  				s.updateNodeType(k, ToExpr(c.Value), d.id)
   476  			}
   477  		}
   478  		v = c.arc
   479  	}
   480  
   481  	id := d.id
   482  	// TODO: should we treat comprehension values as optional?
   483  	// It seems so, but it causes some hangs.
   484  	// id.setOptional(nil)
   485  
   486  	for _, env := range d.envs {
   487  		if n.node.ArcType == ArcNotPresent {
   488  			b := n.node.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), n.node.Label)
   489  			ctx.current().err = b
   490  			n.yield()
   491  			return nil
   492  		}
   493  
   494  		env = linkChildren(env, d.leaf)
   495  
   496  		if ctx.isDevVersion() {
   497  			n.scheduleConjunct(Conjunct{env, d.expr, id}, id)
   498  		} else {
   499  			n.addExprConjunct(Conjunct{env, d.expr, id}, state)
   500  		}
   501  	}
   502  
   503  	return nil
   504  }
   505  
   506  // linkChildren adds environments for the chain of vertices to a result
   507  // environment.
   508  func linkChildren(env *Environment, c *Comprehension) *Environment {
   509  	if c.parent != nil {
   510  		env = linkChildren(env, c.parent)
   511  		env = spawn(env, c.arc)
   512  	}
   513  	return env
   514  }