cuelang.org/go@v0.13.0/internal/core/adt/share.go (about) 1 // Copyright 2024 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 contains logic regarding structure sharing. 18 19 // Notes 20 // 21 // TODO: 22 // We may want to consider tracking closedness in parallel to the Vertex 23 // structure, for instance in a CloseInfo or in a cue.Value itself. 24 // 25 // reg: {} 26 // #def: sub: reg 27 // 28 // By tracking closedness inside the CloseInfo, we can still share the 29 // structure and only have to change 30 // 31 // Maybe this is okay, though, as #Def itself can be shared, at least. 32 33 func (n *nodeContext) unshare() { 34 n.noSharing = true 35 36 if !n.isShared { 37 return 38 } 39 n.isShared = false 40 n.node.IsShared = false 41 42 v := n.node.BaseValue.(*Vertex) 43 44 // TODO: the use of cycle for BaseValue is getting increasingly outdated. 45 // Find another mechanism once we get rid of the old evaluator. 46 n.node.BaseValue = n.origBaseValue 47 48 for _, id := range n.sharedIDs { 49 n.scheduleVertexConjuncts(n.shared, v, id) 50 } 51 52 n.decSharedIDs() 53 } 54 55 // finalizeSharing should be called when it is known for sure a node can be 56 // shared. 57 func (n *nodeContext) finalizeSharing() { 58 n.decSharedIDs() 59 if !n.isShared { 60 return 61 } 62 switch v := n.node.BaseValue.(type) { 63 case *Vertex: 64 if n.shareCycleType == NoCycle { 65 v.Finalize(n.ctx) 66 } else if !v.isFinal() { 67 // TODO: ideally we just handle cycles in optional chains directly, 68 // rather than relying on this mechanism. This requires us to add 69 // a mechanism to detect that. 70 n.ctx.toFinalize = append(n.ctx.toFinalize, v) 71 } 72 // If state.parent is non-nil, we determined earlier that this Vertex 73 // is not rooted and that it can safely be shared. Because it is 74 // not-rooted, though, it will not have a path location, resulting in 75 // bad error messages, and in some cases dropped errors. To avoid this, 76 // we reset the parent and label of the Vertex so that its path reflects 77 // its assigned location. 78 if v.state != nil && v.state.parent != nil { 79 v.Parent = v.state.parent 80 81 // TODO: see if this can be removed and why some errors are not 82 // propagated when removed. 83 n.isShared = false 84 } 85 case *Bottom: 86 // An error trumps sharing. We can leave it as is. 87 default: 88 panic("unreachable") 89 } 90 } 91 92 func (n *nodeContext) addShared(id CloseInfo) { 93 if len(n.sharedIDs) == 0 || n.shareCycleType < id.CycleType { 94 n.shareCycleType = id.CycleType 95 } 96 97 // At this point, the node may still be unshared at a later point. For this 98 // purpose we need to keep the retain count above zero until all conjuncts 99 // have been processed and it is clear that sharing is possible. Delaying 100 // such a count should not hurt performance, as a shared node is completed 101 // anyway. 102 n.sharedIDs = append(n.sharedIDs, id) 103 } 104 105 func (n *nodeContext) decSharedIDs() { 106 if n.shareDecremented { 107 return 108 } 109 n.shareDecremented = true 110 for _, id := range n.sharedIDs { 111 n.updateConjunctInfo(n.node.Kind(), id, 0) 112 } 113 } 114 115 func (n *nodeContext) share(c Conjunct, arc *Vertex, id CloseInfo) { 116 if n.isShared { 117 panic("already sharing") 118 } 119 n.origBaseValue = n.node.BaseValue 120 n.node.BaseValue = arc 121 n.node.IsShared = true 122 n.isShared = true 123 n.shared = c 124 n.addShared(id) 125 126 if arc.IsDetached() && arc.MayAttach() { // TODO: Second check necessary? 127 // This node can safely be shared. Since it is not rooted, though, it 128 // does not have a path location. Instead of setting the parent path 129 // directly, though, we record the prospective parent in the state: as 130 // the evaluator uses the Parent field during evaluation, setting the 131 // field directly here can result in incorrect evaluation results. 132 // Setting the parent in the state instead allows us to defer setting 133 // Parent until it is safe to do so.. 134 if s := arc.getState(n.ctx); s != nil { 135 s.parent = n.node 136 } 137 } 138 } 139 140 func (n *nodeContext) shareIfPossible(c Conjunct, arc *Vertex, id CloseInfo) bool { 141 if !n.ctx.Sharing { 142 return false 143 } 144 145 // We disallow sharing for any Arcs, even pending ones, to be defensive. 146 // CUE currently does not always unwind sharing properly in the precense of 147 // pending arcs. See, for instance: 148 // 149 // a: X 150 // if true { 151 // a: b: c: e: 1 // ensure 'e' is added 152 // } 153 // X: b: Y 154 // Y: c: d: int 155 // 156 // TODO: allow sharing in the precense of pending or not present arcs. 157 if len(n.node.Arcs) > 0 { 158 return false 159 } 160 161 // See Issue #3801: structure sharing seems to be broken for non-rooted 162 // values. We disable sharing for now. 163 // TODO: make sharing work again for non-rooted structs. 164 if arc.nonRooted || arc.IsDynamic { 165 return false 166 } 167 168 // We do not allowing sharing if the conjunct has a cycle. Sharing is only 169 // possible if there is a single conjunct. We want to further evaluate this 170 // conjunct to force recognition of a structural cycle. 171 if id.CycleType == IsCyclic && (n.node.nonRooted || n.node.IsDynamic) { 172 return false 173 } 174 175 if n.noSharing || n.isShared || n.ctx.errs != nil { 176 return false 177 } 178 179 // This line is to deal with this case: 180 // 181 // reg: {} 182 // #def: sub: reg 183 // 184 // Ideally we find a different solution, like passing closedness 185 // down elsewhere. In fact, as we do this in closeContexts, it probably 186 // already works, it will just not be reflected in the debug output. 187 // We could fix that by not printing structure shared nodes, which is 188 // probably a good idea anyway. 189 // 190 // TODO: come up with a mechanism to allow this case. 191 if n.node.ClosedRecursive && !arc.ClosedRecursive { 192 return false 193 } 194 195 // Sharing let expressions is not supported and will result in unmarked 196 // structural cycles. Processing will still terminate, but printing the 197 // result will result in an infinite loop. 198 // 199 // TODO: allow this case. 200 if arc.Label.IsLet() { 201 return false 202 } 203 204 n.share(c, arc, id) 205 return true 206 } 207 208 // Vertex values that are held in BaseValue will be wrapped in the following 209 // order: 210 // 211 // disjuncts -> (shared | computed | data) 212 // 213 // DerefDisjunct 214 // - get the current value under computation 215 // 216 // DerefValue 217 // - get the value the node ultimately represents. 218 // 219 220 // DerefValue unrolls indirections of Vertex values. These may be introduced, 221 // for instance, by temporary bindings such as comprehension values. 222 // It returns v itself if v does not point to another Vertex. 223 func (v *Vertex) DerefValue() *Vertex { 224 for { 225 arc, ok := v.BaseValue.(*Vertex) 226 if !ok { 227 return v 228 } 229 v = arc 230 } 231 } 232 233 // DerefDisjunct indirects a node that points to a disjunction. 234 func (v *Vertex) DerefDisjunct() *Vertex { 235 for { 236 arc, ok := v.BaseValue.(*Vertex) 237 if !ok || !arc.IsDisjunct { 238 return v 239 } 240 v = arc 241 } 242 } 243 244 // DerefNonDisjunct indirects a node that points to a disjunction. 245 func (v *Vertex) DerefNonDisjunct() *Vertex { 246 for { 247 arc, ok := v.BaseValue.(*Vertex) 248 if !ok || arc.IsDisjunct { 249 return v 250 } 251 v = arc 252 } 253 } 254 255 // DerefNonRooted indirects a node that points to a value that is not rooted. 256 // This includes structure-shared nodes that point to a let field: let fields 257 // may or may not be part of a struct, and thus should be treated as non-rooted. 258 func (v *Vertex) DerefNonRooted() *Vertex { 259 for { 260 arc, ok := v.BaseValue.(*Vertex) 261 if !ok || arc.IsDisjunct || (v.IsShared && !arc.Label.IsLet()) { 262 return v 263 } 264 v = arc 265 } 266 } 267 268 // DerefNonShared finds the indirection of an arc that is not the result of 269 // structure sharing. This is especially relevant when indirecting disjunction 270 // values. 271 func (v *Vertex) DerefNonShared() *Vertex { 272 if v.state != nil && v.state.isShared { 273 return v 274 } 275 for { 276 arc, ok := v.BaseValue.(*Vertex) 277 if !ok { 278 return v 279 } 280 v = arc 281 } 282 }