cuelang.org/go@v0.13.0/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 } 148 149 x := c.Value 150 151 if !n.ctx.isDevVersion() { 152 ci = ci.SpawnEmbed(c) 153 ci.closeInfo.span |= ComprehensionSpan 154 } 155 156 node := n.node.DerefDisjunct() 157 158 var decls []Decl 159 switch v := ToExpr(x).(type) { 160 case *StructLit: 161 kind := TopKind 162 numFixed := 0 163 var fields []Decl 164 for _, d := range v.Decls { 165 switch f := d.(type) { 166 case *Field: 167 numFixed++ 168 169 if f.Label.IsInt() { 170 kind &= ListKind 171 } else if f.Label.IsString() { 172 kind &= StructKind 173 } 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 182 comp: ec, 183 parent: c, 184 arc: node, 185 } 186 187 conjunct := MakeConjunct(env, c, ci) 188 if n.ctx.isDevVersion() { 189 n.assertInitialized() 190 n.insertArc(f.Label, ArcPending, conjunct, conjunct.CloseInfo, false) 191 } else { 192 n.insertFieldUnchecked(f.Label, ArcPending, conjunct) 193 } 194 195 fields = append(fields, f) 196 197 case *LetField: 198 // TODO: consider merging this case with the LetField case. 199 200 numFixed++ 201 202 // Create partial comprehension 203 c := &Comprehension{ 204 Syntax: c.Syntax, 205 Clauses: c.Clauses, 206 Value: f, 207 208 comp: ec, 209 parent: c, 210 arc: node, 211 } 212 213 conjunct := MakeConjunct(env, c, ci) 214 n.assertInitialized() 215 arc := n.insertFieldUnchecked(f.Label, ArcMember, conjunct) 216 if n.ctx.isDevVersion() { 217 arc.MultiLet = true 218 } else { 219 arc.MultiLet = f.IsMulti 220 } 221 222 fields = append(fields, f) 223 224 default: 225 decls = append(decls, d) 226 } 227 } 228 229 if len(fields) > 0 { 230 // Create a stripped struct that only includes fixed fields. 231 // TODO(perf): this StructLit may be inserted more than once in 232 // the same vertex: once taking the StructLit of the referred node 233 // and once for inserting the Conjunct of the original node. 234 // Is this necessary (given closedness rules), and is this posing 235 // a performance problem? 236 st := v 237 if len(fields) < len(v.Decls) { 238 st = &StructLit{ 239 Src: v.Src, 240 Decls: fields, 241 } 242 } 243 node.AddStruct(st, env, ci) 244 switch { 245 case !ec.done: 246 ec.structs = append(ec.structs, st) 247 case len(ec.envs) > 0: 248 st.Init(n.ctx) 249 if kind == StructKind || kind == ListKind { 250 n.updateNodeType(kind, st, ci) 251 } 252 } 253 } 254 255 c.kind = kind 256 257 switch numFixed { 258 case 0: 259 // Add comprehension as is. 260 261 case len(v.Decls): 262 // No comprehension to add at this level. 263 // The should be considered a struct if it has only non-regular 264 // fields (like definitions), and no embeddings. 265 if kind == TopKind { 266 c.kind = StructKind 267 } 268 return 269 270 default: 271 // Create a new StructLit with only the fields that need to be 272 // added at this level. 273 x = &StructLit{Decls: decls} 274 } 275 } 276 277 if n.ctx.isDevVersion() { 278 t := n.scheduleTask(handleComprehension, env, x, ci) 279 t.comp = ec 280 t.leaf = c 281 } else { 282 n.comprehensions = append(n.comprehensions, envYield{ 283 envComprehension: ec, 284 leaf: c, 285 env: env, 286 id: ci, 287 expr: x, 288 }) 289 } 290 } 291 292 type compState struct { 293 ctx *OpContext 294 comp *Comprehension 295 i int 296 f YieldFunc 297 state vertexStatus 298 } 299 300 // yield evaluates a Comprehension within the given Environment and calls 301 // f for each result. 302 func (c *OpContext) yield( 303 node *Vertex, // errors are associated with this node 304 env *Environment, // env for field for which this yield is called 305 comp *Comprehension, 306 state combinedFlags, 307 f YieldFunc, // called for every result 308 ) *Bottom { 309 s := &compState{ 310 ctx: c, 311 comp: comp, 312 f: f, 313 state: state.vertexStatus(), 314 } 315 y := comp.Clauses[0] 316 317 saved := c.PushState(env, y.Source()) 318 if node != nil { 319 defer c.PopArc(c.PushArc(node)) 320 } 321 322 s.i++ 323 y.yield(s) 324 s.i-- 325 326 return c.PopState(saved) 327 } 328 329 func (s *compState) yield(env *Environment) (ok bool) { 330 c := s.ctx 331 if s.i >= len(s.comp.Clauses) { 332 s.f(env) 333 return true 334 } 335 dst := s.comp.Clauses[s.i] 336 saved := c.PushState(env, dst.Source()) 337 338 s.i++ 339 dst.yield(s) 340 s.i-- 341 342 if b := c.PopState(saved); b != nil { 343 c.AddBottom(b) 344 return false 345 } 346 return !c.HasErr() 347 } 348 349 // injectComprehension evaluates and inserts embeddings. It first evaluates all 350 // embeddings before inserting the results to ensure that the order of 351 // evaluation does not matter. 352 func (n *nodeContext) injectComprehensions(state vertexStatus) (progress bool) { 353 unreachableForDev(n.ctx) 354 355 workRemaining := false 356 357 // We use variables, instead of range, as the list may grow dynamically. 358 for i := 0; i < len(n.comprehensions); i++ { 359 d := &n.comprehensions[i] 360 if d.self || d.inserted { 361 continue 362 } 363 if err := n.processComprehension(d, state); err != nil { 364 // TODO: Detect that the nodes are actually equal 365 if err.ForCycle && err.Value == n.node { 366 n.selfComprehensions = append(n.selfComprehensions, *d) 367 progress = true 368 d.self = true 369 return 370 } 371 372 d.err = err 373 workRemaining = true 374 375 continue 376 377 // TODO: add this when it can be done without breaking other 378 // things. 379 // 380 // // Add comprehension to ensure incomplete error is inserted. 381 // // This ensures that the error is reported in the Vertex 382 // // where the comprehension was defined, and not just in the 383 // // node below. This, in turn, is necessary to support 384 // // certain logic, like export, that expects to be able to 385 // // detect an "incomplete" error at the first level where it 386 // // is necessary. 387 // n := d.node.getNodeContext(ctx) 388 // n.addBottom(err) 389 390 } 391 progress = true 392 } 393 394 if !workRemaining { 395 n.comprehensions = n.comprehensions[:0] // Signal that all work is done. 396 } 397 398 return progress 399 } 400 401 // injectSelfComprehensions processes comprehensions that were earlier marked 402 // as iterating over the node in which they are defined. Such comprehensions 403 // are legal as long as they do not modify the arc set of the node. 404 func (n *nodeContext) injectSelfComprehensions(state vertexStatus) { 405 unreachableForDev(n.ctx) 406 407 // We use variables, instead of range, as the list may grow dynamically. 408 for i := 0; i < len(n.selfComprehensions); i++ { 409 n.processComprehension(&n.selfComprehensions[i], state) 410 } 411 n.selfComprehensions = n.selfComprehensions[:0] // Signal that all work is done. 412 } 413 414 // processComprehension processes a single Comprehension conjunct. 415 // It returns an incomplete error if there was one. Fatal errors are 416 // processed as a "successfully" completed computation. 417 func (n *nodeContext) processComprehension(d *envYield, state vertexStatus) *Bottom { 418 ctx := n.ctx 419 420 // Compute environments, if needed. 421 if !d.done { 422 var envs []*Environment 423 f := func(env *Environment) { 424 envs = append(envs, env) 425 } 426 427 if err := ctx.yield(d.vertex, d.env, d.comp, oldOnly(state), f); err != nil { 428 if err.IsIncomplete() { 429 return err 430 } 431 432 // continue to collect other errors. 433 d.done = true 434 d.inserted = true 435 if d.vertex != nil { 436 d.vertex.state.addBottom(err) 437 ctx.PopArc(d.vertex) 438 } 439 return nil 440 } 441 442 d.envs = envs 443 444 if len(d.envs) > 0 { 445 for _, s := range d.structs { 446 s.Init(n.ctx) 447 } 448 } 449 d.structs = nil 450 d.done = true 451 } 452 453 d.inserted = true 454 455 if len(d.envs) == 0 { 456 n.node.updateArcType(ArcNotPresent) 457 return nil 458 } 459 460 v := n.node 461 for c := d.leaf; c.parent != nil; c = c.parent { 462 v = n.ctx.deref(v) 463 v.updateArcType(c.arcType) 464 if v.ArcType == ArcNotPresent { 465 parent := v.Parent 466 b := parent.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), v.Label) 467 d.envComprehension.vertex.state.addBottom(b) 468 ctx.current().err = b 469 ctx.current().state = taskFAILED 470 return nil 471 } 472 if k := c.kind; k == StructKind || k == ListKind { 473 v := v.DerefDisjunct() 474 if s := v.getBareState(n.ctx); s != nil { 475 s.updateNodeType(k, ToExpr(c.Value), d.id) 476 } 477 } 478 v = c.arc 479 } 480 481 id := d.id 482 // TODO: should we treat comprehension values as optional? 483 // It seems so, but it causes some hangs. 484 // id.setOptional(nil) 485 486 for _, env := range d.envs { 487 if n.node.ArcType == ArcNotPresent { 488 b := n.node.reportFieldCycleError(ctx, d.comp.Syntax.Pos(), n.node.Label) 489 ctx.current().err = b 490 n.yield() 491 return nil 492 } 493 494 env = linkChildren(env, d.leaf) 495 496 if ctx.isDevVersion() { 497 n.scheduleConjunct(Conjunct{env, d.expr, id}, id) 498 } else { 499 n.addExprConjunct(Conjunct{env, d.expr, id}, state) 500 } 501 } 502 503 return nil 504 } 505 506 // linkChildren adds environments for the chain of vertices to a result 507 // environment. 508 func linkChildren(env *Environment, c *Comprehension) *Environment { 509 if c.parent != nil { 510 env = linkChildren(env, c.parent) 511 env = spawn(env, c.arc) 512 } 513 return env 514 }