cuelang.org/go@v0.10.1/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  			err:  nil,   // shut up linter
   148  			envs: nil,   // shut up linter
   149  			done: false, // shut up linter
   150  		}
   151  	}
   152  
   153  	if ec.done && len(ec.envs) == 0 {
   154  		n.decComprehension(c)
   155  		return
   156  	}
   157  
   158  	x := c.Value
   159  
   160  	if !n.ctx.isDevVersion() {
   161  		ci = ci.SpawnEmbed(c)
   162  		ci.closeInfo.span |= ComprehensionSpan
   163  	}
   164  
   165  	var decls []Decl
   166  	switch v := ToExpr(x).(type) {
   167  	case *StructLit:
   168  		numFixed := 0
   169  		var fields []Decl
   170  		for _, d := range v.Decls {
   171  			switch f := d.(type) {
   172  			case *Field:
   173  				numFixed++
   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  					cc:      ci.cc,
   182  
   183  					comp:   ec,
   184  					parent: c,
   185  					arc:    n.node,
   186  				}
   187  
   188  				conjunct := MakeConjunct(env, c, ci)
   189  				if n.ctx.isDevVersion() {
   190  					n.assertInitialized()
   191  					_, c.arcCC = n.insertArcCC(f.Label, ArcPending, conjunct, conjunct.CloseInfo, false)
   192  					c.cc = ci.cc
   193  					ci.cc.incDependent(n.ctx, COMP, c.arcCC)
   194  				} else {
   195  					n.insertFieldUnchecked(f.Label, ArcPending, conjunct)
   196  				}
   197  
   198  				fields = append(fields, f)
   199  
   200  			case *LetField:
   201  				// TODO: consider merging this case with the LetField case.
   202  
   203  				numFixed++
   204  
   205  				// Create partial comprehension
   206  				c := &Comprehension{
   207  					Syntax:  c.Syntax,
   208  					Clauses: c.Clauses,
   209  					Value:   f,
   210  
   211  					comp:   ec,
   212  					parent: c,
   213  					arc:    n.node,
   214  				}
   215  
   216  				conjunct := MakeConjunct(env, c, ci)
   217  				n.assertInitialized()
   218  				arc := n.insertFieldUnchecked(f.Label, ArcMember, conjunct)
   219  				arc.MultiLet = f.IsMulti
   220  
   221  				fields = append(fields, f)
   222  
   223  			default:
   224  				decls = append(decls, d)
   225  			}
   226  		}
   227  
   228  		if len(fields) > 0 {
   229  			// Create a stripped struct that only includes fixed fields.
   230  			// TODO(perf): this StructLit may be inserted more than once in
   231  			// the same vertex: once taking the StructLit of the referred node
   232  			// and once for inserting the Conjunct of the original node.
   233  			// Is this necessary (given closedness rules), and is this posing
   234  			// a performance problem?
   235  			st := v
   236  			if len(fields) < len(v.Decls) {
   237  				st = &StructLit{
   238  					Src:   v.Src,
   239  					Decls: fields,
   240  				}
   241  			}
   242  			n.node.AddStruct(st, env, ci)
   243  			switch {
   244  			case !ec.done:
   245  				ec.structs = append(ec.structs, st)
   246  			case len(ec.envs) > 0:
   247  				st.Init(n.ctx)
   248  			}
   249  		}
   250  
   251  		switch numFixed {
   252  		case 0:
   253  			// Add comprehension as is.
   254  
   255  		case len(v.Decls):
   256  			// No comprehension to add at this level.
   257  			return
   258  
   259  		default:
   260  			// Create a new StructLit with only the fields that need to be
   261  			// added at this level.
   262  			x = &StructLit{Decls: decls}
   263  		}
   264  	}
   265  
   266  	if n.ctx.isDevVersion() {
   267  		t := n.scheduleTask(handleComprehension, env, x, ci)
   268  		t.comp = ec
   269  		t.leaf = c
   270  	} else {
   271  		n.comprehensions = append(n.comprehensions, envYield{
   272  			envComprehension: ec,
   273  			leaf:             c,
   274  			env:              env,
   275  			id:               ci,
   276  			expr:             x,
   277  		})
   278  	}
   279  }
   280  
   281  type compState struct {
   282  	ctx   *OpContext
   283  	comp  *Comprehension
   284  	i     int
   285  	f     YieldFunc
   286  	state vertexStatus
   287  }
   288  
   289  // yield evaluates a Comprehension within the given Environment and calls
   290  // f for each result.
   291  func (c *OpContext) yield(
   292  	node *Vertex, // errors are associated with this node
   293  	env *Environment, // env for field for which this yield is called
   294  	comp *Comprehension,
   295  	state combinedFlags,
   296  	f YieldFunc, // called for every result
   297  ) *Bottom {
   298  	s := &compState{
   299  		ctx:   c,
   300  		comp:  comp,
   301  		f:     f,
   302  		state: state.vertexStatus(),
   303  	}
   304  	y := comp.Clauses[0]
   305  
   306  	saved := c.PushState(env, y.Source())
   307  	if node != nil {
   308  		defer c.PopArc(c.PushArc(node))
   309  	}
   310  
   311  	s.i++
   312  	y.yield(s)
   313  	s.i--
   314  
   315  	return c.PopState(saved)
   316  }
   317  
   318  func (s *compState) yield(env *Environment) (ok bool) {
   319  	c := s.ctx
   320  	if s.i >= len(s.comp.Clauses) {
   321  		s.f(env)
   322  		return true
   323  	}
   324  	dst := s.comp.Clauses[s.i]
   325  	saved := c.PushState(env, dst.Source())
   326  
   327  	s.i++
   328  	dst.yield(s)
   329  	s.i--
   330  
   331  	if b := c.PopState(saved); b != nil {
   332  		c.AddBottom(b)
   333  		return false
   334  	}
   335  	return !c.HasErr()
   336  }
   337  
   338  // injectComprehension evaluates and inserts embeddings. It first evaluates all
   339  // embeddings before inserting the results to ensure that the order of
   340  // evaluation does not matter.
   341  func (n *nodeContext) injectComprehensions(state vertexStatus) (progress bool) {
   342  	unreachableForDev(n.ctx)
   343  
   344  	workRemaining := false
   345  
   346  	// We use variables, instead of range, as the list may grow dynamically.
   347  	for i := 0; i < len(n.comprehensions); i++ {
   348  		d := &n.comprehensions[i]
   349  		if d.self || d.inserted {
   350  			continue
   351  		}
   352  		if err := n.processComprehension(d, state); err != nil {
   353  			// TODO:  Detect that the nodes are actually equal
   354  			if err.ForCycle && err.Value == n.node {
   355  				n.selfComprehensions = append(n.selfComprehensions, *d)
   356  				progress = true
   357  				d.self = true
   358  				return
   359  			}
   360  
   361  			d.err = err
   362  			workRemaining = true
   363  
   364  			continue
   365  
   366  			// TODO: add this when it can be done without breaking other
   367  			// things.
   368  			//
   369  			// // Add comprehension to ensure incomplete error is inserted.
   370  			// // This ensures that the error is reported in the Vertex
   371  			// // where the comprehension was defined, and not just in the
   372  			// // node below. This, in turn, is necessary to support
   373  			// // certain logic, like export, that expects to be able to
   374  			// // detect an "incomplete" error at the first level where it
   375  			// // is necessary.
   376  			// n := d.node.getNodeContext(ctx)
   377  			// n.addBottom(err)
   378  
   379  		}
   380  		progress = true
   381  	}
   382  
   383  	if !workRemaining {
   384  		n.comprehensions = n.comprehensions[:0] // Signal that all work is done.
   385  	}
   386  
   387  	return progress
   388  }
   389  
   390  // injectSelfComprehensions processes comprehensions that were earlier marked
   391  // as iterating over the node in which they are defined. Such comprehensions
   392  // are legal as long as they do not modify the arc set of the node.
   393  func (n *nodeContext) injectSelfComprehensions(state vertexStatus) {
   394  	unreachableForDev(n.ctx)
   395  
   396  	// We use variables, instead of range, as the list may grow dynamically.
   397  	for i := 0; i < len(n.selfComprehensions); i++ {
   398  		n.processComprehension(&n.selfComprehensions[i], state)
   399  	}
   400  	n.selfComprehensions = n.selfComprehensions[:0] // Signal that all work is done.
   401  }
   402  
   403  // processComprehension processes a single Comprehension conjunct.
   404  // It returns an incomplete error if there was one. Fatal errors are
   405  // processed as a "successfully" completed computation.
   406  func (n *nodeContext) processComprehension(d *envYield, state vertexStatus) *Bottom {
   407  	err := n.processComprehensionInner(d, state)
   408  
   409  	// NOTE: we cannot move this to defer in processComprehensionInner, as we
   410  	// use panics to implement "yielding" (and possibly coroutines in the
   411  	// future).
   412  	n.decComprehension(d.leaf)
   413  
   414  	return err
   415  }
   416  
   417  func (n *nodeContext) decComprehension(p *Comprehension) {
   418  	for ; p != nil; p = p.parent {
   419  		cc := p.cc
   420  		if cc != nil {
   421  			cc.decDependent(n.ctx, COMP, p.arcCC)
   422  		}
   423  		p.cc = nil
   424  	}
   425  }
   426  
   427  func (n *nodeContext) processComprehensionInner(d *envYield, state vertexStatus) *Bottom {
   428  	ctx := n.ctx
   429  
   430  	// Compute environments, if needed.
   431  	if !d.done {
   432  		var envs []*Environment
   433  		f := func(env *Environment) {
   434  			envs = append(envs, env)
   435  		}
   436  
   437  		if err := ctx.yield(d.vertex, d.env, d.comp, oldOnly(state), f); err != nil {
   438  			if err.IsIncomplete() {
   439  				return err
   440  			}
   441  
   442  			// continue to collect other errors.
   443  			d.done = true
   444  			d.inserted = true
   445  			if d.vertex != nil {
   446  				d.vertex.state.addBottom(err)
   447  				ctx.PopArc(d.vertex)
   448  			}
   449  			return nil
   450  		}
   451  
   452  		d.envs = envs
   453  
   454  		if len(d.envs) > 0 {
   455  			for _, s := range d.structs {
   456  				s.Init(n.ctx)
   457  			}
   458  		}
   459  		d.structs = nil
   460  		d.done = true
   461  	}
   462  
   463  	d.inserted = true
   464  
   465  	if len(d.envs) == 0 {
   466  		c := d.leaf
   467  		for p := c.arcCC; p != nil; p = p.parent {
   468  			// because the parent referrer will reach a zero count before this
   469  			// node will reach a zero count, we need to propagate the arcType.
   470  			p.updateArcType(ArcNotPresent)
   471  		}
   472  		return nil
   473  	}
   474  
   475  	v := n.node
   476  	f := v.Label
   477  	for c := d.leaf; c.parent != nil; c = c.parent {
   478  		// because the parent referrer will reach a zero count before this
   479  		// node will reach a zero count, we need to propagate the arcType.
   480  		for arc, p := c.arcCC, c.cc; p != nil; arc, p = arc.parent, p.parent {
   481  			// TODO: remove this line once we use the arcType of the
   482  			// closeContext in notAllowedError.
   483  			arc.src.updateArcType(c.arcType)
   484  			t := arc.arcType
   485  			arc.updateArcType(c.arcType)
   486  			if p.isClosed && t >= ArcPending && !matchPattern(ctx, p.Expr, f) {
   487  				ctx.notAllowedError(p.src, arc.src)
   488  			}
   489  		}
   490  		v.updateArcType(c.arcType)
   491  		if v.ArcType == ArcNotPresent {
   492  			parent := v.Parent
   493  			b := parent.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), v.Label)
   494  			d.envComprehension.vertex.state.addBottom(b)
   495  			ctx.current().err = b
   496  			ctx.current().state = taskFAILED
   497  			return nil
   498  		}
   499  		v = c.arc
   500  	}
   501  
   502  	id := d.id
   503  
   504  	for _, env := range d.envs {
   505  		if n.node.ArcType == ArcNotPresent {
   506  			b := n.node.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), n.node.Label)
   507  			ctx.current().err = b
   508  			n.yield()
   509  			return nil
   510  		}
   511  
   512  		env = linkChildren(env, d.leaf)
   513  
   514  		if ctx.isDevVersion() {
   515  			n.scheduleConjunct(Conjunct{env, d.expr, id}, id)
   516  		} else {
   517  			n.addExprConjunct(Conjunct{env, d.expr, id}, state)
   518  		}
   519  	}
   520  
   521  	return nil
   522  }
   523  
   524  // linkChildren adds environments for the chain of vertices to a result
   525  // environment.
   526  func linkChildren(env *Environment, c *Comprehension) *Environment {
   527  	if c.parent != nil {
   528  		env = linkChildren(env, c.parent)
   529  		env = spawn(env, c.arc)
   530  	}
   531  	return env
   532  }