cuelang.org/go@v0.13.0/internal/core/adt/typocheck.go (about) 1 // Copyright 2025 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 // https://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 // This file holds CUE's algorithm to detect misspelled field names. 18 // 19 // ## Outline 20 // 21 // Typo checking MAY be enabled whenever a node is unified with a definition or 22 // a struct returned from the close builtin. Each distinct unification of such a 23 // struct triggers a separate "typo check", which is associated with a unique 24 // identifier. This identifier is passed along all values that result from 25 // unification with this struct. 26 // 27 // Once the processing of a node is complete, it is checked that for all its 28 // fields there is supportive evidence that the field is allowed for all structs 29 // for which typo checking is enabled. 30 // 31 // ## Selecting which Structs to Typo Check 32 // 33 // The algorithm is quite general and allows many types of heuristics to be 34 // applied. The initial goal is to mimic V2 semantics as closely as possible to 35 // facilitate migration to V3. 36 // 37 // ### Embeddings 38 // 39 // Embeddings provide evidence allowing fields for their enclosing scopes. Even 40 // when an embedding references a definition, it will not start its own typo 41 // check. 42 // 43 // ### Definitions 44 // 45 // By default, all references to non-embedded definitions are typo checked. The 46 // following situations qualify. 47 // 48 // #A: b: {a: int} 49 // a: #A 50 // 51 // // Trigger typo check 52 // r1: #A 53 // r2: a 54 // 55 // // Do NOT trigger typo check 56 // r3: a.b 57 // 58 // In the case of r3, no typo check is triggered as the inserted value does not 59 // reference a definition directly. This choice is somewhat arbitrary. The main 60 // reason to pick this semantics is to be compatible with V2. 61 // 62 // ### Inline structs 63 // 64 // Like in V2, inline structs are generally not typo checked. We can add this 65 // later if necessary. 66 // 67 // ## Tracking Evidence 68 // 69 // The basic principle of this algorithm is that each (dynamic) reference is 70 // associated with a unique identifier. When a node is unified with a definition 71 // all its descendant nodes are tagged with this number if any of the conjuncts 72 // of this definition end up being unified with such nodes. Once a node finished 73 // processing, it is checked, recursively, that all its fields adhere to this 74 // schema by checking that there is supportive evidence that this schema allows 75 // such fields. 76 // 77 // Consider, for instance, the following CUE. Here, the reference to #Schema 78 // will be assigned the unique identifier 7. 79 // 80 // foo: #Schema & { 81 // a: 1 // marked with 7 as #Schema has a field `a`. 82 // b: 1 // marked with 7 as the pattern of #Schema allows `b`. 83 // c: 1 // not marked with 7 as #Schema does not have a field `c`. 84 // } 85 // 86 // #Schema: { 87 // a?: int 88 // [<="b"]: int 89 // } 90 // 91 // Details of this algorithm are included below. 92 // 93 // ### Evidence sets and multiple insertions 94 // 95 // The same struct may be referred to within a node multiple times. If it is not 96 // itself a typo-checked definition, it may provide evidence for multiple other 97 // typo-checked definitions. To avoid having to process the conjuncts of such 98 // structs multiple times, we will still assign a unique identifier to such 99 // structs, and will annotate each typo-checked definition to which this applies 100 // that it is satisfied by this struct. 101 // 102 // In other words, each typo-checked struct may be associated with multiple 103 // unique identifiers that provide evidence for an allowed field. See the 104 // section below for a more detailed example. 105 // 106 // ### Embeddings 107 // 108 // Embeddings are a special case of structs that are not typo checked, but may 109 // provide evidence for other typo-checked definitions. As a struct associated 110 // with an embedding may be referred to multiple times, we will also assign a 111 // unique identifier to embeddings. This identifier is then also added to the 112 // evidence set of the typo-checked definition. 113 // 114 // Note that if a definition is embedded within a struct, that struct is 115 // considered closed after unifying all embeddings. We need to track this 116 // separately. This is discussed in the code below where appropriate. Ideally, 117 // we would get rid of this behavior by only allowing "open" or "closed" 118 // unifications, without considering any of the other context. 119 // 120 // Consider the following example: 121 // 122 // a: #A & { #A, b: int, c: int } 123 // #A: #B 124 // #B: { b: int } 125 // 126 // In this case, for `a`, we need find evidence that all fields of `#A` are 127 // allowed. If `a` were to be referenced by another field, we would also 128 // need to check that the struct with the embedded definition, which is 129 // closed after unification, is valid. 130 // 131 // So, for `a` we track two requirements, say `1`, which corresponds to the 132 // reference to `#A` and `2`, which corresponds to the literal struct. 133 // During evaluation, we additionally assign `3` to `#B`. 134 // 135 // The algorithm now proceeds as follows. We start with the requirement sets 136 // `{1}` and `{2}`. For the first, while evaluating definition `#A`, we find 137 // that this redirects to `#B`. In this case, as `#B` is at least as strict 138 // as `#A`, we can rewrite the set as `{3}`. This means we need to find 139 // evidence that each field of `a` is marked with a `3`. 140 // For the second case, `{2}`, we find that `#A` is embedded. As this is not 141 // a further restriction, we add the ID for `#A` to the set, resulting in 142 // `{2, 1}`. Subsequently, `#A` maps to `#B` again, but in this case, as `#A` 143 // is embedded, it is also additive, resulting in `{2, 1, 3}`, where each field 144 // in `a` needs to be marked with at least one of these values. 145 // 146 // After `a` is fully unified, field `b` will be marked with `3` and thus has 147 // evidence for both requirements. Field `c`, however, is marked with `2`, which 148 // is only supports the second requirement, and thus will result in a typo 149 // error. 150 // 151 // NOTE 1: that the second set is strictly more permissive then the first 152 // requirement and could be elided. 153 // NOTE 2: a reference to the same node within one field is only assigned a 154 // single ID and only needs to be processed once per node. 155 // 156 // ### Pruning of sub nodes 157 // 158 // For definitions, typo checking proceeds recursively. However, typo checking 159 // is disabled for certain fields, even when a node still has subfields. Typo 160 // checking is disabled if a field has: 161 // 162 // - a "naked" top `_` (so not, for instance `{_}`), - an ellipsis `...`, or - 163 // it has a "non-concrete" validator, like matchn. 164 // 165 // In the latter case, the builtin takes over the responsibility of checking the 166 // type. 167 // 168 import ( 169 "math" 170 "slices" 171 172 "cuelang.org/go/cue/ast" 173 ) 174 175 type defID uint32 176 177 const deleteID defID = math.MaxUint32 178 179 func (c *OpContext) getNextDefID() defID { 180 c.stats.NumCloseIDs++ 181 c.nextDefID++ 182 return c.nextDefID 183 } 184 185 type refInfo struct { 186 v *Vertex 187 id defID 188 189 // exclude defines a subtree of CloseInfo.def that should not be 190 // transitively added to the set of allowed fields. 191 // 192 // It would probably be more efficient to track a separate allow and deny 193 // list, rather than tracking the entire tree except for the excluded. 194 exclude defID 195 196 // ignore defines whether we should not do typo checking for this defID. 197 ignore bool 198 199 // isOuterStruct indicates that this struct is marked as "outerID" in a 200 // CloseInfo. The debug visualization in ./debug.go uses this to show a 201 // mark ("S") next to the defID to visualize that this fact. 202 isOuterStruct bool 203 } 204 205 type conjunctFlags uint8 206 207 const ( 208 cHasEllipsis conjunctFlags = 1 << iota 209 cHasTop 210 cHasStruct 211 cHasOpenValidator 212 ) 213 214 type conjunctInfo struct { 215 id defID 216 kind Kind 217 flags conjunctFlags 218 } 219 220 func (c conjunctFlags) hasTop() bool { 221 return c&(cHasTop) != 0 222 } 223 224 func (c conjunctFlags) hasStruct() bool { 225 return c&(cHasStruct) != 0 226 } 227 228 func (c conjunctFlags) forceOpen() bool { 229 return c&(cHasEllipsis|cHasOpenValidator) != 0 230 } 231 232 type replaceID struct { 233 from defID 234 to defID 235 add bool // If true, add to the set. If false, replace from with to. 236 } 237 238 func (n *nodeContext) addReplacement(x replaceID) { 239 if x.from == x.to { 240 return 241 } 242 // TODO: we currently may compute n.reqSets too early in some rare 243 // circumstances. We clear the set if it needs to be recomputed. 244 n.computedCloseInfo = false 245 n.reqSets = n.reqSets[:0] 246 247 n.replaceIDs = append(n.replaceIDs, x) 248 } 249 250 func (n *nodeContext) updateConjunctInfo(k Kind, id CloseInfo, flags conjunctFlags) { 251 if n.ctx.OpenDef { 252 return 253 } 254 255 for i, c := range n.conjunctInfo { 256 if c.id == id.defID { 257 n.conjunctInfo[i].kind &= k 258 n.conjunctInfo[i].flags |= flags 259 return 260 } 261 } 262 n.conjunctInfo = append(n.conjunctInfo, conjunctInfo{ 263 id: id.defID, 264 kind: k, 265 flags: flags, 266 }) 267 } 268 269 // addResolver adds a resolver to typo checking. Both definitions and 270 // non-definitions should typically be added: non-definitions may be added 271 // multiple times to a single node. As we only want to insert each conjunct 272 // once, we need to ensure that within all contexts a single ID assigned to such 273 // a resolver is tracked. 274 func (n *nodeContext) addResolver(v *Vertex, id CloseInfo, forceIgnore bool) CloseInfo { 275 if n.ctx.OpenDef { 276 return id 277 } 278 279 isClosed := id.FromDef || v.ClosedNonRecursive 280 281 if isClosed && !forceIgnore { 282 for i, x := range n.reqDefIDs { 283 if x.id == id.outerID && id.outerID != 0 { 284 n.reqDefIDs[i].ignore = false 285 break 286 } 287 } 288 } 289 290 var ignore bool 291 switch { 292 case forceIgnore: 293 // Special mode to always ignore the outer enclosing group. 294 // This is the case, for instance, if a resolver resolves to a 295 // non-definition. 296 ignore = true 297 // TODO: Consider resetting FromDef. 298 // id.FromDef = false 299 case id.enclosingEmbed != 0 || id.outerID == 0: 300 // We have a reference within an inner embedding group. If this is 301 // a definition, or otherwise typo checked struct, we need to track 302 // the embedding for mutual compatibility. 303 // is a definition: 304 // a: { 305 // // Even though #A and #B do not constraint `a` individually, 306 // // they need to be checked for mutual consistency within 307 // // the embedding. 308 // #A & #B 309 // } 310 ignore = !isClosed 311 default: 312 // In the default case we can disable typo checking this type if it is 313 // an embedding. 314 ignore = id.FromEmbed 315 } 316 317 srcID := id.defID 318 dstID := defID(0) 319 for _, x := range n.reqDefIDs { 320 if x.v == v { 321 dstID = x.id 322 break 323 } 324 } 325 326 if dstID == 0 || id.enclosingEmbed != 0 { 327 next := n.ctx.getNextDefID() 328 if dstID != 0 { 329 // If we need to activate an enclosing embed group, and the added 330 // resolver was already before, we need to allocate a new ID and 331 // add the original ID to the set of the new one. 332 n.addReplacement(replaceID{from: next, to: dstID, add: true}) 333 } 334 dstID = next 335 336 n.reqDefIDs = append(n.reqDefIDs, refInfo{ 337 v: v, 338 id: dstID, 339 exclude: id.enclosingEmbed, 340 ignore: ignore, 341 }) 342 } 343 id.defID = dstID 344 345 // TODO: consider using add: !isClosed 346 n.addReplacement(replaceID{from: srcID, to: dstID, add: true}) 347 if id.enclosingEmbed != 0 && !ignore { 348 ph := id.outerID 349 n.addReplacement(replaceID{from: dstID, to: ph, add: true}) 350 _, dstID = n.newGroup(id, false) 351 id.enclosingEmbed = dstID 352 } 353 354 return id 355 } 356 357 // subField updates a CloseInfo for subfields of a struct. 358 func (c *OpContext) subField(ci CloseInfo) CloseInfo { 359 ci.outerID = 0 360 ci.enclosingEmbed = 0 361 return ci 362 } 363 364 func (n *nodeContext) newGroup(id CloseInfo, placeholder bool) (CloseInfo, defID) { 365 srcID := id.defID 366 dstID := n.ctx.getNextDefID() 367 // TODO: consider only adding when record || OpenGraph 368 n.reqDefIDs = append(n.reqDefIDs, refInfo{ 369 v: emptyNode, 370 id: dstID, 371 ignore: true, 372 isOuterStruct: placeholder, 373 }) 374 id.defID = dstID 375 n.addReplacement(replaceID{from: srcID, to: dstID, add: true}) 376 return id, dstID 377 } 378 379 // AddOpenConjunct adds w as a conjunct of v and disables typo checking for w, 380 // even if it is a definition. 381 func (v *Vertex) AddOpenConjunct(ctx *OpContext, w *Vertex) { 382 n := v.getBareState(ctx) 383 ci := n.injectEmbedNode(w, CloseInfo{}) 384 c := MakeConjunct(nil, w, ci) 385 v.AddConjunct(c) 386 } 387 388 // injectEmbedNode is used to track typo checking within an embedding. 389 // Consider, for instance: 390 // 391 // #A: {a: int} 392 // #B: {b: int} 393 // #C: { 394 // #A & #B // fails 395 // c: int 396 // } 397 // 398 // In this case, even though #A and #B are both embedded, they are intended 399 // to be mutually exclusive. We track this by introducing a separate defID 400 // for the embedding. Suppose that the embedding #A&#B is assigned defID 2, 401 // where its parent is defID 1. Then #A is assigned 3 and #B is assigned 4. 402 // 403 // We can then say that requirement 3 (node A) holds if all fields contain 404 // either label 3, or any field within 1 that is not 2. 405 func (n *nodeContext) injectEmbedNode(x Decl, id CloseInfo) CloseInfo { 406 id.FromEmbed = true 407 408 // Filter cases where we do not need to track the definition. 409 switch x := x.(type) { 410 case *DisjunctionExpr, *Disjunction, *Comprehension: 411 return id 412 case *BinaryExpr: 413 if x.Op != AndOp { 414 return id 415 } 416 } 417 418 id, dstID := n.newGroup(id, false) 419 id.enclosingEmbed = dstID 420 421 return id 422 } 423 424 // splitStruct is used to mark the outer struct of a field in which embeddings 425 // occur. The significance is that a reference to this node expects a node 426 // to be closed, even if it only has embeddings. Consider for instance: 427 // 428 // // A is closed and allows the fields of #B plus c. 429 // A: { { #B }, c: int } 430 // 431 // TODO(flatclose): this is a temporary solution to handle the case where a 432 // definition is embedded within a struct. It can be removed if we implement 433 // the #A vs #A... semantics. 434 func (n *nodeContext) splitStruct(s *StructLit, id CloseInfo) CloseInfo { 435 if n.ctx.OpenDef { 436 return id 437 } 438 439 if _, ok := s.Src.(*ast.File); ok { 440 // If this is not a file, the struct indicates the scope/ 441 // boundary at which closedness should apply. This is not true 442 // for files. 443 // We should also not spawn if this is a nested Comprehension, 444 // where the spawn is already done as it may lead to spurious 445 // field not allowed errors. We can detect this with a nil s.Src. 446 // TODO(evalv3): use a more principled detection mechanism. 447 // TODO: set this as a flag in StructLit so as to not have to 448 // do the somewhat dangerous cast here. 449 return id 450 } 451 452 return n.splitScope(id) 453 } 454 455 func (n *nodeContext) splitScope(id CloseInfo) CloseInfo { 456 id, dstID := n.newGroup(id, true) 457 458 if id.outerID == 0 { 459 id.outerID = dstID 460 } 461 462 return id 463 } 464 465 func (n *nodeContext) checkTypos() { 466 ctx := n.ctx 467 if ctx.OpenDef { 468 return 469 } 470 noDeref := n.node // noDeref retained for debugging purposes. 471 v := noDeref.DerefValue() 472 473 // Stop early, avoiding the work in appendRequired below, if we have no arcs to check. 474 if len(v.Arcs) == 0 { 475 return 476 } 477 478 // Avoid unnecessary errors. 479 if b, ok := v.BaseValue.(*Bottom); ok && !b.CloseCheck { 480 return 481 } 482 483 required := getReqSets(n) 484 if len(required) == 0 { 485 return 486 } 487 488 // TODO(perf): reuse buffers via OpContext 489 requiredCopy := make(reqSets, 0, len(required)) 490 var replacements []replaceID 491 492 var err *Bottom 493 // outer: 494 for _, a := range v.Arcs { 495 f := a.Label 496 if a.IsFromDisjunction() { 497 continue // Already checked in disjuncts. 498 } 499 500 // TODO(mem): child states of uncompleted nodes must have a state. 501 na := a.state 502 503 replacements = na.getReplacements(replacements[:0]) 504 required := append(requiredCopy[:0], required...) 505 // do the right thing in appendRequired either way. 506 required.replaceIDs(n.ctx, replacements...) 507 508 a = a.DerefDisjunct() 509 // TODO(perf): somehow prevent error generation of recursive structures, 510 // or at least make it cheap. Right now if this field is a typo, likely 511 // all descendents will be regarded as typos. 512 if b, ok := a.BaseValue.(*Bottom); ok { 513 if !b.CloseCheck { 514 continue 515 } 516 } 517 518 if allowedInClosed(f) { 519 continue 520 } 521 522 if n.hasEvidenceForAll(required, na.conjunctInfo) { 523 continue 524 } 525 526 // TODO: do not descend on optional? 527 528 // openDebugGraph(ctx, a, "NOT ALLOWED") // Uncomment for debugging. 529 if b := ctx.notAllowedError(a); b != nil && a.ArcType <= ArcRequired { 530 err = CombineErrors(nil, err, b) 531 } 532 } 533 534 if err != nil { 535 n.AddChildError(err) // TODO: should not be necessary. 536 } 537 } 538 539 // hasEvidenceForAll reports whether there is evidence in a set of 540 // conjuncts for each of the typo-checked structs represented by 541 // the reqSets. 542 func (n *nodeContext) hasEvidenceForAll(a reqSets, conjuncts []conjunctInfo) bool { 543 for i := uint32(0); int(i) < len(a); i += a[i].size { 544 if a[i].size == 0 { 545 panic("unexpected set length") 546 } 547 if !hasEvidenceForOne(a[i:i+a[i].size], conjuncts) { 548 return false 549 } 550 } 551 return true 552 } 553 554 // hasEvidenceForOne reports whether a single typo-checked set has evidence for 555 // any of its defIDs. 556 func hasEvidenceForOne(a reqSets, conjuncts []conjunctInfo) bool { 557 for _, c := range conjuncts { 558 for _, ri := range a { 559 if c.id == ri.id { 560 return true 561 } 562 } 563 } 564 return false 565 } 566 567 // reqSets defines a set of sets of defIDs that can each satisfy a required id. 568 // 569 // A reqSet holds a sequence of a defID "representative", or "head" for a 570 // requirement, followed by all defIDs that satisfy this requirement. For head 571 // elements, size indicates the number of entries in the set, including the 572 // head. For non-head elements, size is 0. 573 type reqSets []reqSet 574 575 // A single reqID might be satisfied by multiple defIDs, if the definition 576 // associated with the reqID embeds other definitions, for instance. In this 577 // case we keep a list of defIDs that may also be satisfied. 578 // 579 // This type is used in [nodeContext.equivalences] as follows: 580 // - if an embedding is used by a definition, it is inserted in the list 581 // pointed to by its refInfo. Similarly, 582 // refInfo is added to the list 583 type reqSet struct { 584 id defID 585 // size is the number of elements in the set. This is only set for the head. 586 // Entries with equivalence IDs have size set to 0. 587 size uint32 588 del defID // TODO(flatclose): can be removed later. 589 once bool 590 } 591 592 // assert checks the invariants of a reqSets. It can be used for debugging. 593 func (a reqSets) assert() { 594 for i := 0; i < len(a); { 595 e := a[i] 596 if e.size == 0 { 597 panic("head element with 0 size") 598 } 599 if i+int(e.size) > len(a) { 600 panic("set extends beyond end of slice") 601 } 602 for j := 1; j < int(e.size); j++ { 603 if a[i+j].size != 0 { 604 panic("non-head element with non-zero size") 605 } 606 } 607 608 i += int(e.size) 609 } 610 } 611 612 // replaceIDs replaces defIDs mappings in the receiving reqSets in place. 613 // 614 // The following rules apply: 615 // 616 // - Mapping a typo-checked definition to a new typo-checked definition replaces 617 // the set from the "from" definition in its entirety. It is assumed that the 618 // set is already added to the requirements. 619 // 620 // - A mapping from a typo-checked definition to 0 removes the set from the 621 // requirements. This typically comes from _ or .... 622 // 623 // - A mapping from an equivalence (non-head) value to 0 removes the 624 // equivalence. This does not change the outcome of typo checking, but 625 // reduces the size of the equivalence list, which helps performance. 626 // 627 // - A mapping from an equivalence (non-head) value to a new value widens the 628 // allowed values for the respective set by adding the new value to the 629 // equivalence list. 630 // 631 // In words; 632 // - Definition: if not in embed, create new group 633 // - If in active definition, replace old definition 634 // - If in embed, replace embed in respective sets. definition starts new group 635 // - child definition replaces parent definition 636 func (a *reqSets) replaceIDs(ctx *OpContext, b ...replaceID) { 637 temp := *a 638 temp = temp[:0] 639 buf := ctx.reqSetsBuf[:0] 640 outer: 641 for i := 0; i < len(*a); { 642 e := (*a)[i] 643 if e.size != 0 { 644 // If the head is dropped, the entire group is deleted. 645 for _, x := range b { 646 if e.id == x.from && !x.add { 647 i += int(e.size) 648 continue outer 649 } 650 } 651 if len(buf) > 0 { 652 buf[0].size = uint32(len(buf)) 653 if len(temp)+len(buf) > i { 654 *a = slices.Replace(*a, len(temp), i, buf...) 655 i = len(temp) + len(buf) 656 temp = (*a)[:i] 657 } else { 658 temp = append(temp, buf...) 659 } 660 buf = buf[:0] 661 } 662 } 663 664 buf = transitiveMapping(ctx, buf, e, b) 665 666 i++ 667 } 668 if len(buf) > 0 { 669 buf[0].size = uint32(len(buf)) 670 temp = append(temp, buf...) 671 } 672 ctx.reqSetsBuf = buf[:0] // to be reused later on 673 *a = temp 674 } 675 676 // TODO: this is a polynomial algorithm. At some point, if this turns out to be 677 // a bottleneck, we should consider filtering unnecessary entries and/or using a 678 // more efficient algorithm. 679 func transitiveMapping(ctx *OpContext, buf reqSets, x reqSet, b []replaceID) reqSets { 680 // Trim subtree for embedded conjunctions. 681 if len(buf) > 0 && buf[0].del == x.id { 682 return buf 683 } 684 685 // do not add duplicates 686 for _, y := range buf { 687 if x.id == y.id { 688 return buf 689 } 690 } 691 692 buf = append(buf, x) 693 ctx.stats.CloseIDElems++ 694 695 for _, y := range b { 696 if x.id == y.from { 697 if y.to == deleteID { // do we need this? 698 buf = buf[:len(buf)-1] 699 return buf 700 } 701 buf = transitiveMapping(ctx, buf, reqSet{ 702 id: y.to, 703 once: x.once, 704 }, b) 705 } 706 } 707 return buf 708 } 709 710 // mergeCloseInfo merges the conjunctInfo of nw that is missing from nv into nv. 711 // 712 // This is used to merge conjunctions from a disjunct to the Vertex that 713 // originated the disjunct. 714 // TODO: consider whether we can do without. We usually aim to not check 715 // such nodes, but sometimes we do. 716 func mergeCloseInfo(nv, nw *nodeContext) { 717 v := nv.node 718 w := nw.node 719 if w == nil { 720 return 721 } 722 // Merge missing closeInfos 723 outer: 724 for _, wci := range nw.conjunctInfo { 725 for _, vci := range nv.conjunctInfo { 726 if wci.id == vci.id { 727 continue outer 728 } 729 } 730 nv.conjunctInfo = append(nv.conjunctInfo, wci) 731 } 732 733 outer2: 734 for _, d := range nw.replaceIDs { 735 for _, vd := range nv.replaceIDs { 736 if d == vd { 737 continue outer2 738 } 739 } 740 nv.replaceIDs = append(nv.replaceIDs, d) 741 } 742 743 for _, wa := range w.Arcs { 744 for _, va := range v.Arcs { 745 if va.Label == wa.Label { 746 mergeCloseInfo(va.state, wa.state) 747 break 748 } 749 } 750 } 751 } 752 753 func (n *nodeContext) getReplacements(a []replaceID) []replaceID { 754 for p := n.node; p != nil && p.state != nil; p = p.Parent { 755 a = append(a, p.state.replaceIDs...) 756 } 757 return a 758 } 759 760 // getReqSets initializes, if necessary, and returns the reqSets for n. 761 func getReqSets(n *nodeContext) reqSets { 762 if n == nil { 763 return nil 764 } 765 766 if n.computedCloseInfo { 767 return n.reqSets 768 } 769 n.reqSets = n.reqSets[:0] 770 n.computedCloseInfo = true 771 772 a := n.reqSets 773 v := n.node 774 775 if p := v.Parent; p != nil { 776 aReq := getReqSets(p.state) 777 if !n.dropParentRequirements { 778 a = append(a, aReq...) 779 } 780 } 781 a.filterNonRecursive() 782 783 outer: 784 for _, y := range n.reqDefIDs { 785 if y.ignore { 786 continue 787 } 788 for _, x := range a { 789 if x.id == y.id { 790 continue outer 791 } 792 } 793 once := false 794 if y.v != nil { 795 once = !y.v.ClosedRecursive 796 } 797 798 a = append(a, reqSet{ 799 id: y.id, 800 once: once, 801 del: y.exclude, 802 size: 1, 803 }) 804 } 805 806 a.replaceIDs(n.ctx, n.replaceIDs...) 807 808 // If 'v' is a hidden field, then all reqSets in 'a' for which there is no 809 // corresponding entry in conjunctInfo should be removed from 'a'. 810 if allowedInClosed(v.Label) { 811 a.filterSets(func(a []reqSet) bool { 812 for _, e := range a { 813 for _, c := range n.conjunctInfo { 814 if c.id == e.id { 815 return true // keep the set 816 } 817 } 818 } 819 return false // discard the set 820 }) 821 } 822 823 a.filterTop(n.conjunctInfo) 824 825 n.reqSets = a 826 return a 827 } 828 829 // If there is a top or ellipsis for all supported conjuncts, we have 830 // evidence that this node can be dropped. 831 func (a *reqSets) filterTop(conjuncts []conjunctInfo) { 832 a.filterSets(func(a []reqSet) bool { 833 var f conjunctFlags 834 for _, e := range a { 835 for _, c := range conjuncts { 836 if e.id != c.id { 837 continue 838 } 839 flags := c.flags 840 if c.id < a[0].id { 841 flags &^= cHasStruct 842 } 843 f |= flags 844 } 845 } 846 if (f.hasTop() && !f.hasStruct()) || f.forceOpen() { 847 return false 848 } 849 return true 850 }) 851 } 852 853 func (a *reqSets) filterNonRecursive() { 854 a.filterSets(func(e []reqSet) bool { 855 x := e[0] 856 if x.once { // || x.id == 0 857 return false // discard the entry 858 } 859 return true // keep the entry 860 }) 861 } 862 863 // filter keeps all reqSets e in a for which f(e) and removes the rest. 864 func (a *reqSets) filterSets(f func(e []reqSet) bool) { 865 temp := (*a)[:0] 866 for i := 0; i < len(*a); { 867 e := (*a)[i] 868 set := (*a)[i : i+int(e.size)] 869 870 if f(set) { 871 temp = append(temp, set...) 872 } 873 874 i += int(e.size) 875 } 876 *a = temp 877 }