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

     1  // Copyright 2024 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  import "slices"
    18  
    19  // # Overview
    20  //
    21  // This files contains the disjunction algorithm of the CUE evaluator. It works
    22  // in unison with the code in overlay.go.
    23  //
    24  // In principle, evaluating disjunctions is a matter of unifying each disjunct
    25  // with the non-disjunct values, eliminate those that fail and see what is left.
    26  // In case of multiple disjunctions it is a simple cross product of disjuncts.
    27  // The key is how to do this efficiently.
    28  //
    29  // # Classification of disjunction performance
    30  //
    31  // The key to an efficient disjunction algorithm is to minimize the impact of
    32  // taking cross product of disjunctions. This is especially pertinent if
    33  // disjunction expressions can be unified with themselves, as can be the case in
    34  // recursive definitions, as this can lead to exponential time complexity.
    35  //
    36  // We identify the following categories of importance for performance
    37  // optimization:
    38  //
    39  //  - Eliminate duplicates
    40  //      - For completed disjunctions
    41  //      - For partially computed disjuncts
    42  //  - Fail early / minimize work before failure
    43  //      - Filter disjuncts before unification (TODO)
    44  //          - Based on discriminator field
    45  //          - Based on a non-destructive unification of the disjunct and
    46  //            the current value computed so far
    47  //      - During the regular destructive unification
    48  //          - Traverse arcs where failure may occur
    49  //          - Copy on write (TODO)
    50  //
    51  // We discuss these aspects in more detail below.
    52  //
    53  // # Eliminating completed duplicates
    54  //
    55  // Eliminating completed duplicates can be achieved by comparing them for
    56  // equality. A disjunct can only be considered completed if all disjuncts have
    57  // been selected and evaluated, or at any time if processing fails.
    58  //
    59  // The following values should be recursively considered for equality:
    60  //
    61  //  - the value of the node,
    62  //  - the value of its arcs,
    63  //  - the key and value of the pattern constraints, and
    64  //  - the expression of the allowed fields.
    65  //
    66  // In some of these cases it may not be possible to detect if two nodes are
    67  // equal. For instance, two pattern constraints with two different regular
    68  // expressions as patterns, but that define an identical language, should be
    69  // considered equal. In practice, however, this is hard to distinguish.
    70  //
    71  // In the end this is mostly a matter of performance. As we noted, the biggest
    72  // concern is to avoid a combinatorial explosion when disjunctions are unified
    73  // with itself. The hope is that we can at least catch these cases, either
    74  // because they will evaluate to the same values, or because we can identify
    75  // that the underlying expressions are the same, or both.
    76  //
    77  // # Eliminating partially-computed duplicates
    78  //
    79  // We start with some observations and issues regarding partially evaluated
    80  // nodes.
    81  //
    82  // ## Issue: Closedness
    83  //
    84  // Two identical CUE values with identical field, values, and pattern
    85  // constraints, may still need to be consider as different, as they may exhibit
    86  // different closedness behavior. Consider, for instance, this example:
    87  //
    88  //  #def: {
    89  //      {} | {c: string} // D1
    90  //      {} | {a: string} // D2
    91  //  }
    92  //  x: #def
    93  //  x: c: "foo"
    94  //
    95  // Now, consider the case of the cross product that unifies the two empty
    96  // structs for `x`. Note that `x` already has a field `c`. After unifying the
    97  // first disjunction with `x`, both intermediate disjuncts will have the value
    98  // `{c: "foo"}`:
    99  //
   100  //         {c: "foo"} & ({} | {c: string})
   101  //       =>
   102  //         {c: "foo"} | {c: "foo"}
   103  //
   104  // One would think that one of these disjuncts can be eliminated. Nonetheless,
   105  // there is a difference. The second disjunct, which resulted from unifying
   106  //  `{c: "foo"}` with `{c: string}`, will remain valid. The first disjunct,
   107  // however, will fail after it is unified and completed with the `{}` of the
   108  // second disjunctions (D2): only at this point is it known that x was unified
   109  // with an empty closed struct, and that field `c` needs to be rejected.
   110  //
   111  // One possible solution would be to fully compute the cross product of `#def`
   112  // and use this expanded disjunction for unification, as this would mean that
   113  // full knowledge of closedness information is available.
   114  //
   115  // Although this is possible in some cases and can be a useful performance
   116  // optimization, it is not always possible to use the fully evaluated disjuncts
   117  // in such a precomputed cross product. For instance, if a disjunction relies on
   118  // a comprehension or a default value, it is not possible to fully evaluate the
   119  // disjunction, as merging it with another value may change the inputs for such
   120  // expressions later on. This means that we can only rely on partial evaluation
   121  // in some cases.
   122  //
   123  // ## Issue: Outstanding tasks in partial results
   124  //
   125  // Some tasks may not be completed until all conjuncts are known. For cross
   126  // products of disjunctions this may mean that such tasks cannot be completed
   127  // until all cross products are done. For instance, it is typically not possible
   128  // to evaluate a tasks that relies on taking a default value that may change as
   129  // more disjuncts are added. A similar argument holds for comprehensions on
   130  // values that may still be changed as more disjunctions come in.
   131  //
   132  // ## Evaluating equality of partially evaluated nodes
   133  //
   134  // Because unevaluated expressions may depend on results that have yet to be
   135  // computed, we cannot reliably compare the results of a Vertex to determine
   136  // equality. We need a different strategy.
   137  //
   138  // The strategy we take is based on the observation that at the start of a cross
   139  // product, the base conjunct is the same for all disjuncts. We can factor these
   140  // inputs out and focus on the differences between the disjuncts. In other
   141  // words, we can focus solely on the differences that manifest at the insertion
   142  // points (or "disjunction holes") of the disjuncts.
   143  //
   144  // In short, two disjuncts are equal if:
   145  //
   146  //  1. the disjunction holes that were already processed are equal, and
   147  //  2. they have either no outstanding tasks, or the outstanding tasks are equal
   148  //
   149  // Coincidentally, analyzing the differences as discussed in this section is
   150  // very similar in nature to precomputing a disjunct and using that. The main
   151  // difference is that we potentially have more information to prematurely
   152  // evaluate expressions and thus to prematurely filter values. For instance, the
   153  // mixed in value may have fixed a value that previously was not fixed. This
   154  // means that any expression referencing this value may be evaluated early and
   155  // can cause a disjunct to fail and be eliminated earlier.
   156  //
   157  // A disadvantage of this approach, however, is that it is not fully precise: it
   158  // may not filter some disjuncts that are logically identical. There are
   159  // strategies to further optimize this. For instance, if all remaining holes do
   160  // not contribute to closedness, which can be determined by walking up the
   161  // closedness parent chain, we may be able to safely filter disjuncts with equal
   162  // results.
   163  //
   164  // # Invariants
   165  //
   166  // We use the following assumptions in the below implementation:
   167  //
   168  //  - No more conjuncts are added to a disjunct after its processing begins.
   169  //    If a disjunction results in a value that causes more fields to be added
   170  //    later, this may not influence the result of the disjunction, i.e., those
   171  //    changes must be idempotent.
   172  //  - TODO: consider if any other assumptions are made.
   173  //
   174  // # Algorithm
   175  //
   176  // The evaluator accumulates all disjuncts of a Vertex in the nodeContext along
   177  // with the closeContext at which each was defined. A single task is scheduled
   178  // to process them all at once upon the first encounter of a disjunction.
   179  //
   180  // The algorithm is as follows:
   181  //  - Initialize the current Vertex n with the result evaluated so far as a
   182  //    list of "previous disjuncts".
   183  //  - Iterate over each disjunction
   184  //    - For each previous disjunct x
   185  //      - For each disjunct y in the current disjunctions
   186  //        - Unify
   187  //        - Discard if error, store in the list of current disjunctions if
   188  //          it differs from all other disjunctions in this list.
   189  //  - Set n to the result of the disjunction.
   190  //
   191  // This algorithm is recursive: if a disjunction is encountered in a disjunct,
   192  // it is processed as part of the evaluation of that disjunct.
   193  //
   194  
   195  // A disjunct is the expanded form of the disjuncts of either an Disjunction or
   196  // a DisjunctionExpr.
   197  //
   198  // TODO(perf): encode ADT structures in the correct form so that we do not have to
   199  // compute these each time.
   200  type disjunct struct {
   201  	expr Expr
   202  	err  *Bottom
   203  
   204  	isDefault bool
   205  	mode      defaultMode
   206  }
   207  
   208  func (n *nodeContext) scheduleDisjunction(d envDisjunct) {
   209  	if len(n.disjunctions) == 0 {
   210  		// This processes all disjunctions in a single pass.
   211  		n.scheduleTask(handleDisjunctions, nil, nil, CloseInfo{})
   212  	}
   213  
   214  	n.disjunctions = append(n.disjunctions, d)
   215  	n.hasDisjunction = true
   216  }
   217  
   218  func initArcs(ctx *OpContext, v *Vertex) bool {
   219  	ok := true
   220  	for _, a := range v.Arcs {
   221  		s := a.getState(ctx)
   222  		if s != nil && s.errs != nil {
   223  			ok = false
   224  			if a.ArcType == ArcMember {
   225  				break
   226  			}
   227  		} else if !initArcs(ctx, a) {
   228  			ok = false
   229  		}
   230  	}
   231  	return ok
   232  }
   233  
   234  func (n *nodeContext) processDisjunctions() *Bottom {
   235  	ID := n.pushDisjunctionTask()
   236  	defer ID.pop()
   237  
   238  	defer func() {
   239  		// TODO:
   240  		// Clear the buffers.
   241  		// TODO: we may want to retain history of which disjunctions were
   242  		// processed. In that case we can set a disjunction position to end
   243  		// of the list and schedule new tasks if this position equals the
   244  		// disjunction list length.
   245  	}()
   246  
   247  	// TODO(perf): check scalar errors so far to avoid unnecessary work.
   248  
   249  	// TODO: during processing disjunctions, new disjunctions may be added.
   250  	// We copy the slice to prevent the original slice from being overwritten.
   251  	// TODO(perf): use some pre-existing buffer or use a persising position
   252  	// so that disjunctions can be processed incrementally.
   253  	a := slices.Clone(n.disjunctions)
   254  	n.disjunctions = n.disjunctions[:0]
   255  
   256  	if !initArcs(n.ctx, n.node) {
   257  		return n.getError()
   258  	}
   259  
   260  	// If the disjunct of an enclosing disjunction operation has an attemptOnly
   261  	// runMode, this disjunct should have this also and may not finalize.
   262  	// Finalization may cause incoming dependencies to be broken. If an outer
   263  	// disjunction still has open holes, this means that more conjuncts may be
   264  	// incoming and that finalization would prematurely prevent those from being
   265  	// added. In practice, this may result in the infamous "already closed"
   266  	// panic.
   267  	var outerRunMode runMode
   268  	for p := n.node; p != nil; p = p.Parent {
   269  		if p.IsDisjunct {
   270  			outerRunMode = p.state.runMode
   271  			break
   272  		}
   273  	}
   274  
   275  	// TODO(perf): single pass for quick filter on all disjunctions.
   276  	// n.node.unify(n.ctx, allKnown, attemptOnly)
   277  
   278  	// Initially we compute the cross product of a disjunction with the
   279  	// nodeContext as it is processed so far.
   280  	cross := []*nodeContext{n}
   281  	results := []*nodeContext{} // TODO: use n.disjuncts as buffer.
   282  
   283  	// Slow path for processing all disjunctions. Do not use `range` in case
   284  	// evaluation adds more disjunctions.
   285  	for i := 0; i < len(a); i++ {
   286  		d := &a[i]
   287  		n.nextDisjunction(i, len(a), d.holeID)
   288  
   289  		// We need to only finalize the last series of disjunctions. However,
   290  		// disjunctions can be nested.
   291  		mode := attemptOnly
   292  		switch {
   293  		case outerRunMode != 0:
   294  			mode = outerRunMode
   295  			if i < len(a)-1 {
   296  				mode = attemptOnly
   297  			}
   298  		case i == len(a)-1:
   299  			mode = finalize
   300  		}
   301  
   302  		// Mark no final in nodeContext and observe later.
   303  		results = n.crossProduct(results, cross, d, mode)
   304  
   305  		// TODO: do we unwind only at the end or also intermittently?
   306  		switch len(results) {
   307  		case 0:
   308  			// TODO: now we have disjunct counters, do we plug holes at all?
   309  
   310  			// Empty intermediate result. Further processing will not result in
   311  			// any new result, so we can terminate here.
   312  			// TODO(errors): investigate remaining disjunctions for errors.
   313  			return n.collectErrors(d)
   314  
   315  		case 1:
   316  			// TODO: consider injecting the disjuncts into the main nodeContext
   317  			// here. This would allow other values that this disjunctions
   318  			// depends on to be evaluated. However, we should investigate
   319  			// whether this might lead to a situation where the order of
   320  			// evaluating disjunctions matters. So to be safe, we do not allow
   321  			// this for now.
   322  		}
   323  
   324  		// switch up buffers.
   325  		cross, results = results, cross[:0]
   326  	}
   327  
   328  	switch len(cross) {
   329  	case 0:
   330  		panic("unreachable: empty disjunction already handled above")
   331  
   332  	case 1:
   333  		d := cross[0].node
   334  		n.setBaseValue(d)
   335  		if n.defaultMode == maybeDefault {
   336  			n.defaultMode = cross[0].defaultMode
   337  		}
   338  		if n.defaultAttemptInCycle != nil && n.defaultMode != isDefault {
   339  			c := n.ctx
   340  			path := c.PathToString(n.defaultAttemptInCycle.Path())
   341  
   342  			index := c.MarkPositions()
   343  			c.AddPosition(n.defaultAttemptInCycle)
   344  			err := c.Newf("ambiguous default elimination by referencing %v", path)
   345  			c.ReleasePositions(index)
   346  
   347  			b := &Bottom{Code: CycleError, Err: err}
   348  			n.setBaseValue(b)
   349  			return b
   350  		}
   351  
   352  	default:
   353  		// append, rather than assign, to allow reusing the memory of
   354  		// a pre-existing slice.
   355  		n.disjuncts = append(n.disjuncts, cross...)
   356  	}
   357  
   358  	var completed condition
   359  	numDefaults := 0
   360  	if len(n.disjuncts) == 1 {
   361  		completed = n.disjuncts[0].completed
   362  	}
   363  	for _, d := range n.disjuncts {
   364  		if d.defaultMode == isDefault {
   365  			numDefaults++
   366  			completed = d.completed
   367  		}
   368  	}
   369  	if numDefaults == 1 || len(n.disjuncts) == 1 {
   370  		n.signal(completed)
   371  	}
   372  
   373  	return nil
   374  }
   375  
   376  // crossProduct computes the cross product of the disjuncts of a disjunction
   377  // with an existing set of results.
   378  func (n *nodeContext) crossProduct(dst, cross []*nodeContext, dn *envDisjunct, mode runMode) []*nodeContext {
   379  	defer n.unmarkDepth(n.markDepth())
   380  	defer n.unmarkOptional(n.markOptional())
   381  
   382  	// TODO(perf): use a pre-allocated buffer in n.ctx. Note that the actual
   383  	// buffer may grow and has a max size of len(cross) * len(dn.disjuncts).
   384  	tmp := make([]*nodeContext, 0, len(cross))
   385  
   386  	leftHasDefault := false
   387  	rightHasDefault := false
   388  
   389  	for i, p := range cross {
   390  		ID := n.nextCrossProduct(i, len(cross), p)
   391  
   392  		// TODO: use a partial unify instead
   393  		// p.completeNodeConjuncts()
   394  		initArcs(n.ctx, p.node)
   395  
   396  		for j, d := range dn.disjuncts {
   397  			ID.node.nextDisjunct(j, len(dn.disjuncts), d.expr)
   398  
   399  			c := MakeConjunct(dn.env, d.expr, dn.cloneID)
   400  			r, err := p.doDisjunct(c, d.mode, mode, n.node)
   401  
   402  			if err != nil {
   403  				// TODO: store more error context
   404  				dn.disjuncts[j].err = err
   405  				continue
   406  			}
   407  
   408  			tmp = append(tmp, r)
   409  			if p.defaultMode == isDefault {
   410  				leftHasDefault = true
   411  			}
   412  			if d.mode == isDefault {
   413  				rightHasDefault = true
   414  			}
   415  		}
   416  	}
   417  
   418  	for _, r := range tmp {
   419  		// Unroll nested disjunctions.
   420  		switch len(r.disjuncts) {
   421  		case 0:
   422  			r.defaultMode = combineDefault2(r.defaultMode, r.origDefaultMode, leftHasDefault, rightHasDefault)
   423  			// r did not have a nested disjunction.
   424  			dst = appendDisjunct(n.ctx, dst, r)
   425  
   426  		case 1:
   427  			panic("unexpected number of disjuncts")
   428  
   429  		default:
   430  			for _, x := range r.disjuncts {
   431  				m := combineDefault(r.origDefaultMode, x.defaultMode)
   432  
   433  				// TODO(defaults): using rightHasDefault instead of true here is
   434  				// not according to the spec, but may result in better user
   435  				// ergononmics. See Issue #1304.
   436  				x.defaultMode = combineDefault2(r.defaultMode, m, leftHasDefault, true)
   437  				dst = appendDisjunct(n.ctx, dst, x)
   438  			}
   439  		}
   440  	}
   441  
   442  	return dst
   443  }
   444  
   445  func combineDefault2(a, b defaultMode, hasDefaultA, hasDefaultB bool) defaultMode {
   446  	if !hasDefaultA {
   447  		a = maybeDefault
   448  	}
   449  	if !hasDefaultB {
   450  		b = maybeDefault
   451  	}
   452  	return combineDefault(a, b)
   453  }
   454  
   455  // collectErrors collects errors from a failed disjunctions.
   456  func (n *nodeContext) collectErrors(dn *envDisjunct) (errs *Bottom) {
   457  	code := EvalError
   458  	for _, d := range dn.disjuncts {
   459  		if b := d.err; b != nil {
   460  			n.disjunctErrs = append(n.disjunctErrs, b)
   461  			if b.Code > code {
   462  				code = b.Code
   463  			}
   464  		}
   465  	}
   466  
   467  	b := &Bottom{
   468  		Code: code,
   469  		Err:  n.disjunctError(),
   470  		Node: n.node,
   471  	}
   472  	return b
   473  }
   474  
   475  // doDisjunct computes a single disjunct. n is the current disjunct that is
   476  // augmented, whereas orig is the original node where disjunction processing
   477  // started. orig is used to clean up Environments.
   478  func (n *nodeContext) doDisjunct(c Conjunct, m defaultMode, mode runMode, orig *Vertex) (*nodeContext, *Bottom) {
   479  	ID := n.logDoDisjunct()
   480  	_ = ID // Do not remove, used for debugging.
   481  
   482  	oc := newOverlayContext(n.ctx)
   483  
   484  	// Complete as much of the pending work of this node and its parent before
   485  	// copying. Note that once a copy is made, the disjunct is no longer able
   486  	// to receive conjuncts from the original.
   487  	n.completeNodeTasks(mode)
   488  
   489  	// TODO: we may need to process incoming notifications for all arcs in
   490  	// the copied disjunct, but only those notifications not coming from
   491  	// within the arc itself.
   492  
   493  	n.scheduler.blocking = n.scheduler.blocking[:0]
   494  
   495  	d := oc.cloneRoot(n)
   496  
   497  	n.ctx.pushOverlay(n.node, oc.vertexMap)
   498  	defer n.ctx.popOverlay()
   499  
   500  	d.runMode = mode
   501  	c.Env = oc.derefDisjunctsEnv(c.Env)
   502  
   503  	v := d.node
   504  
   505  	defer n.setBaseValue(n.swapBaseValue(v))
   506  
   507  	// Clear relevant scheduler states.
   508  	// TODO: do something more principled: just ensure that a node that has
   509  	// not all holes filled out yet is not finalized. This may require
   510  	// a special mode, or evaluating more aggressively if finalize is not given.
   511  	v.status = unprocessed
   512  
   513  	d.scheduleConjunct(c, c.CloseInfo)
   514  
   515  	oc.unlinkOverlay()
   516  
   517  	// TODO(perf): do not set to nil, but rather maintain an index to unwind
   518  	// to avoid allocting new arrays.
   519  	saved := n.ctx.blocking
   520  	n.ctx.blocking = nil
   521  	defer func() { n.ctx.blocking = saved }()
   522  
   523  	d.defaultMode = n.defaultMode
   524  	d.origDefaultMode = m
   525  
   526  	v.unify(n.ctx, allKnown, mode, true)
   527  
   528  	if err := d.getErrorAll(); err != nil && !isCyclePlaceholder(err) {
   529  		d.free()
   530  		return nil, err
   531  	}
   532  
   533  	d.node.DerefDisjunct().state.origDefaultMode = d.origDefaultMode
   534  	d = d.node.DerefDisjunct().state // TODO: maybe do not unroll at all.
   535  
   536  	return d, nil
   537  }
   538  
   539  func (n *nodeContext) finalizeDisjunctions() {
   540  	if len(n.disjuncts) == 0 {
   541  		return
   542  	}
   543  
   544  	// TODO: we clear the Conjuncts to be compatible with the old evaluator.
   545  	// This is especially relevant for the API. Ideally, though, we should
   546  	// update Conjuncts to reflect the actual conjunct that went into the
   547  	// disjuncts.
   548  	numErrs := 0
   549  	for _, x := range n.disjuncts {
   550  		x.node.Conjuncts = nil
   551  
   552  		if b := x.getErr(); b != nil {
   553  			n.disjunctErrs = append(n.disjunctErrs, b)
   554  			numErrs++
   555  			continue
   556  		}
   557  	}
   558  
   559  	if len(n.disjuncts) == numErrs {
   560  		n.makeError()
   561  		return
   562  	}
   563  
   564  	a := make([]Value, len(n.disjuncts))
   565  	p := 0
   566  	hasDefaults := false
   567  	for i, x := range n.disjuncts {
   568  		switch x.defaultMode {
   569  		case isDefault:
   570  			a[i] = a[p]
   571  			a[p] = x.node
   572  			p++
   573  			hasDefaults = true
   574  
   575  		case notDefault:
   576  			hasDefaults = true
   577  			fallthrough
   578  		case maybeDefault:
   579  			a[i] = x.node
   580  		}
   581  	}
   582  
   583  	d := &Disjunction{
   584  		Values:      a,
   585  		NumDefaults: p,
   586  		HasDefaults: hasDefaults,
   587  	}
   588  
   589  	v := n.node
   590  
   591  	if n.defaultAttemptInCycle == nil || d.NumDefaults == 1 {
   592  		n.setBaseValue(d)
   593  	} else {
   594  		c := n.ctx
   595  		path := c.PathToString(n.defaultAttemptInCycle.Path())
   596  
   597  		index := c.MarkPositions()
   598  		c.AddPosition(n.defaultAttemptInCycle)
   599  		err := c.Newf("cycle across unresolved disjunction referenced by %v", path)
   600  		c.ReleasePositions(index)
   601  
   602  		b := &Bottom{Code: CycleError, Err: err}
   603  		n.setBaseValue(b)
   604  	}
   605  
   606  	// The conjuncts will have too much information. Better have no
   607  	// information than incorrect information.
   608  	v.Arcs = nil
   609  	v.ChildErrors = nil
   610  }
   611  
   612  func (n *nodeContext) getErrorAll() *Bottom {
   613  	err := n.getError()
   614  	if err != nil {
   615  		return err
   616  	}
   617  	for _, a := range n.node.Arcs {
   618  		if a.ArcType > ArcRequired || a.Label.IsLet() {
   619  			return nil
   620  		}
   621  		n := a.getState(n.ctx)
   622  		if n != nil {
   623  			if err := n.getErrorAll(); err != nil {
   624  				return err
   625  			}
   626  		}
   627  	}
   628  	return nil
   629  }
   630  
   631  func (n *nodeContext) getError() *Bottom {
   632  	if b := n.node.Bottom(); b != nil && !isCyclePlaceholder(b) {
   633  		return b
   634  	}
   635  	if n.node.ChildErrors != nil {
   636  		return n.node.ChildErrors
   637  	}
   638  	if errs := n.errs; errs != nil {
   639  		return errs
   640  	}
   641  	if n.ctx.errs != nil {
   642  		return n.ctx.errs
   643  	}
   644  	return nil
   645  }
   646  
   647  // appendDisjunct appends a disjunct x to a, if it is not a duplicate.
   648  func appendDisjunct(ctx *OpContext, a []*nodeContext, x *nodeContext) []*nodeContext {
   649  	if x == nil {
   650  		return a
   651  	}
   652  
   653  	nv := x.node.DerefValue()
   654  	nx := nv.BaseValue
   655  	if nx == nil || isCyclePlaceholder(nx) {
   656  		nx = x.getValidators(finalized)
   657  	}
   658  
   659  	// check uniqueness
   660  	// TODO: if a node is not finalized, we could check that the parent
   661  	// (overlayed) closeContexts are identical.
   662  outer:
   663  	for _, xn := range a {
   664  		xv := xn.node.DerefValue()
   665  		if xv.status != finalized || nv.status != finalized {
   666  			// Partial node
   667  
   668  			if !equalPartialNode(xn.ctx, x.node, xn.node) {
   669  				continue outer
   670  			}
   671  			if len(xn.tasks) != xn.taskPos || len(x.tasks) != x.taskPos {
   672  				if len(xn.tasks) != len(x.tasks) {
   673  					continue
   674  				}
   675  			}
   676  			for i, t := range xn.tasks[xn.taskPos:] {
   677  				s := x.tasks[i]
   678  				if s.x != t.x {
   679  					continue outer
   680  				}
   681  			}
   682  			vx, okx := nx.(Value)
   683  			ny := xv.BaseValue
   684  			if ny == nil || isCyclePlaceholder(ny) {
   685  				ny = x.getValidators(finalized)
   686  			}
   687  			vy, oky := ny.(Value)
   688  			if okx && oky && !Equal(ctx, vx, vy, CheckStructural) {
   689  				continue outer
   690  
   691  			}
   692  		} else {
   693  			// Complete nodes.
   694  			if !Equal(ctx, xn.node.DerefValue(), x.node.DerefValue(), CheckStructural) {
   695  				continue outer
   696  			}
   697  		}
   698  
   699  		// free vertex
   700  		if x.defaultMode == isDefault {
   701  			xn.defaultMode = isDefault
   702  		}
   703  		// TODO: x.free()
   704  		mergeCloseInfo(xn, x)
   705  		return a
   706  	}
   707  
   708  	return append(a, x)
   709  }
   710  
   711  func equalPartialNode(ctx *OpContext, x, y *Vertex) bool {
   712  	nx := x.state
   713  	ny := y.state
   714  
   715  	if nx == nil && ny == nil {
   716  		// Both nodes were finalized. We can compare them directly.
   717  		return Equal(ctx, x, y, CheckStructural)
   718  	}
   719  
   720  	// TODO: process the nodes with allKnown, attemptOnly.
   721  
   722  	if nx == nil || ny == nil {
   723  		return false
   724  	}
   725  
   726  	if !isEqualNodeValue(nx, ny) {
   727  		return false
   728  	}
   729  
   730  	switch cx, cy := x.PatternConstraints, y.PatternConstraints; {
   731  	case cx == nil && cy == nil:
   732  	case cx == nil || cy == nil:
   733  		return false
   734  	case len(cx.Pairs) != len(cy.Pairs):
   735  		return false
   736  	default:
   737  		// Assume patterns are in the same order.
   738  		for i, p := range cx.Pairs {
   739  			if !Equal(ctx, p.Constraint, cy.Pairs[i].Constraint, 0) {
   740  				return false
   741  			}
   742  		}
   743  	}
   744  
   745  	if len(x.Arcs) != len(y.Arcs) {
   746  		return false
   747  	}
   748  
   749  	// TODO(perf): use merge sort
   750  outer:
   751  	for _, a := range x.Arcs {
   752  		for _, b := range y.Arcs {
   753  			if a.Label != b.Label {
   754  				continue
   755  			}
   756  			if !equalPartialNode(ctx, a, b) {
   757  				return false
   758  			}
   759  			continue outer
   760  		}
   761  		return false
   762  	}
   763  	return true
   764  }
   765  
   766  // isEqualNodeValue reports whether the two nodes are of the same type and have
   767  // the same value.
   768  //
   769  // TODO: this could be done much more cleanly if we are more deligent in early
   770  // evaluation.
   771  func isEqualNodeValue(x, y *nodeContext) bool {
   772  	xk := x.kind
   773  	yk := y.kind
   774  
   775  	// If a node is mid evaluation, the kind might not be actual if the type is
   776  	// a struct, as whether a struct is a struct kind or an embedded type is
   777  	// determined later. This is just a limitation of the current
   778  	// implementation, we should update the kind more directly so that this code
   779  	// is not necessary.
   780  	// TODO: verify that this is still necessary and if so fix it so that this
   781  	// can be removed.
   782  	if x.aStruct != nil {
   783  		xk &= StructKind
   784  	}
   785  	if y.aStruct != nil {
   786  		yk &= StructKind
   787  	}
   788  
   789  	if xk != yk {
   790  		return false
   791  	}
   792  	if x.hasTop != y.hasTop {
   793  		return false
   794  	}
   795  	if !isEqualValue(x.ctx, x.scalar, y.scalar) {
   796  		return false
   797  	}
   798  
   799  	// Do some quick checks first.
   800  	if len(x.checks) != len(y.checks) {
   801  		return false
   802  	}
   803  	if len(x.tasks) != x.taskPos || len(y.tasks) != y.taskPos {
   804  		if len(x.tasks) != len(y.tasks) {
   805  			return false
   806  		}
   807  	}
   808  
   809  	if !isEqualValue(x.ctx, x.lowerBound, y.lowerBound) {
   810  		return false
   811  	}
   812  	if !isEqualValue(x.ctx, x.upperBound, y.upperBound) {
   813  		return false
   814  	}
   815  
   816  	// Assume that checks are added in the same order.
   817  	for i, c := range x.checks {
   818  		d := y.checks[i]
   819  		if !Equal(x.ctx, c.x.(Value), d.x.(Value), CheckStructural) {
   820  			return false
   821  		}
   822  	}
   823  
   824  	for i, t := range x.tasks[x.taskPos:] {
   825  		s := y.tasks[i]
   826  		if s.x != t.x {
   827  			return false
   828  		}
   829  	}
   830  
   831  	return true
   832  }
   833  
   834  type ComparableValue interface {
   835  	comparable
   836  	Value
   837  }
   838  
   839  func isEqualValue[P ComparableValue](ctx *OpContext, x, y P) bool {
   840  	var zero P
   841  
   842  	if x == y {
   843  		return true
   844  	}
   845  	if x == zero || y == zero {
   846  		return false
   847  	}
   848  
   849  	return Equal(ctx, x, y, CheckStructural)
   850  }
   851  
   852  // IsFromDisjunction reports whether any conjunct of v was a disjunction.
   853  // There are three cases:
   854  //  1. v is a disjunction itself. This happens when the result is an
   855  //     unresolved disjunction.
   856  //  2. v is a disjunct. This happens when only a single disjunct remains. In this
   857  //     case there will be a forwarded node that is marked with IsDisjunct.
   858  //  3. the disjunction was erroneous and none of the disjuncts failed.
   859  //
   860  // TODO(evalv3): one case that is not covered by this is erroneous disjunctions.
   861  // This is not the worst, but fixing it may lead to better error messages.
   862  func (v *Vertex) IsFromDisjunction() bool {
   863  	_, ok := v.BaseValue.(*Disjunction)
   864  	return ok || v.isDisjunct()
   865  }
   866  
   867  // TODO: export this instead of IsDisjunct
   868  func (v *Vertex) isDisjunct() bool {
   869  	for {
   870  		if v.IsDisjunct {
   871  			return true
   872  		}
   873  		arc, ok := v.BaseValue.(*Vertex)
   874  		if !ok {
   875  			return false
   876  		}
   877  		v = arc
   878  	}
   879  }