this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/core/adt: add handleParents and processAncestors

The scheduler's process() now calls handleParents before
running tasks. handleParents walks the Vertex parent chain
and triggers fieldConjunctsKnown processing on ancestors
that still have pending conjuncts. A new childConjunctsDone
condition lets processAncestors short-circuit once a
parent's contributions are confirmed complete.

Also fixes a disjunction propagation regression in
issue3857 where one arm of a shared disjunct was missing
its default marker.

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I6dadfe9d7f7ba59b6531a6c41d255579c7d6f797
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1236062
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>

+94 -31
+2 -2
cue/testdata/disjunctions/issue3857.txtar
··· 49 49 } 50 50 out: { 51 51 two: {shared: "foo", disj: *"default" | _} 52 - one: {shared: "foo", disj: _} 52 + one: {shared: "foo", disj: *"default" | _} 53 53 } 54 54 }) 55 55 } ··· 82 82 env1: { 83 83 conf: { 84 84 two: {shared: "foo", disj: *{} | _} 85 - one: {shared: "foo", disj: *{} | _ | {} | _} 85 + one: {shared: "foo", disj: *{} | _} 86 86 } 87 87 } 88 88 })
+3
internal/core/adt/sched.go
··· 415 415 s.signal(f(s)) 416 416 } 417 417 418 + s.handleParents(needs, mode) 419 + 418 420 if s.ctx.LogEval > 0 && len(s.tasks) > 0 { 419 421 420 422 if v := s.tasks[0].node.node; v != nil { ··· 712 714 defer func() { 713 715 ctx.freeScope = ctx.freeScope[:len(ctx.freeScope)-1] 714 716 717 + // TODO(pushdown): try to remove once transitioned. 715 718 if n := t.node; n.toComplete { 716 719 n.toComplete = false 717 720 n.completeNodeTasks(attemptOnly)
+4
internal/core/adt/sched_test.go
··· 16 16 17 17 import ( 18 18 "fmt" 19 + "math/bits" 19 20 "strings" 20 21 "testing" 21 22 ··· 55 56 } 56 57 if autoFieldConjunctsKnown != fieldConjunctsKnown { 57 58 t.Error("inconsistent state name for fieldConjunctsKnown") 59 + } 60 + if idx := bits.TrailingZeros16(uint16(fieldConjunctsKnown)); idx != fieldConjunctsKnownIdx { 61 + t.Errorf("fieldConjunctsKnownIdx = %d, want %d", fieldConjunctsKnownIdx, idx) 58 62 } 59 63 } 60 64
+76 -10
internal/core/adt/states.go
··· 178 178 // used to trigger finalization of disjunctions. 179 179 disjunctionTask 180 180 181 + childConjunctsDone 182 + 181 183 leftOfMaxCoreCondition 182 184 183 185 finalStateKnown condition = leftOfMaxCoreCondition - 1 ··· 245 247 246 248 // TODO(assoclist): see comment above. 247 249 listTypeKnown condition = 0 250 + 251 + // fieldConjunctsKnownIdx is the bit-position index of 252 + // fieldConjunctsKnown, for use as a scheduler.counters index. 253 + // It must equal bits.TrailingZeros16(uint16(fieldConjunctsKnown)). 254 + fieldConjunctsKnownIdx = 4 248 255 ) 249 256 250 257 // schedConfig configures a taskContext with the states needed for the ··· 256 263 complete: stateCompletions, 257 264 } 258 265 266 + // handleParents checks whether needs is already met and, if not, triggers 267 + // ancestor processing to propagate conjuncts downward. It reports whether 268 + // all ancestors have completed processing. 269 + func (s *scheduler) handleParents(needs condition, mode runMode) (done bool) { 270 + if s.meets(needs) { 271 + return true 272 + } 273 + 274 + return s.node.processAncestors(mode) 275 + } 276 + 277 + // processAncestors walks up the parent chain, recursively processing each 278 + // ancestor's pending conjuncts until fieldConjunctsKnown is reached. It 279 + // returns true when all ancestors have finished processing. 280 + func (n *nodeContext) processAncestors(mode runMode) (done bool) { 281 + if n == nil { 282 + return // Some tests do not set node. 283 + } 284 + v := n.node 285 + 286 + if n.meets(allAncestorsProcessed) { 287 + return true 288 + } 289 + 290 + parentsDone := true 291 + p := n.node.Parent 292 + switch { 293 + case p != nil: 294 + n := p.state 295 + // p.state is nil when the parent vertex exists but has not yet 296 + // entered evaluation (no nodeContext has been created for it). 297 + // Two known trigger paths: 298 + // 1. completePending → lookup → process → handleParents: a 299 + // SelectorExpr inside an IfClause fires in a comprehension 300 + // while its parent struct is still lazy. 301 + // 2. unifyNode passes arcTypeKnown|fieldSetKnown to process, 302 + // so handleParents no longer returns early once arcTypeKnown 303 + // is met, reaching processAncestors for nodes whose parents 304 + // were never unified. (Reproducer: TestScript/cmd_typocheck) 305 + // Without this guard the nil receiver panics at n.meets(...). 306 + if n == nil { 307 + break 308 + } 309 + 310 + if n.meets(childConjunctsDone) { 311 + break 312 + } 313 + 314 + parentsDone = n.processAncestors(mode) 315 + 316 + if n.counters[fieldConjunctsKnownIdx] > 0 { 317 + n.process(fieldConjunctsKnown, mode) 318 + } 319 + 320 + if parentsDone && n.counters[fieldConjunctsKnownIdx] == 0 { 321 + n.completed |= childConjunctsDone 322 + } 323 + } 324 + 325 + if done || v.IsDynamic || v.Label.IsLet() || 326 + v.Parent.allChildConjunctsKnown(n.ctx) { 327 + n.signal(allAncestorsProcessed) 328 + } 329 + 330 + return parentsDone 331 + } 332 + 259 333 // stateCompletions indicates the completion of conditions based on the 260 334 // completions of other conditions. 261 335 func stateCompletions(s *scheduler) condition { ··· 318 392 return true 319 393 } 320 394 321 - // TODO(refcount): allow partial processed? 322 - if v.Status() == finalized || (v.state == nil && v.status != unprocessed) { 323 - // This can happen, for instance, if this is called on a parent of a 324 - // rooted node that is marked as a parent for a dynamic node. 325 - // In practice this should be handled by the caller, but we add this 326 - // as an extra safeguard. 327 - // TODO: remove this check at some point. 395 + n := v.getState(ctx) 396 + if n == nil { 328 397 return true 329 398 } 330 - 331 - n := v.getState(ctx) 332 - 333 399 return n.meets(fieldConjunctsKnown | allAncestorsProcessed) 334 400 } 335 401
+9 -19
internal/core/adt/unify.go
··· 353 353 } 354 354 } 355 355 356 + // TODO(pushdown): remove 356 357 case needs&fieldSetKnown != 0: 357 358 n.evalArcTypes(mode) 358 359 } ··· 515 516 defer n.ctx.Un(n.ctx.Indentf(n.node, "(%v)", mode)) 516 517 } 517 518 519 + // TODO(pushdown): can be removed remove 518 520 // In attemptOnly mode, don't assert initialization to allow processing 519 521 // of partially initialized vertices 520 522 if mode != attemptOnly { ··· 524 526 return 525 527 } 526 528 529 + // TODO(pushdown): okay to remove, but results in different default 530 + // behavior. Verify. 527 531 if n.isCompleting > 0 { 528 532 return 529 533 } ··· 532 536 n.isCompleting-- 533 537 }() 534 538 539 + // Needed to not have nil pointer exceptions in some builtin calls. 535 540 v := n.node 536 - 537 - if !v.Label.IsLet() { 538 - if p := v.Parent; p != nil && p.state != nil { 539 - if !v.IsDynamic && n.completed&allAncestorsProcessed == 0 { 540 - p.state.completeNodeTasks(mode) 541 - } 542 - } 543 - } 544 - 545 541 if v.IsDynamic || v.Label.IsLet() || v.Parent.allChildConjunctsKnown(n.ctx) { 546 542 n.signal(allAncestorsProcessed) 547 543 } ··· 552 548 n.process(needs, mode) 553 549 n.updateScalar() 554 550 } 555 - 556 - // As long as ancestors are not processed, it is still possible for 557 - // conjuncts to be inserted. Until that time, it is not okay to decrement 558 - // theroot. It is not necessary to wait on tasks to complete, though, 559 - // as pending tasks will have their own dependencies on root, meaning it 560 - // is safe to decrement here. 561 - if !n.meets(allAncestorsProcessed) && !n.node.Label.IsLet() && mode != finalize { 562 - return 563 - } 564 551 } 565 552 566 553 func (n *nodeContext) updateScalar() { ··· 592 579 593 580 // TODO: this should only be done if n is not currently running tasks. 594 581 // Investigate how to work around this. 595 - n.completeNodeTasks(finalize) 582 + // TODO(pushdown): probably okay to remove. 583 + // n.completeNodeTasks(finalize) 596 584 597 585 n.incDepth() 598 586 defer n.decDepth() ··· 815 803 // which circumstances this is still necessary, and at least ensure 816 804 // this will not be run if node v currently has a running task. 817 805 state.completeNodeTasks(attemptOnly) 806 + // TODO(pushdown): consider this once transitioned. 807 + // state.process(needFieldSetKnown, flags.runMode) 818 808 } 819 809 820 810 // TODO: verify lookup types.