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 }