cuelang.org/go@v0.10.1/internal/core/adt/unify.go (about) 1 // Copyright 2023 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 ( 18 "fmt" 19 20 "cuelang.org/go/cue/token" 21 ) 22 23 // TODO(mpvl): perhaps conjunctsProcessed is a better name for this. 24 func (v *Vertex) isInitialized() bool { 25 return v.status == finalized || (v.state != nil && v.state.isInitialized) 26 } 27 28 func (n *nodeContext) assertInitialized() { 29 if n != nil && n.ctx.isDevVersion() { 30 if v := n.node; !v.isInitialized() { 31 panic(fmt.Sprintf("vertex %p not initialized", v)) 32 } 33 } 34 } 35 36 // isInProgress reports whether v is in the midst of being evaluated. This means 37 // that conjuncts have been scheduled, but that it has not been finalized. 38 func (v *Vertex) isInProgress() bool { 39 return v.status != finalized && v.state != nil && v.state.isInitialized 40 } 41 42 func (v *Vertex) getBareState(c *OpContext) *nodeContext { 43 if v.status == finalized { // TODO: use BaseValue != nil 44 return nil 45 } 46 if v.state == nil { 47 v.state = c.newNodeContext(v) 48 v.state.initBare() 49 v.state.refCount = 1 50 } 51 52 // An additional refCount for the current user. 53 v.state.refCount += 1 54 55 // TODO: see if we can get rid of ref counting after new evaluator is done: 56 // the recursive nature of the new evaluator should make this unnecessary. 57 58 return v.state 59 } 60 61 func (v *Vertex) getState(c *OpContext) *nodeContext { 62 s := v.getBareState(c) 63 if s != nil && !s.isInitialized { 64 s.scheduleConjuncts() 65 } 66 return s 67 } 68 69 // initNode initializes a nodeContext for the evaluation of the given Vertex. 70 func (n *nodeContext) initBare() { 71 v := n.node 72 if v.Parent != nil && v.Parent.state != nil { 73 v.state.depth = v.Parent.state.depth + 1 74 n.blockOn(allAncestorsProcessed) 75 } 76 77 n.blockOn(scalarKnown | listTypeKnown | arcTypeKnown) 78 79 if v.Label.IsDef() { 80 v.Closed = true 81 } 82 83 if v.Parent != nil { 84 if v.Parent.Closed { 85 v.Closed = true 86 } 87 } 88 } 89 90 func (n *nodeContext) scheduleConjuncts() { 91 n.isInitialized = true 92 93 v := n.node 94 ctx := n.ctx 95 96 ctx.stats.Unifications++ 97 98 // Set the cache to a cycle error to ensure a cyclic reference will result 99 // in an error if applicable. A cyclic error may be ignored for 100 // non-expression references. The cycle error may also be removed as soon 101 // as there is evidence what a correct value must be, but before all 102 // validation has taken place. 103 // 104 // TODO(cycle): having a more recursive algorithm would make this 105 // special cycle handling unnecessary. 106 v.BaseValue = cycle 107 108 defer ctx.PopArc(ctx.PushArc(v)) 109 110 root := n.node.rootCloseContext(n.ctx) 111 root.incDependent(n.ctx, INIT, nil) // decremented below 112 113 for _, c := range v.Conjuncts { 114 ci := c.CloseInfo 115 ci.cc = root 116 n.scheduleConjunct(c, ci) 117 } 118 119 root.decDependent(ctx, INIT, nil) 120 } 121 122 func (v *Vertex) unify(c *OpContext, needs condition, mode runMode) bool { 123 if c.LogEval > 0 { 124 c.nest++ 125 c.Logf(v, "Unify %v", fmt.Sprintf("%p", v)) 126 defer func() { 127 c.Logf(v, "END Unify") 128 c.nest-- 129 }() 130 } 131 132 if mode == ignore { 133 return false 134 } 135 136 n := v.getState(c) 137 if n == nil { 138 return true // already completed 139 } 140 defer n.free() 141 142 defer n.unmarkDepth(n.markDepth()) 143 144 // Typically a node processes all conjuncts before processing its fields. 145 // So this condition is very likely to trigger. If for some reason the 146 // parent has not been processed yet, we could attempt to process more 147 // of its tasks to increase the chances of being able to find the 148 // information we are looking for here. For now we just continue as is. 149 // 150 // For dynamic nodes, the parent only exists to provide a path context. 151 // 152 // Note that if mode is final, we will guarantee that the conditions for 153 // this if clause are met down the line. So we assume this is already the 154 // case and set the signal accordingly if so. 155 if v.Label.IsLet() || v.IsDynamic || v.Parent.allChildConjunctsKnown() || mode == finalize { 156 n.signal(allAncestorsProcessed) 157 } 158 159 nodeOnlyNeeds := needs &^ (subFieldsProcessed) 160 n.process(nodeOnlyNeeds, mode) 161 162 defer c.PopArc(c.PushArc(v)) 163 164 w := v.DerefDisjunct() 165 if w != v { 166 // Should resolve with dereference. 167 v.Closed = w.Closed 168 v.status = w.status 169 v.ChildErrors = CombineErrors(nil, v.ChildErrors, w.ChildErrors) 170 v.Arcs = nil 171 return w.state.meets(needs) 172 } 173 n.updateScalar() 174 175 if n.aStruct != nil { 176 n.updateNodeType(StructKind, n.aStruct, n.aStructID) 177 } 178 179 // First process all but the subfields. 180 switch { 181 case n.meets(nodeOnlyNeeds): 182 // pass through next phase. 183 case mode != finalize: 184 // TODO: disjunctions may benefit from evaluation as much prematurely 185 // as possible, as this increases the chances of premature failure. 186 // We should consider doing a recursive "attemptOnly" evaluation here. 187 return false 188 } 189 190 if n.isShared { 191 if isCyclePlaceholder(n.origBaseValue) { 192 n.origBaseValue = nil 193 } 194 } else if isCyclePlaceholder(n.node.BaseValue) { 195 n.node.BaseValue = nil 196 } 197 if !n.isShared { 198 // TODO(sharewithval): allow structure sharing if we only have validator 199 // and references. 200 // TODO: rewrite to use mode when we get rid of old evaluator. 201 state := finalized 202 n.validateValue(state) 203 } 204 205 if n.node.Label.IsLet() || n.meets(allAncestorsProcessed) { 206 if cc := v.rootCloseContext(n.ctx); !cc.isDecremented { // TODO: use v.cc 207 cc.decDependent(c, ROOT, nil) // REF(decrement:nodeDone) 208 cc.isDecremented = true 209 } 210 } 211 212 // At this point, no more conjuncts will be added, so we could decrement 213 // the notification counters. 214 215 switch { 216 case n.completed&subFieldsProcessed != 0: 217 // done 218 219 case needs&subFieldsProcessed != 0: 220 if DebugSort > 0 { 221 DebugSortArcs(n.ctx, n.node) 222 } 223 224 switch { 225 case assertStructuralCycle(n): 226 // TODO: consider bailing on error if n.errs != nil. 227 case n.completeAllArcs(needs, mode): 228 } 229 230 if mode == finalize { 231 n.signal(subFieldsProcessed) 232 } 233 234 if v.BaseValue == nil { 235 // TODO: this seems to not be possible. Possibly remove. 236 state := finalized 237 v.BaseValue = n.getValidators(state) 238 } 239 if v := n.node.Value(); v != nil && IsConcrete(v) { 240 // Ensure that checks are not run again when this value is used 241 // in a validator. 242 checks := n.checks 243 n.checks = n.checks[:0] 244 for _, v := range checks { 245 // TODO(errors): make Validate return bottom and generate 246 // optimized conflict message. Also track and inject IDs 247 // to determine origin location.s 248 if b := c.Validate(v, n.node); b != nil { 249 n.addBottom(b) 250 } 251 } 252 } 253 254 case needs&fieldSetKnown != 0: 255 n.evalArcTypes() 256 } 257 258 if err := n.getErr(); err != nil { 259 n.errs = nil 260 if b := n.node.Bottom(); b != nil { 261 err = CombineErrors(nil, b, err) 262 } 263 n.node.BaseValue = err 264 } 265 266 if mode == attemptOnly { 267 return n.meets(needs) 268 } 269 270 if mask := n.completed & needs; mask != 0 { 271 // TODO: phase3: validation 272 n.signal(mask) 273 } 274 275 n.finalizeDisjunctions() 276 277 w = v.DerefValue() // Dereference anything, including shared nodes. 278 if w != v { 279 // Clear value fields that are now referred to in the dereferenced 280 // value (w). 281 v.ChildErrors = nil 282 v.Arcs = nil 283 284 result := w.unify(c, needs, mode) 285 286 // Set control fields that are referenced without dereferencing. 287 if w.Closed { 288 v.Closed = true 289 } 290 if w.HasEllipsis { 291 v.HasEllipsis = true 292 } 293 v.status = w.status 294 295 return result 296 } 297 298 // TODO: adding this is wrong, but it should not cause the snippet below 299 // to hang. Investigate. 300 // v.Closed = v.cc.isClosed 301 // 302 // This hangs: 303 // issue1940: { 304 // #T: ["a", #T] | ["c", #T] | ["d", [...#T]] 305 // #A: t: #T 306 // #B: x: #A 307 // #C: #B 308 // #C: x: #A 309 // } 310 311 // validationCompleted 312 if n.completed&(subFieldsProcessed) != 0 { 313 n.node.HasEllipsis = n.node.cc.hasEllipsis 314 315 n.node.updateStatus(finalized) 316 317 defer n.unmarkOptional(n.markOptional()) 318 319 // The next piece of code addresses the following case. 320 // order matters 321 // c1: c: [string]: f2 322 // f2: c1 323 // Also: cycle/issue990 324 if pc := n.node.PatternConstraints; pc != nil { 325 for _, c := range pc.Pairs { 326 c.Constraint.Finalize(n.ctx) 327 } 328 } 329 330 if DebugDeps { 331 RecordDebugGraph(n.ctx, n.node, "Finalize") 332 } 333 } 334 335 return n.meets(needs) 336 } 337 338 // Once returning, all arcs plus conjuncts that can be known are known. 339 // 340 // Proof: 341 // - if there is a cycle, all completeNodeConjuncts will be called 342 // repeatedly for all nodes in this cycle, and all tasks on the cycle 343 // will have run at least once. 344 // - any tasks that were blocking on values on this circle to be completed 345 // will thus have to be completed at some point in time if they can. 346 // - any tasks that were blocking on values outside of this ring will have 347 // initiated its own execution, which is either not cyclic, and thus 348 // completes, or is on a different cycle, in which case it completes as 349 // well. 350 // 351 // Goal: 352 // - complete notifications 353 // - decrement reference counts for root and notify. 354 // NOT: 355 // - complete value. That is reserved for Unify. 356 func (n *nodeContext) completeNodeTasks(mode runMode) { 357 n.assertInitialized() 358 359 if n.isCompleting > 0 { 360 return 361 } 362 n.isCompleting++ 363 defer func() { 364 n.isCompleting-- 365 }() 366 367 v := n.node 368 c := n.ctx 369 370 if n.ctx.LogEval > 0 { 371 c.nest++ 372 defer func() { 373 c.nest-- 374 }() 375 } 376 377 if p := v.Parent; p != nil && p.state != nil { 378 if !v.IsDynamic && n.completed&allAncestorsProcessed == 0 { 379 p.state.completeNodeTasks(mode) 380 } 381 } 382 383 if v.IsDynamic || v.Parent.allChildConjunctsKnown() { 384 n.signal(allAncestorsProcessed) 385 } 386 387 if len(n.scheduler.tasks) != n.scheduler.taskPos { 388 // TODO: do we need any more requirements here? 389 const needs = valueKnown | fieldConjunctsKnown 390 391 n.process(needs, mode) 392 n.updateScalar() 393 } 394 395 // Check: 396 // - parents (done) 397 // - incoming notifications 398 // - pending arcs (or incoming COMPS) 399 // TODO: replace with something more principled that does not piggyback on 400 // debug information. 401 for _, r := range v.cc.externalDeps { 402 src := r.src 403 a := &src.arcs[r.index] 404 if a.decremented || a.kind != NOTIFY { 405 continue 406 } 407 if n := src.src.getState(n.ctx); n != nil { 408 n.completeNodeTasks(mode) 409 } 410 } 411 412 // As long as ancestors are not processed, it is still possible for 413 // conjuncts to be inserted. Until that time, it is not okay to decrement 414 // theroot. It is not necessary to wait on tasks to complete, though, 415 // as pending tasks will have their own dependencies on root, meaning it 416 // is safe to decrement here. 417 if !n.meets(allAncestorsProcessed) && !n.node.Label.IsLet() && mode != finalize { 418 return 419 } 420 421 // At this point, no more conjuncts will be added, so we could decrement 422 // the notification counters. 423 424 if cc := v.rootCloseContext(n.ctx); !cc.isDecremented { // TODO: use v.cc 425 cc.isDecremented = true 426 427 cc.decDependent(n.ctx, ROOT, nil) // REF(decrement:nodeDone) 428 } 429 430 return 431 } 432 433 func (n *nodeContext) updateScalar() { 434 // Set BaseValue to scalar, but only if it was not set before. Most notably, 435 // errors should not be discarded. 436 if n.scalar != nil && (!n.node.IsErr() || isCyclePlaceholder(n.node.BaseValue)) { 437 n.node.BaseValue = n.scalar 438 n.signal(scalarKnown) 439 } 440 } 441 442 func (n *nodeContext) completeAllArcs(needs condition, mode runMode) bool { 443 if n.node.status == evaluatingArcs { 444 // NOTE: this was an "incomplete" error pre v0.6. If this is a problem 445 // we could make this a CycleError. Technically, this may be correct, 446 // as it is possible to make the values exactly as the inserted 447 // values. It seems more user friendly to just disallow this, though. 448 // TODO: make uniform error messages 449 // see compbottom2.cue: 450 n.ctx.addErrf(CycleError, pos(n.node), "mutual dependency") 451 n.node.IsCyclic = true 452 // Consider using this, although not all 453 // mutual dependencies are irrecoverable. 454 // n.reportCycleError() 455 } 456 457 // TODO: remove the use of updateStatus as a cycle detection mechanism. 458 n.node.updateStatus(evaluatingArcs) 459 460 if n.underlying != nil { 461 // References within the disjunct may end up referencing the layer that 462 // this node overlays. Also for these nodes we want to be able to detect 463 // structural cycles early. For this reason, we also set the 464 // evaluatingArcs status in the underlying layer. 465 // 466 // TODO: for now, this seems not necessary. Moreover, this will cause 467 // benchmarks/cycle to display a spurious structural cycle. But it 468 // shortens some of the structural cycle depths. So consider using this. 469 // 470 // status := n.underlying.status 471 // n.underlying.updateStatus(evaluatingArcs) defer func() { 472 // n.underlying.status = status }() 473 } 474 475 // TODO: this should only be done if n is not currently running tasks. 476 // Investigate how to work around this. 477 n.completeNodeTasks(finalize) 478 479 for _, r := range n.node.cc.externalDeps { 480 src := r.src 481 a := &src.arcs[r.index] 482 if a.decremented { 483 continue 484 } 485 a.decremented = true 486 487 // FIXME: we should be careful to not evaluate parent nodes if we 488 // are inside a disjunction, or at least ensure that there are no 489 // disjunction values leaked into non-disjunction nodes through 490 // evaluating externalDeps. 491 src.src.unify(n.ctx, needTasksDone, attemptOnly) 492 a.cc.decDependent(n.ctx, a.kind, src) // REF(arcs) 493 } 494 495 n.incDepth() 496 497 // XXX(0.7): only set success if needs complete arcs. 498 success := true 499 // Visit arcs recursively to validate and compute error. 500 for n.arcPos < len(n.node.Arcs) { 501 a := n.node.Arcs[n.arcPos] 502 n.arcPos++ 503 504 if !a.unify(n.ctx, needs, mode) { 505 success = false 506 } 507 508 // At this point we need to ensure that all notification cycles 509 // for Arc a have been processed. 510 511 if a.ArcType == ArcPending { 512 // TODO: cancel tasks? 513 // TODO: is this ever run? Investigate once new evaluator work is 514 // complete. 515 a.ArcType = ArcNotPresent 516 continue 517 } 518 519 // Errors are allowed in let fields. Handle errors and failure to 520 // complete accordingly. 521 if !a.Label.IsLet() && a.ArcType <= ArcRequired { 522 a := a.DerefValue() 523 if err := a.Bottom(); err != nil { 524 n.node.AddChildError(err) 525 } 526 success = true // other arcs are irrelevant 527 } 528 529 // TODO: harmonize this error with "cannot combine" 530 switch { 531 case a.ArcType > ArcRequired, !a.Label.IsString(): 532 case n.kind&StructKind == 0: 533 if !n.node.IsErr() { 534 n.reportFieldMismatch(pos(a.Value()), nil, a.Label, n.node.Value()) 535 } 536 // case !wasVoid: 537 // case n.kind == TopKind: 538 // // Theoretically it may be possible that a "void" arc references 539 // // this top value where it really should have been a struct. One 540 // // way to solve this is to have two passes over the arcs, where 541 // // the first pass additionally analyzes whether comprehensions 542 // // will yield values and "un-voids" an arc ahead of the rest. 543 // // 544 // // At this moment, though, I fail to see a possibility to create 545 // // faulty CUE using this mechanism, though. At most error 546 // // messages are a bit unintuitive. This may change once we have 547 // // functionality to reflect on types. 548 // if _, ok := n.node.BaseValue.(*Bottom); !ok { 549 // n.node.BaseValue = &StructMarker{} 550 // n.kind = StructKind 551 // } 552 } 553 } 554 555 n.decDepth() 556 557 k := 0 558 for _, a := range n.node.Arcs { 559 if a.ArcType != ArcNotPresent { 560 n.node.Arcs[k] = a 561 k++ 562 } 563 } 564 n.node.Arcs = n.node.Arcs[:k] 565 566 // This should be called after all arcs have been processed, because 567 // whether sharing is possible or not may depend on how arcs with type 568 // ArcPending will resolve. 569 n.finalizeSharing() 570 571 // Strip struct literals that were not initialized and are not part 572 // of the output. 573 // 574 // TODO(perf): we could keep track if any such structs exist and only 575 // do this removal if there is a change of shrinking the list. 576 k = 0 577 for _, s := range n.node.Structs { 578 if s.initialized { 579 n.node.Structs[k] = s 580 k++ 581 } 582 } 583 n.node.Structs = n.node.Structs[:k] 584 585 // TODO: This seems to be necessary, but enables structural cycles. 586 // Evaluator whether we still need this. 587 // 588 // pc := n.node.PatternConstraints 589 // if pc == nil { 590 // return success 591 // } 592 // for _, c := range pc.Pairs { 593 // c.Constraint.Finalize(n.ctx) 594 // } 595 596 return success 597 } 598 599 func (n *nodeContext) evalArcTypes() { 600 for _, a := range n.node.Arcs { 601 if a.ArcType != ArcPending { 602 continue 603 } 604 a.unify(n.ctx, arcTypeKnown, yield) 605 // Ensure the arc is processed up to the desired level 606 if a.ArcType == ArcPending { 607 // TODO: cancel tasks? 608 a.ArcType = ArcNotPresent 609 } 610 } 611 } 612 613 func (v *Vertex) lookup(c *OpContext, pos token.Pos, f Feature, flags combinedFlags) *Vertex { 614 task := c.current() 615 needs := flags.conditions() 616 runMode := flags.runMode() 617 618 v = v.DerefValue() 619 620 c.Logf(c.vertex, "LOOKUP %v", f) 621 622 state := v.getState(c) 623 if state != nil { 624 // If the scheduler associated with this vertex was already running, 625 // it means we have encountered a cycle. In that case, we allow to 626 // proceed with partial data, in which case a "pending" arc will be 627 // created to be completed later. 628 629 // Report error for now. 630 if state.hasErr() { 631 c.AddBottom(state.getErr()) 632 } 633 // TODO: ideally this should not be run at this point. Consider under 634 // which circumstances this is still necessary, and at least ensure 635 // this will not be run if node v currently has a running task. 636 state.completeNodeTasks(attemptOnly) 637 } 638 639 // TODO: remove because unnecessary? 640 if task != nil && task.state != taskRUNNING { 641 return nil // abort, task is blocked or terminated in a cycle. 642 } 643 644 // TODO: verify lookup types. 645 646 arc := v.LookupRaw(f) 647 // We leave further dereferencing to the caller, but we do dereference for 648 // the remainder of this function to be able to check the status. 649 arcReturn := arc 650 if arc != nil { 651 arc = arc.DerefNonRooted() 652 // TODO(perf): NonRooted is the minimum, but consider doing more. 653 // arc = arc.DerefValue() 654 } 655 656 // TODO: clean up this logic: 657 // - signal arcTypeKnown when ArcMember or ArcNotPresent is set, 658 // similarly to scalarKnown. 659 // - make it clear we want to yield if it is now known if a field exists. 660 661 var arcState *nodeContext 662 switch { 663 case arc != nil: 664 if arc.ArcType == ArcMember { 665 return arcReturn 666 } 667 arcState = arc.getState(c) 668 669 case state == nil || state.meets(needTasksDone): 670 // This arc cannot exist. 671 v.reportFieldIndexError(c, pos, f) 672 return nil 673 674 default: 675 arc = &Vertex{Parent: state.node, Label: f, ArcType: ArcPending} 676 v.Arcs = append(v.Arcs, arc) 677 arcState = arc.getState(c) // TODO: consider using getBareState. 678 } 679 680 if arcState != nil && (!arcState.meets(needTasksDone) || !arcState.meets(arcTypeKnown)) { 681 needs |= arcTypeKnown 682 // If this arc is not ArcMember, which it is not at this point, 683 // any pending arcs could influence the field set. 684 for _, a := range arc.Arcs { 685 if a.ArcType == ArcPending { 686 needs |= fieldSetKnown 687 break 688 } 689 } 690 arcState.completeNodeTasks(attemptOnly) 691 692 // Child nodes, if pending and derived from a comprehension, may 693 // still cause this arc to become not pending. 694 if arc.ArcType != ArcMember { 695 for _, a := range arcState.node.Arcs { 696 if a.ArcType == ArcPending { 697 a.unify(c, arcTypeKnown, runMode) 698 } 699 } 700 } 701 702 switch runMode { 703 case ignore, attemptOnly: 704 // TODO: should we avoid notifying ArcPending vertices here? 705 if task != nil { 706 arcState.addNotify2(task.node.node, task.id) 707 } 708 return arcReturn 709 710 case yield: 711 arcState.process(needs, yield) 712 // continue processing, as successful processing may still result 713 // in an invalid field. 714 715 case finalize: 716 // TODO: should we try to use finalize? Using it results in errors and this works. It would be more principled, though. 717 arcState.process(needs, yield) 718 } 719 } 720 721 switch arc.ArcType { 722 case ArcMember: 723 return arcReturn 724 725 case ArcOptional, ArcRequired: 726 label := f.SelectorString(c.Runtime) 727 b := &Bottom{ 728 Code: IncompleteError, 729 Err: c.NewPosf(pos, 730 "cannot reference optional field: %s", label), 731 } 732 c.AddBottom(b) 733 // TODO: yield failure 734 return nil 735 736 case ArcNotPresent: 737 v.reportFieldIndexError(c, pos, f) 738 return nil 739 740 case ArcPending: 741 // should not happen. 742 panic("unreachable") 743 } 744 745 v.reportFieldIndexError(c, pos, f) 746 return nil 747 } 748 749 // accept reports whether the given feature is allowed by the pattern 750 // constraints. 751 func (v *Vertex) accept(ctx *OpContext, f Feature) bool { 752 // TODO: this is already handled by callers at the moment, but it may be 753 // better design to move this here. 754 // if v.LookupRaw(f) != nil { 755 // return true, true 756 // } 757 758 v = v.DerefValue() 759 760 pc := v.PatternConstraints 761 if pc == nil { 762 return false 763 } 764 765 return matchPattern(ctx, pc.Allowed, f) 766 }