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  }