cuelang.org/go@v0.10.1/internal/core/adt/conjunct.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/ast" 21 ) 22 23 // This file contains functionality for processing conjuncts to insert the 24 // corresponding values in the Vertex. 25 // 26 // Conjuncts are divided into two classes: 27 // - literal values that need no evaluation: these are inserted directly into 28 // the Vertex. 29 // - field or value expressions that need to be evaluated: these are inserted 30 // as a task into the Vertex' associated scheduler for later evaluation. 31 // The implementation of these tasks can be found in tasks.go. 32 // 33 // The main entrypoint is scheduleConjunct. 34 35 // scheduleConjunct splits c into parts to be incrementally processed and queues 36 // these parts up for processing. it will itself not cause recursive processing. 37 func (n *nodeContext) scheduleConjunct(c Conjunct, id CloseInfo) { 38 n.assertInitialized() 39 40 // Explanation of switch statement: 41 // 42 // A Conjunct can be a leaf or, through a ConjunctGroup, a tree. The tree 43 // reflects the history of how the conjunct was inserted in terms of 44 // definitions and embeddings. This, in turn, is used to compute closedness. 45 // 46 // Once all conjuncts for a Vertex have been collected, this tree contains 47 // all the information needed to trace its histroy: if a Vertex is 48 // referenced in an expression, this tree can be used to insert the 49 // conjuncts keeping closedness in mind. 50 // 51 // In the collection phase, however, this is not sufficient. CUE computes 52 // conjuncts "out of band". This means that conjuncts accumulate in 53 // different parts of the tree in an indeterminate order. closeContext is 54 // used to account for this. 55 // 56 // Basically, if the closeContext associated with c belongs to n, we take 57 // it that the conjunct needs to be inserted at the point in the tree 58 // associated by this closeContext. If, on the other hand, the closeContext 59 // is not defined or does not belong to this node, we take this conjunct 60 // is inserted by means of a reference. In this case we assume that the 61 // computation of the tree has completed and the tree can be used to reflect 62 // the closedness structure. 63 // 64 // TODO: once the evaluator is done and all tests pass, consider having 65 // two different entry points to account for these cases. 66 switch cc := c.CloseInfo.cc; { 67 case cc == nil || cc.src != n.node: 68 // In this case, a Conjunct is inserted from another Arc. If the 69 // conjunct represents an embedding or definition, we need to create a 70 // new closeContext to represent this. 71 if id.cc == nil { 72 id.cc = n.node.rootCloseContext(n.ctx) 73 } 74 if id.cc == cc { 75 panic("inconsistent state: same closeContext") 76 } 77 var t closeNodeType 78 if c.CloseInfo.FromDef { 79 t |= closeDef 80 } 81 if c.CloseInfo.FromEmbed { 82 t |= closeEmbed 83 } 84 if t != 0 { 85 id, _ = id.spawnCloseContext(n.ctx, t) 86 } 87 if !id.cc.done { 88 id.cc.incDependent(n.ctx, DEFER, nil) 89 defer id.cc.decDependent(n.ctx, DEFER, nil) 90 } 91 92 if id.cc.src != n.node { 93 panic("inconsistent state: nodes differ") 94 } 95 default: 96 97 // In this case, the conjunct is inserted as the result of an expansion 98 // of a conjunct in place, not a reference. In this case, we must use 99 // the cached closeContext. 100 id.cc = cc 101 102 // Note this subtlety: we MUST take the cycle info from c when this is 103 // an in place evaluated node, otherwise we must take that of id. 104 id.CycleInfo = c.CloseInfo.CycleInfo 105 } 106 107 if id.cc.needsCloseInSchedule != nil { 108 dep := id.cc.needsCloseInSchedule 109 id.cc.needsCloseInSchedule = nil 110 defer id.cc.decDependent(n.ctx, EVAL, dep) 111 } 112 113 env := c.Env 114 115 if id.cc.isDef { 116 n.node.Closed = true 117 } 118 119 switch x := c.Elem().(type) { 120 case *ConjunctGroup: 121 for _, c := range *x { 122 // TODO(perf): can be one loop 123 124 cc := c.CloseInfo.cc 125 if cc.src == n.node && cc.needsCloseInSchedule != nil { 126 // We need to handle this specifically within the ConjunctGroup 127 // loop, because multiple conjuncts may be using the same root 128 // closeContext. This can be merged once Vertex.Conjuncts is an 129 // interface, requiring any list to be a root conjunct. 130 131 dep := cc.needsCloseInSchedule 132 cc.needsCloseInSchedule = nil 133 defer cc.decDependent(n.ctx, EVAL, dep) 134 } 135 } 136 for _, c := range *x { 137 n.scheduleConjunct(c, id) 138 } 139 140 case *Vertex: 141 // TODO: move this logic to scheduleVertexConjuncts or at least ensure 142 // that we can also share data Vertices? 143 if x.IsData() { 144 n.unshare() 145 n.insertValueConjunct(env, x, id) 146 } else { 147 n.scheduleVertexConjuncts(c, x, id) 148 } 149 150 case Value: 151 // TODO: perhaps some values could be shared. 152 n.unshare() 153 n.insertValueConjunct(env, x, id) 154 155 case *BinaryExpr: 156 // NOTE: do not unshare: a conjunction could still allow structure 157 // sharing, such as in the case of `ref & ref`. 158 if x.Op == AndOp { 159 n.scheduleConjunct(MakeConjunct(env, x.X, id), id) 160 n.scheduleConjunct(MakeConjunct(env, x.Y, id), id) 161 return 162 } 163 164 n.unshare() 165 // Even though disjunctions and conjunctions are excluded, the result 166 // must may still be list in the case of list arithmetic. This could 167 // be a scalar value only once this is no longer supported. 168 n.scheduleTask(handleExpr, env, x, id) 169 170 case *StructLit: 171 n.unshare() 172 n.scheduleStruct(env, x, id) 173 174 case *ListLit: 175 n.unshare() 176 177 // At this point we known we have at least an empty list. 178 n.updateCyclicStatus(id) 179 180 env := &Environment{ 181 Up: env, 182 Vertex: n.node, 183 } 184 n.scheduleTask(handleListLit, env, x, id) 185 186 case *DisjunctionExpr: 187 n.unshare() 188 189 // TODO(perf): reuse envDisjunct values so that we can also reuse the 190 // disjunct slice. 191 d := envDisjunct{ 192 env: env, 193 cloneID: id, 194 src: x, 195 expr: x, 196 } 197 for _, dv := range x.Values { 198 d.disjuncts = append(d.disjuncts, disjunct{ 199 expr: dv.Val, 200 isDefault: dv.Default, 201 mode: mode(x.HasDefaults, dv.Default), 202 }) 203 } 204 n.scheduleDisjunction(d) 205 206 case *Comprehension: 207 // always a partial comprehension. 208 n.insertComprehension(env, x, id) 209 210 case Resolver: 211 n.scheduleTask(handleResolver, env, x, id) 212 213 case Evaluator: 214 n.unshare() 215 // Interpolation, UnaryExpr, CallExpr 216 n.scheduleTask(handleExpr, env, x, id) 217 218 default: 219 panic("unreachable") 220 } 221 222 n.ctx.stats.Conjuncts++ 223 } 224 225 // scheduleStruct records all elements of this conjunct in the structure and 226 // then processes it. If an element needs to be inserted for evaluation, 227 // it may be scheduled. 228 func (n *nodeContext) scheduleStruct(env *Environment, 229 s *StructLit, 230 ci CloseInfo) { 231 n.updateCyclicStatus(ci) 232 233 // NOTE: This is a crucial point in the code: 234 // Unification dereferencing happens here. The child nodes are set to 235 // an Environment linked to the current node. Together with the De Bruijn 236 // indices, this determines to which Vertex a reference resolves. 237 238 childEnv := &Environment{ 239 Up: env, 240 Vertex: n.node, 241 } 242 243 hasEmbed := false 244 hasEllipsis := false 245 246 // TODO: do we still need this? 247 // shouldClose := ci.cc.isDef || ci.cc.isClosedOnce 248 249 s.Init(n.ctx) 250 251 // TODO: do we still need to AddStruct and do we still need to Disable? 252 parent := n.node.AddStruct(s, childEnv, ci) 253 parent.Disable = true // disable until processing is done. 254 ci.IsClosed = false 255 256 // TODO: precompile 257 loop1: 258 for _, d := range s.Decls { 259 switch d.(type) { 260 case *Ellipsis: 261 hasEllipsis = true 262 break loop1 263 } 264 } 265 266 // TODO(perf): precompile whether struct has embedding. 267 loop2: 268 for _, d := range s.Decls { 269 switch d.(type) { 270 case *Comprehension, Expr: 271 // No need to increment and decrement, as there will be at least 272 // one entry. 273 if _, ok := s.Src.(*ast.File); !ok { 274 // If this is not a file, the struct indicates the scope/ 275 // boundary at which closedness should apply. This is not true 276 // for files. 277 // TODO: set this as a flag in StructLit so as to not have to 278 // do the somewhat dangerous cast here. 279 ci, _ = ci.spawnCloseContext(n.ctx, 0) 280 } 281 // Note: adding a count is not needed here, as there will be an 282 // embed spawn below. 283 hasEmbed = true 284 break loop2 285 } 286 } 287 288 // First add fixed fields and schedule expressions. 289 for _, d := range s.Decls { 290 switch x := d.(type) { 291 case *Field: 292 if x.Label.IsString() && x.ArcType == ArcMember { 293 n.aStruct = s 294 n.aStructID = ci 295 } 296 fc := MakeConjunct(childEnv, x, ci) 297 // fc.CloseInfo.cc = nil // TODO: should we add this? 298 n.insertArc(x.Label, x.ArcType, fc, ci, true) 299 300 case *LetField: 301 lc := MakeConjunct(childEnv, x, ci) 302 n.insertArc(x.Label, ArcMember, lc, ci, true) 303 304 case *Comprehension: 305 ci, cc := ci.spawnCloseContext(n.ctx, closeEmbed) 306 cc.incDependent(n.ctx, DEFER, nil) 307 defer cc.decDependent(n.ctx, DEFER, nil) 308 n.insertComprehension(childEnv, x, ci) 309 hasEmbed = true 310 311 case *Ellipsis: 312 // Can be added unconditionally to patterns. 313 ci.cc.isDef = false 314 ci.cc.isClosed = false 315 ci.cc.isDefOrig = false 316 317 case *DynamicField: 318 if x.ArcType == ArcMember { 319 n.aStruct = s 320 n.aStructID = ci 321 } 322 n.scheduleTask(handleDynamic, childEnv, x, ci) 323 324 case *BulkOptionalField: 325 326 // All do not depend on each other, so can be added at once. 327 n.scheduleTask(handlePatternConstraint, childEnv, x, ci) 328 329 case Expr: 330 // TODO: perhaps special case scalar Values to avoid creating embedding. 331 ci, cc := ci.spawnCloseContext(n.ctx, closeEmbed) 332 333 // TODO: do we need to increment here? 334 cc.incDependent(n.ctx, DEFER, nil) // decrement deferred below 335 defer cc.decDependent(n.ctx, DEFER, nil) 336 337 ec := MakeConjunct(childEnv, x, ci) 338 n.scheduleConjunct(ec, ci) 339 hasEmbed = true 340 } 341 } 342 if hasEllipsis { 343 ci.cc.hasEllipsis = true 344 } 345 if !hasEmbed { 346 n.aStruct = s 347 n.aStructID = ci 348 ci.cc.hasNonTop = true 349 } 350 351 // TODO: probably no longer necessary. 352 parent.Disable = false 353 } 354 355 // scheduleVertexConjuncts injects the conjuncst of src n. If src was not fully 356 // evaluated, it subscribes dst for future updates. 357 func (n *nodeContext) scheduleVertexConjuncts(c Conjunct, arc *Vertex, closeInfo CloseInfo) { 358 // disjunctions, we need to dereference he underlying node. 359 if deref(n.node) == deref(arc) { 360 return 361 } 362 363 if n.shareIfPossible(c, arc, closeInfo) { 364 arc.getState(n.ctx) 365 return 366 } 367 368 // We need to ensure that each arc is only unified once (or at least) a 369 // bounded time, witch each conjunct. Comprehensions, for instance, may 370 // distribute a value across many values that get unified back into the 371 // same value. If such a value is a disjunction, than a disjunction of N 372 // disjuncts will result in a factor N more unifications for each 373 // occurrence of such value, resulting in exponential running time. This 374 // is especially common values that are used as a type. 375 // 376 // However, unification is idempotent, so each such conjunct only needs 377 // to be unified once. This cache checks for this and prevents an 378 // exponential blowup in such case. 379 // 380 // TODO(perf): this cache ensures the conjuncts of an arc at most once 381 // per ID. However, we really need to add the conjuncts of an arc only 382 // once total, and then add the close information once per close ID 383 // (pointer can probably be shared). Aside from being more performant, 384 // this is probably the best way to guarantee that conjunctions are 385 // linear in this case. 386 387 ciKey := closeInfo 388 ciKey.Refs = nil 389 ciKey.Inline = false 390 key := arcKey{arc, ciKey} 391 for _, k := range n.arcMap { 392 if key == k { 393 return 394 } 395 } 396 n.arcMap = append(n.arcMap, key) 397 398 if IsDef(c.Expr()) { 399 // TODO: or should we always insert the wrapper (for errors)? 400 ci, dc := closeInfo.spawnCloseContext(n.ctx, closeDef) 401 closeInfo = ci 402 403 dc.incDependent(n.ctx, DEFER, nil) // decrement deferred below 404 defer dc.decDependent(n.ctx, DEFER, nil) 405 } 406 407 if !n.node.nonRooted || n.node.IsDynamic { 408 if state := arc.getBareState(n.ctx); state != nil { 409 state.addNotify2(n.node, closeInfo) 410 } 411 } 412 413 // TODO(perf): buffer or use stack. 414 var a []*closeContext 415 a = appendPrefix(a, closeInfo.cc) 416 417 // Use explicit index in case Conjuncts grows during iteration. 418 for i := 0; i < len(arc.Conjuncts); i++ { 419 c := arc.Conjuncts[i] 420 n.insertAndSkipConjuncts(a, c, closeInfo) 421 } 422 423 if state := arc.getBareState(n.ctx); state != nil { 424 n.toComplete = true 425 } 426 } 427 428 // appendPrefix records the closeContext from the root of the current node to 429 // cc by walking up the parent chain and storing the results ancestor first. 430 // This is used to split conjunct trees into a forest of little trees. 431 func appendPrefix(a []*closeContext, cc *closeContext) []*closeContext { 432 if cc.parent != nil { 433 a = appendPrefix(a, cc.parent) 434 } 435 a = append(a, cc) 436 return a 437 } 438 439 // insertAndSkipConjuncts analyzes the conjunct tree represented by c and splits 440 // it into branches from the point where it deviates from the conjunct branch 441 // represented by skip. 442 // 443 // TODO(evalv3): Consider this example: 444 // 445 // #A: { 446 // b: {} // error only reported here. 447 // c: b & { 448 // // error (g not allowed) not reported here, as it would be okay if b 449 // // were valid. Instead, it is reported at b only. This is conform 450 // // the spec. 451 // d: 1 452 // } 453 // } 454 // x: #A 455 // x: b: g: 1 456 // 457 // We could also report an error at g by tracing if a conjunct crosses a isDef 458 // boundary between the root of c and the cc of the conjunct. 459 // Not doing so might have an effect on the outcome of disjunctions. This may be 460 // okay (ideally closedness is not modal), but something to consider. For now, 461 // we should probably copy whatever v2 was doing. 462 func (n *nodeContext) insertAndSkipConjuncts(skip []*closeContext, c Conjunct, id CloseInfo) { 463 if c.CloseInfo.cc == nil { 464 n.scheduleConjunct(c, id) 465 return 466 } 467 468 root := c.CloseInfo.cc.origin 469 470 // TODO(perf): closeContexts should be exact prefixes. So instead of 471 // searching the list, we could test them incrementally. This seems more 472 // robust for now as the data structure might slightly change and cause 473 // disalignment. 474 for _, s := range skip { 475 if root == s.origin { 476 switch x := c.Elem().(type) { 477 case *ConjunctGroup: 478 for _, c := range *x { 479 n.insertAndSkipConjuncts(skip, c, id) 480 } 481 482 default: 483 // TODO: do leaf conjuncts that match need different treatment 484 // from those that don't? Right now, we treat them the same. 485 n.scheduleConjunct(c, id) 486 } 487 return 488 } 489 } 490 491 n.scheduleConjunct(c, id) 492 } 493 494 func (n *nodeContext) addNotify2(v *Vertex, c CloseInfo) []receiver { 495 // No need to do the notification mechanism if we are already complete. 496 old := n.notify 497 switch { 498 case n.node.isFinal(): 499 return old 500 case !n.node.isInProgress(): 501 case n.meets(allAncestorsProcessed): 502 return old 503 } 504 505 // Create a "root" closeContext to reflect the entry point of the 506 // reference into n.node relative to cc within v. After that, we can use 507 // assignConjunct to add new conjuncts. 508 509 // TODO: dedup: only add if t does not already exist. First check if this 510 // is even possible by adding a panic. 511 root := n.node.rootCloseContext(n.ctx) 512 if root.isDecremented { 513 return old 514 } 515 516 for _, r := range n.notify { 517 if r.v == v && r.cc == c.cc { 518 return old 519 } 520 } 521 522 cc := c.cc 523 524 if root.linkNotify(n.ctx, v, cc, c.CycleInfo) { 525 n.notify = append(n.notify, receiver{v, cc}) 526 } 527 528 return old 529 } 530 531 // Literal conjuncts 532 533 func (n *nodeContext) insertValueConjunct(env *Environment, v Value, id CloseInfo) { 534 n.updateCyclicStatus(id) 535 536 ctx := n.ctx 537 538 switch x := v.(type) { 539 case *Vertex: 540 if m, ok := x.BaseValue.(*StructMarker); ok { 541 n.aStruct = x 542 n.aStructID = id 543 if m.NeedClose { 544 // TODO: In the new evaluator this is used to mark a struct 545 // as closed in the debug output. Once the old evaluator is 546 // gone, we could simplify this. 547 id.IsClosed = true 548 if ctx.isDevVersion() { 549 var cc *closeContext 550 id, cc = id.spawnCloseContext(n.ctx, 0) 551 cc.isClosedOnce = true 552 } 553 } 554 } 555 556 if !x.IsData() { 557 c := MakeConjunct(env, x, id) 558 n.scheduleVertexConjuncts(c, x, id) 559 return 560 } 561 562 // TODO: evaluate value? 563 switch v := x.BaseValue.(type) { 564 default: 565 panic(fmt.Sprintf("invalid type %T", x.BaseValue)) 566 567 case *ListMarker: 568 n.updateCyclicStatus(id) 569 570 // TODO: arguably we know now that the type _must_ be a list. 571 n.scheduleTask(handleListVertex, env, x, id) 572 573 return 574 575 case *StructMarker: 576 for _, a := range x.Arcs { 577 if a.ArcType != ArcMember { 578 continue 579 } 580 // TODO(errors): report error when this is a regular field. 581 c := MakeConjunct(nil, a, id) 582 n.insertArc(a.Label, a.ArcType, c, id, true) 583 } 584 585 case Value: 586 n.insertValueConjunct(env, v, id) 587 } 588 589 return 590 591 case *Bottom: 592 id.cc.hasNonTop = true 593 n.addBottom(x) 594 return 595 596 case *Builtin: 597 id.cc.hasNonTop = true 598 if v := x.BareValidator(); v != nil { 599 n.insertValueConjunct(env, v, id) 600 return 601 } 602 } 603 604 if !n.updateNodeType(v.Kind(), v, id) { 605 return 606 } 607 608 switch x := v.(type) { 609 case *Disjunction: 610 // TODO(perf): reuse envDisjunct values so that we can also reuse the 611 // disjunct slice. 612 d := envDisjunct{ 613 env: env, 614 cloneID: id, 615 src: x, 616 value: x, 617 } 618 for i, dv := range x.Values { 619 d.disjuncts = append(d.disjuncts, disjunct{ 620 expr: dv, 621 isDefault: i < x.NumDefaults, 622 mode: mode(x.HasDefaults, i < x.NumDefaults), 623 }) 624 } 625 n.scheduleDisjunction(d) 626 627 case *Conjunction: 628 // TODO: consider sharing: conjunct could be `ref & ref`, for instance, 629 // in which case ref could still be shared. 630 631 for _, x := range x.Values { 632 n.insertValueConjunct(env, x, id) 633 } 634 635 case *Top: 636 n.hasTop = true 637 id.cc.hasTop = true 638 639 case *BasicType: 640 id.cc.hasNonTop = true 641 642 case *BoundValue: 643 id.cc.hasNonTop = true 644 switch x.Op { 645 case LessThanOp, LessEqualOp: 646 if y := n.upperBound; y != nil { 647 n.upperBound = nil 648 v := SimplifyBounds(ctx, n.kind, x, y) 649 if err := valueError(v); err != nil { 650 err.AddPosition(v) 651 err.AddPosition(n.upperBound) 652 err.AddClosedPositions(id) 653 } 654 n.insertValueConjunct(env, v, id) 655 return 656 } 657 n.upperBound = x 658 659 case GreaterThanOp, GreaterEqualOp: 660 if y := n.lowerBound; y != nil { 661 n.lowerBound = nil 662 v := SimplifyBounds(ctx, n.kind, x, y) 663 if err := valueError(v); err != nil { 664 err.AddPosition(v) 665 err.AddPosition(n.lowerBound) 666 err.AddClosedPositions(id) 667 } 668 n.insertValueConjunct(env, v, id) 669 return 670 } 671 n.lowerBound = x 672 673 case EqualOp, NotEqualOp, MatchOp, NotMatchOp: 674 // This check serves as simplifier, but also to remove duplicates. 675 k := 0 676 match := false 677 for _, c := range n.checks { 678 if y, ok := c.(*BoundValue); ok { 679 switch z := SimplifyBounds(ctx, n.kind, x, y); { 680 case z == y: 681 match = true 682 case z == x: 683 continue 684 } 685 } 686 n.checks[k] = c 687 k++ 688 } 689 n.checks = n.checks[:k] 690 if !match { 691 n.checks = append(n.checks, x) 692 } 693 return 694 } 695 696 case Validator: 697 // This check serves as simplifier, but also to remove duplicates. 698 for i, y := range n.checks { 699 if b := SimplifyValidator(ctx, x, y); b != nil { 700 n.checks[i] = b 701 return 702 } 703 } 704 kind := x.Kind() 705 n.updateNodeType(kind, x, id) 706 // A validator that is inserted in a closeContext should behave like top 707 // in the sense that the closeContext should not be closed if no other 708 // value is present that would erase top (cc.hasNonTop): if a field is 709 // only associated with a validator, we leave it to the validator to 710 // decide what fields are allowed. 711 if kind&(ListKind|StructKind) != 0 { 712 id.cc.hasTop = true 713 } 714 n.checks = append(n.checks, x) 715 716 case *Vertex: 717 // handled above. 718 719 case Value: // *NullLit, *BoolLit, *NumLit, *StringLit, *BytesLit, *Builtin 720 if y := n.scalar; y != nil { 721 if b, ok := BinOp(ctx, EqualOp, x, y).(*Bool); !ok || !b.B { 722 n.reportConflict(x, y, x.Kind(), y.Kind(), n.scalarID, id) 723 } 724 break 725 } 726 n.scalar = x 727 n.scalarID = id 728 n.signal(scalarKnown) 729 730 default: 731 panic(fmt.Sprintf("unknown value type %T", x)) 732 } 733 734 if n.lowerBound != nil && n.upperBound != nil { 735 if u := SimplifyBounds(ctx, n.kind, n.lowerBound, n.upperBound); u != nil { 736 if err := valueError(u); err != nil { 737 err.AddPosition(n.lowerBound) 738 err.AddPosition(n.upperBound) 739 err.AddClosedPositions(id) 740 } 741 n.lowerBound = nil 742 n.upperBound = nil 743 n.insertValueConjunct(env, u, id) 744 } 745 } 746 }