cuelang.org/go@v0.13.0/internal/core/adt/closed.go (about) 1 // Copyright 2020 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 // This file implements the closedness algorithm. 18 19 // Outline of algorithm 20 // 21 // To compute closedness each Vertex is associated with a tree which has 22 // leaf nodes with sets of allowed labels, and interior nodes that describe 23 // how these sets may be combines: Or, for embedding, or And for definitions. 24 // 25 // Each conjunct of a Vertex is associated with such a leaf node. Each 26 // conjunct that evaluates to a struct is added to the list of Structs, which 27 // in the end forms this tree. If a conjunct is embedded, or references another 28 // struct or definition, it adds interior node to reflect this. 29 // 30 // To test whether a feature is allowed, it must satisfy the resulting 31 // expression tree. 32 // 33 // In order to avoid having to copy the tree for each node, the tree is linked 34 // from leaf node to root, rather than the other way around. This allows 35 // parent nodes to be shared as the tree grows and ensures that the growth 36 // of the tree is bounded by the number of conjuncts. As a consequence, this 37 // requires a two-pass algorithm: 38 // 39 // - walk up to mark which nodes are required and count the number of 40 // child nodes that need to be satisfied. 41 // - verify fields in leaf structs and mark parent leafs as satisfied 42 // when appropriate. 43 // 44 // A label is allowed if all required root nodes are marked as accepted after 45 // these two passes. 46 // 47 48 // A note on embeddings: it is important to keep track which conjuncts originate 49 // from an embedding, as an embedded value may eventually turn into a closed 50 // struct. Consider 51 // 52 // a: { 53 // b 54 // d: e: int 55 // } 56 // b: d: { 57 // #A & #B 58 // } 59 // 60 // At the point of evaluating `a`, the struct is not yet closed. However, 61 // descending into `d` will trigger the inclusion of definitions which in turn 62 // causes the struct to be closed. At this point, it is important to know that 63 // `b` originated from an embedding, as otherwise `e` may not be allowed. 64 65 // TODO(perf): 66 // - less nodes 67 // - disable StructInfo nodes that can no longer pass a feature 68 // - sort StructInfos active ones first. 69 70 // TODO(errors): return a dedicated ConflictError that can track original 71 // positions on demand. 72 73 // IsInOneOf reports whether any of the Structs associated with v is contained 74 // within any of the span types in the given mask. 75 func (v *Vertex) IsInOneOf(mask SpanType) bool { 76 for _, s := range v.Structs { 77 if s.CloseInfo.IsInOneOf(mask) { 78 return true 79 } 80 } 81 return false 82 } 83 84 // IsRecursivelyClosed returns true if this value is either a definition or unified 85 // with a definition. 86 func (v *Vertex) IsRecursivelyClosed() bool { 87 return v.ClosedRecursive || v.IsInOneOf(DefinitionSpan) 88 } 89 90 type closeNodeType uint8 91 92 const ( 93 // a closeRef node is created when there is a non-definition reference. 94 closeRef closeNodeType = iota 95 96 // closeDef indicates this node was introduced as a result of referencing 97 // a definition. 98 closeDef 99 100 // closeEmbed indicates this node was added as a result of an embedding. 101 closeEmbed 102 ) 103 104 // TODO: merge with closeInfo: this is a leftover of the refactoring. 105 type CloseInfo struct { 106 *closeInfo // old implementation (TODO: remove) 107 // defID is a unique ID to track anything that gets inserted from this 108 // Conjunct. 109 defID defID 110 enclosingEmbed defID // Tracks an embedding within a struct. 111 outerID defID // Tracks the {} that should be closed after unifying. 112 113 // IsClosed is true if this conjunct represents a single level of closing 114 // as indicated by the closed builtin. 115 IsClosed bool 116 117 // FromEmbed indicates whether this conjunct was inserted because of an 118 // embedding. This flag is sticky: it will be set for conjuncts created 119 // from fields defined by this conjunct. 120 // NOTE: only used when using closeContext. 121 FromEmbed bool 122 123 // FromDef indicates whether this conjunct was inserted because of a 124 // definition. This flag is sticky: it will be set for conjuncts created 125 // from fields defined by this conjunct. 126 // NOTE: only used when using closeContext. 127 FromDef bool 128 129 // Like FromDef, but used by APIs to force FromDef to be true. 130 TopDef bool 131 132 // FieldTypes indicates which kinds of fields (optional, dynamic, patterns, 133 // etc.) are contained in this conjunct. 134 FieldTypes OptionalType 135 136 CycleInfo 137 } 138 139 func (c CloseInfo) Location() Node { 140 if c.closeInfo == nil { 141 return nil 142 } 143 return c.closeInfo.location 144 } 145 146 func (c CloseInfo) span() SpanType { 147 if c.closeInfo == nil { 148 return 0 149 } 150 return c.closeInfo.span 151 } 152 153 func (c CloseInfo) RootSpanType() SpanType { 154 if c.closeInfo == nil { 155 return 0 156 } 157 return c.root 158 } 159 160 // IsInOneOf reports whether c is contained within any of the span types in the 161 // given mask. 162 func (c CloseInfo) IsInOneOf(t SpanType) bool { 163 return c.span()&t != 0 164 } 165 166 // TODO(perf): remove: error positions should always be computed on demand 167 // in dedicated error types. 168 func (c *CloseInfo) AddPositions(ctx *OpContext) { 169 for s := c.closeInfo; s != nil; s = s.parent { 170 if loc := s.location; loc != nil { 171 ctx.AddPosition(loc) 172 } 173 } 174 } 175 176 // TODO(perf): use on StructInfo. Then if parent and expression are the same 177 // it is possible to use cached value. 178 func (c CloseInfo) SpawnEmbed(x Node) CloseInfo { 179 c.closeInfo = &closeInfo{ 180 parent: c.closeInfo, 181 location: x, 182 mode: closeEmbed, 183 root: EmbeddingSpan, 184 span: c.span() | EmbeddingSpan, 185 } 186 return c 187 } 188 189 // SpawnGroup is used for structs that contain embeddings that may end up 190 // closing the struct. This is to force that `b` is not allowed in 191 // 192 // a: {#foo} & {b: int} 193 func (c CloseInfo) SpawnGroup(x Expr) CloseInfo { 194 c.closeInfo = &closeInfo{ 195 parent: c.closeInfo, 196 location: x, 197 span: c.span(), 198 } 199 return c 200 } 201 202 // SpawnSpan is used to track that a value is introduced by a comprehension 203 // or constraint. Definition and embedding spans are introduced with SpawnRef 204 // and SpawnEmbed, respectively. 205 func (c CloseInfo) SpawnSpan(x Node, t SpanType) CloseInfo { 206 c.closeInfo = &closeInfo{ 207 parent: c.closeInfo, 208 location: x, 209 root: t, 210 span: c.span() | t, 211 } 212 return c 213 } 214 215 func (c CloseInfo) SpawnRef(arc *Vertex, isDef bool, x Expr) CloseInfo { 216 span := c.span() 217 found := false 218 if !isDef { 219 xnode := Node(x) // Optimization so we're comparing identical interface types. 220 // TODO: make this work for non-definitions too. 221 for p := c.closeInfo; p != nil; p = p.parent { 222 if p.span == span && p.location == xnode { 223 found = true 224 break 225 } 226 } 227 } 228 if !found { 229 c.closeInfo = &closeInfo{ 230 parent: c.closeInfo, 231 location: x, 232 span: span, 233 } 234 } 235 if isDef { 236 c.mode = closeDef 237 c.closeInfo.root = DefinitionSpan 238 c.closeInfo.span |= DefinitionSpan 239 } 240 return c 241 } 242 243 // IsDef reports whether an expressions is a reference that references a 244 // definition anywhere in its selection path. 245 // 246 // TODO(performance): this should be merged with resolve(). But for now keeping 247 // this code isolated makes it easier to see what it is for. 248 func IsDef(x Expr) (isDef bool, depth int) { 249 switch r := x.(type) { 250 case *FieldReference: 251 isDef = r.Label.IsDef() 252 253 case *SelectorExpr: 254 isDef, depth = IsDef(r.X) 255 depth++ 256 if r.Sel.IsDef() { 257 isDef = true 258 } 259 260 case *IndexExpr: 261 isDef, depth = IsDef(r.X) 262 depth++ 263 } 264 return isDef, depth 265 } 266 267 // A SpanType is used to indicate whether a CUE value is within the scope of 268 // a certain CUE language construct, the span type. 269 type SpanType uint8 270 271 const ( 272 // EmbeddingSpan means that this value was embedded at some point and should 273 // not be included as a possible root node in the todo field of OpContext. 274 EmbeddingSpan SpanType = 1 << iota 275 ConstraintSpan 276 ComprehensionSpan 277 DefinitionSpan 278 ) 279 280 type closeInfo struct { 281 // location records the expression that led to this node's introduction. 282 location Node 283 284 // The parent node in the tree. 285 parent *closeInfo 286 287 // TODO(performance): if references are chained, we could have a separate 288 // parent pointer to skip the chain. 289 290 // mode indicates whether this node was added as part of an embedding, 291 // definition or non-definition reference. 292 mode closeNodeType 293 294 // noCheck means this struct is irrelevant for closedness checking. This can 295 // happen when: 296 // - it is a sibling of a new definition. 297 noCheck bool // don't process for inclusion info 298 299 root SpanType 300 span SpanType 301 } 302 303 // closeStats holds the administrative fields for a closeInfo value. Each 304 // closeInfo is associated with a single closeStats value per unification 305 // operator. This association is done through an OpContext. This allows the 306 // same value to be used in multiple concurrent unification operations. 307 // NOTE: there are other parts of the algorithm that are not thread-safe yet. 308 type closeStats struct { 309 // the other fields of this closeStats value are only valid if generation 310 // is equal to the generation in OpContext. This allows for lazy 311 // initialization of closeStats. 312 generation int 313 314 // These counts keep track of how many required child nodes need to be 315 // completed before this node is accepted. 316 requiredCount int 317 acceptedCount int 318 319 // accepted is set if this node is accepted. 320 accepted bool 321 322 required bool 323 324 inTodoList bool // true if added to todo list. 325 next *closeStats 326 } 327 328 func (c *closeInfo) isClosed() bool { 329 return c.mode == closeDef 330 } 331 332 // isClosed reports whether v is closed at this level (so not recursively). 333 func isClosed(v *Vertex) bool { 334 // We could have used IsRecursivelyClosed here, but (effectively) 335 // implementing it again here allows us to only have to iterate over 336 // Structs once. 337 if v.ClosedRecursive || v.ClosedNonRecursive { 338 return true 339 } 340 // TODO(evalv3): this can be removed once we delete the evalv2 code. 341 for _, s := range v.Structs { 342 if s.IsClosed || s.IsInOneOf(DefinitionSpan) { 343 return true 344 } 345 } 346 return false 347 } 348 349 // Accept determines whether f is allowed in n. It uses the OpContext for 350 // caching administrative fields. 351 func Accept(ctx *OpContext, n *Vertex, f Feature) (found, required bool) { 352 if ctx.isDevVersion() { 353 return n.accept(ctx, f), true 354 } 355 ctx.generation++ 356 ctx.todo = nil 357 358 var optionalTypes OptionalType 359 360 // TODO(perf): more aggressively determine whether a struct is open or 361 // closed: open structs do not have to be checked, yet they can particularly 362 // be the ones with performance issues, for instanced as a result of 363 // embedded for comprehensions. 364 for _, s := range n.Structs { 365 if !s.useForAccept() { 366 continue 367 } 368 markCounts(ctx, s.CloseInfo) 369 optionalTypes |= s.types 370 } 371 372 var str Value 373 if f.Index() == MaxIndex { 374 f &= fTypeMask 375 } else if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() { 376 str = f.ToValue(ctx) 377 } 378 379 for _, s := range n.Structs { 380 if !s.useForAccept() { 381 continue 382 } 383 if verifyArc(ctx, s, f, str) { 384 // Beware: don't add to below expression: this relies on the 385 // side effects of markUp. 386 ok := markUp(ctx, s.closeInfo, 0) 387 found = found || ok 388 } 389 } 390 391 // Reject if any of the roots is not accepted. 392 for x := ctx.todo; x != nil; x = x.next { 393 if !x.accepted { 394 return false, true 395 } 396 } 397 398 return found, ctx.todo != nil 399 } 400 401 func markCounts(ctx *OpContext, info CloseInfo) { 402 if info.IsClosed { 403 markRequired(ctx, info.closeInfo) 404 return 405 } 406 for s := info.closeInfo; s != nil; s = s.parent { 407 if s.isClosed() { 408 markRequired(ctx, s) 409 return 410 } 411 } 412 } 413 414 func markRequired(ctx *OpContext, info *closeInfo) { 415 count := 0 416 for ; ; info = info.parent { 417 var s closeInfo 418 if info != nil { 419 s = *info 420 } 421 422 x := getScratch(ctx, info) 423 424 x.requiredCount += count 425 426 if x.required { 427 return 428 } 429 430 if s.span&EmbeddingSpan == 0 && !x.inTodoList { 431 x.next = ctx.todo 432 ctx.todo = x 433 x.inTodoList = true 434 } 435 436 x.required = true 437 438 if info == nil { 439 return 440 } 441 442 count = 0 443 if s.mode != closeEmbed { 444 count = 1 445 } 446 } 447 } 448 449 func markUp(ctx *OpContext, info *closeInfo, count int) bool { 450 for ; ; info = info.parent { 451 var s closeInfo 452 if info != nil { 453 s = *info 454 } 455 456 x := getScratch(ctx, info) 457 458 x.acceptedCount += count 459 460 if x.acceptedCount < x.requiredCount { 461 return false 462 } 463 464 x.accepted = true 465 466 if info == nil { 467 return true 468 } 469 470 count = 0 471 if x.required && s.mode != closeEmbed { 472 count = 1 473 } 474 } 475 } 476 477 // getScratch: explain generation. 478 func getScratch(ctx *OpContext, s *closeInfo) *closeStats { 479 m := ctx.closed 480 if m == nil { 481 m = map[*closeInfo]*closeStats{} 482 ctx.closed = m 483 } 484 485 x := m[s] 486 if x == nil { 487 x = &closeStats{} 488 m[s] = x 489 } 490 491 if x.generation != ctx.generation { 492 *x = closeStats{generation: ctx.generation} 493 } 494 495 return x 496 } 497 498 func verifyArc(ctx *OpContext, s *StructInfo, f Feature, label Value) bool { 499 isRegular := f.IsString() 500 501 o := s.StructLit 502 env := s.Env 503 504 if len(o.Additional) > 0 || o.IsOpen { 505 return true 506 } 507 508 for _, g := range o.Fields { 509 if f == g.Label { 510 return true 511 } 512 } 513 514 if !isRegular { 515 return false 516 } 517 518 // Do not record errors during this validation. 519 errs := ctx.errs 520 defer func() { ctx.errs = errs }() 521 522 if len(o.Dynamic) > 0 && f.IsString() && label != nil { 523 for _, b := range o.Dynamic { 524 v := env.evalCached(ctx, b.Key) 525 v, _ = ctx.getDefault(v) 526 s, ok := Unwrap(v).(*String) 527 if !ok { 528 continue 529 } 530 if label.(*String).Str == s.Str { 531 return true 532 } 533 } 534 } 535 536 for _, b := range o.Bulk { 537 if matchBulk(ctx, env, b, f, label) { 538 return true 539 } 540 } 541 542 // TODO(perf): delay adding this position: create a special error type that 543 // computes all necessary positions on demand. 544 if ctx != nil { 545 ctx.AddPosition(s.StructLit) 546 } 547 548 return false 549 }