cuelang.org/go@v0.10.1/internal/core/adt/comprehension.go (about) 1 // Copyright 2021 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 // Comprehension algorithm 18 // 19 // Comprehensions are expanded for, if, and let clauses that yield 0 or more 20 // structs to be embedded in the enclosing list or struct. 21 // 22 // CUE allows cascading of insertions, as in: 23 // 24 // a?: int 25 // b?: int 26 // if a != _|_ { 27 // b: 2 28 // } 29 // if b != _|_ { 30 // c: 3 31 // d: 4 32 // } 33 // 34 // even though CUE does not allow the result of a comprehension to depend 35 // on another comprehension within a single struct. The way this works is that 36 // for fields with a fixed prefix path in a comprehension value, the 37 // comprehension is assigned to these respective fields. 38 // 39 // More concretely, the above example is rewritten to: 40 // 41 // a?: int 42 // b: if a != _|_ { 2 } 43 // c: if b != _|_ { 3 } 44 // d: if b != _|_ { 4 } 45 // 46 // where the fields with if clause are only inserted if their condition 47 // resolves to true. (Note that this is not valid CUE; it may be in the future.) 48 // 49 // With this rewrite, any dependencies in comprehension expressions will follow 50 // the same rules, more or less, as with normal evaluation. 51 // 52 // Note that a single comprehension may be distributed across multiple fields. 53 // The evaluator will ensure, however, that a comprehension is only evaluated 54 // once. 55 // 56 // 57 // Closedness 58 // 59 // The comprehension algorithm uses the usual closedness mechanism for marking 60 // fields that belong to a struct: it adds the StructLit associated with the 61 // comprehension value to the respective arc. 62 // 63 // One noteworthy point is that the fields of a struct are only legitimate for 64 // actual results. For instance, if an if clause evaluates to false, the 65 // value is not embedded. 66 // 67 // To account for this, the comprehension algorithm relies on the fact that 68 // the closedness information is computed as a separate step. So even if 69 // the StructLit is added early, its fields will only count once it is 70 // initialized, which is only done when at least one result is added. 71 // 72 73 // envComprehension caches the result of a single comprehension. 74 type envComprehension struct { 75 comp *Comprehension 76 vertex *Vertex // The Vertex from which the comprehension originates. 77 78 // runtime-related fields 79 80 err *Bottom 81 82 // envs holds all the environments that define a single "yield" result in 83 // combination with the comprehension struct. 84 envs []*Environment // nil: unprocessed, non-nil: done. 85 done bool // true once the comprehension has been evaluated 86 87 // StructLits to Init (activate for closedness check) 88 // when at least one value is yielded. 89 structs []*StructLit 90 } 91 92 // envYield defines a comprehension for a specific field within a comprehension 93 // value. Multiple envYields can be associated with a single envComprehension. 94 // An envComprehension only needs to be evaluated once for multiple envYields. 95 type envYield struct { 96 *envComprehension // The original comprehension. 97 leaf *Comprehension // The leaf Comprehension 98 99 // Values specific to the field corresponding to this envYield 100 101 // This envYield was added to selfComprehensions 102 self bool 103 // This envYield was successfully executed and the resulting conjuncts were 104 // added. 105 inserted bool 106 107 env *Environment // The adjusted Environment. 108 id CloseInfo // CloseInfo for the field. 109 expr Node // The adjusted expression. 110 } 111 112 // ValueClause represents a wrapper Environment in a chained clause list 113 // to account for the unwrapped struct. It is never created by the compiler 114 // and serves as a dynamic element only. 115 type ValueClause struct { 116 Node 117 118 // The node in which to resolve lookups in the comprehension's value struct. 119 arc *Vertex 120 } 121 122 func (v *ValueClause) yield(s *compState) { 123 s.yield(s.ctx.spawn(v.arc)) 124 } 125 126 // insertComprehension registers a comprehension with a node, possibly pushing 127 // down its evaluation to the node's children. It will only evaluate one level 128 // of fields at a time. 129 func (n *nodeContext) insertComprehension( 130 env *Environment, 131 c *Comprehension, 132 ci CloseInfo, 133 ) { 134 // TODO(perf): this implementation causes the parent's clauses 135 // to be evaluated for each nested comprehension. It would be 136 // possible to simply store the envComprehension of the parent's 137 // result and have each subcomprehension reuse those. This would 138 // also avoid the below allocation and would probably allow us 139 // to get rid of the ValueClause type. 140 141 ec := c.comp 142 if ec == nil { 143 ec = &envComprehension{ 144 comp: c, 145 vertex: n.node, 146 147 err: nil, // shut up linter 148 envs: nil, // shut up linter 149 done: false, // shut up linter 150 } 151 } 152 153 if ec.done && len(ec.envs) == 0 { 154 n.decComprehension(c) 155 return 156 } 157 158 x := c.Value 159 160 if !n.ctx.isDevVersion() { 161 ci = ci.SpawnEmbed(c) 162 ci.closeInfo.span |= ComprehensionSpan 163 } 164 165 var decls []Decl 166 switch v := ToExpr(x).(type) { 167 case *StructLit: 168 numFixed := 0 169 var fields []Decl 170 for _, d := range v.Decls { 171 switch f := d.(type) { 172 case *Field: 173 numFixed++ 174 175 // Create partial comprehension 176 c := &Comprehension{ 177 Syntax: c.Syntax, 178 Clauses: c.Clauses, 179 Value: f, 180 arcType: f.ArcType, // TODO: can be derived, remove this field. 181 cc: ci.cc, 182 183 comp: ec, 184 parent: c, 185 arc: n.node, 186 } 187 188 conjunct := MakeConjunct(env, c, ci) 189 if n.ctx.isDevVersion() { 190 n.assertInitialized() 191 _, c.arcCC = n.insertArcCC(f.Label, ArcPending, conjunct, conjunct.CloseInfo, false) 192 c.cc = ci.cc 193 ci.cc.incDependent(n.ctx, COMP, c.arcCC) 194 } else { 195 n.insertFieldUnchecked(f.Label, ArcPending, conjunct) 196 } 197 198 fields = append(fields, f) 199 200 case *LetField: 201 // TODO: consider merging this case with the LetField case. 202 203 numFixed++ 204 205 // Create partial comprehension 206 c := &Comprehension{ 207 Syntax: c.Syntax, 208 Clauses: c.Clauses, 209 Value: f, 210 211 comp: ec, 212 parent: c, 213 arc: n.node, 214 } 215 216 conjunct := MakeConjunct(env, c, ci) 217 n.assertInitialized() 218 arc := n.insertFieldUnchecked(f.Label, ArcMember, conjunct) 219 arc.MultiLet = f.IsMulti 220 221 fields = append(fields, f) 222 223 default: 224 decls = append(decls, d) 225 } 226 } 227 228 if len(fields) > 0 { 229 // Create a stripped struct that only includes fixed fields. 230 // TODO(perf): this StructLit may be inserted more than once in 231 // the same vertex: once taking the StructLit of the referred node 232 // and once for inserting the Conjunct of the original node. 233 // Is this necessary (given closedness rules), and is this posing 234 // a performance problem? 235 st := v 236 if len(fields) < len(v.Decls) { 237 st = &StructLit{ 238 Src: v.Src, 239 Decls: fields, 240 } 241 } 242 n.node.AddStruct(st, env, ci) 243 switch { 244 case !ec.done: 245 ec.structs = append(ec.structs, st) 246 case len(ec.envs) > 0: 247 st.Init(n.ctx) 248 } 249 } 250 251 switch numFixed { 252 case 0: 253 // Add comprehension as is. 254 255 case len(v.Decls): 256 // No comprehension to add at this level. 257 return 258 259 default: 260 // Create a new StructLit with only the fields that need to be 261 // added at this level. 262 x = &StructLit{Decls: decls} 263 } 264 } 265 266 if n.ctx.isDevVersion() { 267 t := n.scheduleTask(handleComprehension, env, x, ci) 268 t.comp = ec 269 t.leaf = c 270 } else { 271 n.comprehensions = append(n.comprehensions, envYield{ 272 envComprehension: ec, 273 leaf: c, 274 env: env, 275 id: ci, 276 expr: x, 277 }) 278 } 279 } 280 281 type compState struct { 282 ctx *OpContext 283 comp *Comprehension 284 i int 285 f YieldFunc 286 state vertexStatus 287 } 288 289 // yield evaluates a Comprehension within the given Environment and calls 290 // f for each result. 291 func (c *OpContext) yield( 292 node *Vertex, // errors are associated with this node 293 env *Environment, // env for field for which this yield is called 294 comp *Comprehension, 295 state combinedFlags, 296 f YieldFunc, // called for every result 297 ) *Bottom { 298 s := &compState{ 299 ctx: c, 300 comp: comp, 301 f: f, 302 state: state.vertexStatus(), 303 } 304 y := comp.Clauses[0] 305 306 saved := c.PushState(env, y.Source()) 307 if node != nil { 308 defer c.PopArc(c.PushArc(node)) 309 } 310 311 s.i++ 312 y.yield(s) 313 s.i-- 314 315 return c.PopState(saved) 316 } 317 318 func (s *compState) yield(env *Environment) (ok bool) { 319 c := s.ctx 320 if s.i >= len(s.comp.Clauses) { 321 s.f(env) 322 return true 323 } 324 dst := s.comp.Clauses[s.i] 325 saved := c.PushState(env, dst.Source()) 326 327 s.i++ 328 dst.yield(s) 329 s.i-- 330 331 if b := c.PopState(saved); b != nil { 332 c.AddBottom(b) 333 return false 334 } 335 return !c.HasErr() 336 } 337 338 // injectComprehension evaluates and inserts embeddings. It first evaluates all 339 // embeddings before inserting the results to ensure that the order of 340 // evaluation does not matter. 341 func (n *nodeContext) injectComprehensions(state vertexStatus) (progress bool) { 342 unreachableForDev(n.ctx) 343 344 workRemaining := false 345 346 // We use variables, instead of range, as the list may grow dynamically. 347 for i := 0; i < len(n.comprehensions); i++ { 348 d := &n.comprehensions[i] 349 if d.self || d.inserted { 350 continue 351 } 352 if err := n.processComprehension(d, state); err != nil { 353 // TODO: Detect that the nodes are actually equal 354 if err.ForCycle && err.Value == n.node { 355 n.selfComprehensions = append(n.selfComprehensions, *d) 356 progress = true 357 d.self = true 358 return 359 } 360 361 d.err = err 362 workRemaining = true 363 364 continue 365 366 // TODO: add this when it can be done without breaking other 367 // things. 368 // 369 // // Add comprehension to ensure incomplete error is inserted. 370 // // This ensures that the error is reported in the Vertex 371 // // where the comprehension was defined, and not just in the 372 // // node below. This, in turn, is necessary to support 373 // // certain logic, like export, that expects to be able to 374 // // detect an "incomplete" error at the first level where it 375 // // is necessary. 376 // n := d.node.getNodeContext(ctx) 377 // n.addBottom(err) 378 379 } 380 progress = true 381 } 382 383 if !workRemaining { 384 n.comprehensions = n.comprehensions[:0] // Signal that all work is done. 385 } 386 387 return progress 388 } 389 390 // injectSelfComprehensions processes comprehensions that were earlier marked 391 // as iterating over the node in which they are defined. Such comprehensions 392 // are legal as long as they do not modify the arc set of the node. 393 func (n *nodeContext) injectSelfComprehensions(state vertexStatus) { 394 unreachableForDev(n.ctx) 395 396 // We use variables, instead of range, as the list may grow dynamically. 397 for i := 0; i < len(n.selfComprehensions); i++ { 398 n.processComprehension(&n.selfComprehensions[i], state) 399 } 400 n.selfComprehensions = n.selfComprehensions[:0] // Signal that all work is done. 401 } 402 403 // processComprehension processes a single Comprehension conjunct. 404 // It returns an incomplete error if there was one. Fatal errors are 405 // processed as a "successfully" completed computation. 406 func (n *nodeContext) processComprehension(d *envYield, state vertexStatus) *Bottom { 407 err := n.processComprehensionInner(d, state) 408 409 // NOTE: we cannot move this to defer in processComprehensionInner, as we 410 // use panics to implement "yielding" (and possibly coroutines in the 411 // future). 412 n.decComprehension(d.leaf) 413 414 return err 415 } 416 417 func (n *nodeContext) decComprehension(p *Comprehension) { 418 for ; p != nil; p = p.parent { 419 cc := p.cc 420 if cc != nil { 421 cc.decDependent(n.ctx, COMP, p.arcCC) 422 } 423 p.cc = nil 424 } 425 } 426 427 func (n *nodeContext) processComprehensionInner(d *envYield, state vertexStatus) *Bottom { 428 ctx := n.ctx 429 430 // Compute environments, if needed. 431 if !d.done { 432 var envs []*Environment 433 f := func(env *Environment) { 434 envs = append(envs, env) 435 } 436 437 if err := ctx.yield(d.vertex, d.env, d.comp, oldOnly(state), f); err != nil { 438 if err.IsIncomplete() { 439 return err 440 } 441 442 // continue to collect other errors. 443 d.done = true 444 d.inserted = true 445 if d.vertex != nil { 446 d.vertex.state.addBottom(err) 447 ctx.PopArc(d.vertex) 448 } 449 return nil 450 } 451 452 d.envs = envs 453 454 if len(d.envs) > 0 { 455 for _, s := range d.structs { 456 s.Init(n.ctx) 457 } 458 } 459 d.structs = nil 460 d.done = true 461 } 462 463 d.inserted = true 464 465 if len(d.envs) == 0 { 466 c := d.leaf 467 for p := c.arcCC; p != nil; p = p.parent { 468 // because the parent referrer will reach a zero count before this 469 // node will reach a zero count, we need to propagate the arcType. 470 p.updateArcType(ArcNotPresent) 471 } 472 return nil 473 } 474 475 v := n.node 476 f := v.Label 477 for c := d.leaf; c.parent != nil; c = c.parent { 478 // because the parent referrer will reach a zero count before this 479 // node will reach a zero count, we need to propagate the arcType. 480 for arc, p := c.arcCC, c.cc; p != nil; arc, p = arc.parent, p.parent { 481 // TODO: remove this line once we use the arcType of the 482 // closeContext in notAllowedError. 483 arc.src.updateArcType(c.arcType) 484 t := arc.arcType 485 arc.updateArcType(c.arcType) 486 if p.isClosed && t >= ArcPending && !matchPattern(ctx, p.Expr, f) { 487 ctx.notAllowedError(p.src, arc.src) 488 } 489 } 490 v.updateArcType(c.arcType) 491 if v.ArcType == ArcNotPresent { 492 parent := v.Parent 493 b := parent.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), v.Label) 494 d.envComprehension.vertex.state.addBottom(b) 495 ctx.current().err = b 496 ctx.current().state = taskFAILED 497 return nil 498 } 499 v = c.arc 500 } 501 502 id := d.id 503 504 for _, env := range d.envs { 505 if n.node.ArcType == ArcNotPresent { 506 b := n.node.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), n.node.Label) 507 ctx.current().err = b 508 n.yield() 509 return nil 510 } 511 512 env = linkChildren(env, d.leaf) 513 514 if ctx.isDevVersion() { 515 n.scheduleConjunct(Conjunct{env, d.expr, id}, id) 516 } else { 517 n.addExprConjunct(Conjunct{env, d.expr, id}, state) 518 } 519 } 520 521 return nil 522 } 523 524 // linkChildren adds environments for the chain of vertices to a result 525 // environment. 526 func linkChildren(env *Environment, c *Comprehension) *Environment { 527 if c.parent != nil { 528 env = linkChildren(env, c.parent) 529 env = spawn(env, c.arc) 530 } 531 return env 532 }