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  }