cuelang.org/go@v0.13.0/internal/core/adt/constraints.go (about)

     1  // Copyright 2023 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 contains functionality for pattern constraints.
    18  
    19  // Constraints keeps track of pattern constraints and the set of allowed
    20  // fields.
    21  type Constraints struct {
    22  	// Pairs lists Pattern-Constraint pairs.
    23  	Pairs []PatternConstraint // TODO(perf): move to Arcs?
    24  
    25  	// Allowed is a Value that defines the set of all allowed fields.
    26  	// To check if a field is allowed, its correpsonding CUE value can be
    27  	// unified with this value.
    28  	Allowed Value
    29  }
    30  
    31  // A PatternConstraint represents a single
    32  //
    33  //	[pattern]: T.
    34  //
    35  // The Vertex holds a list of conjuncts to represent the constraints. We use
    36  // a Vertex so that these can be evaluated and compared for equality.
    37  // Unlike for regular Vertex values, CloseInfo.closeContext is set for
    38  // constraints: it is needed when matching subfields to ensure that conjuncts
    39  // get inserted into the proper groups.
    40  type PatternConstraint struct {
    41  	Pattern    Value
    42  	Constraint *Vertex
    43  }
    44  
    45  // insertListEllipsis inserts the given list ellipsis as a pattern constraint on
    46  // n, applying it to all elements at indexes >= offset.
    47  func (n *nodeContext) insertListEllipsis(offset int, ellipsis Conjunct) {
    48  	ctx := n.ctx
    49  
    50  	var p Value
    51  	if offset == 0 {
    52  		p = &BasicType{
    53  			Src: ellipsis.Field().Source(),
    54  			K:   IntKind,
    55  		}
    56  	} else {
    57  		p = &BoundValue{
    58  			Src:   nil, // TODO: field source.
    59  			Op:    GreaterEqualOp,
    60  			Value: ctx.NewInt64(int64(offset)),
    61  		}
    62  	}
    63  	n.insertConstraint(p, ellipsis)
    64  }
    65  
    66  // insertConstraint ensures a given pattern constraint is present in the
    67  // constraints of n and reports whether the pair was added newly.
    68  //
    69  // The given conjunct must have a closeContext associated with it. This ensures
    70  // that different pattern constraints pairs originating from the same
    71  // closeContext will be collated properly in fields to which these constraints
    72  // are applied.
    73  func (n *nodeContext) insertConstraint(pattern Value, c Conjunct) bool {
    74  	ctx := n.ctx
    75  	v := n.node
    76  
    77  	pcs := v.PatternConstraints
    78  	if pcs == nil {
    79  		pcs = &Constraints{}
    80  		v.PatternConstraints = pcs
    81  	}
    82  
    83  	var constraint *Vertex
    84  	for _, pc := range pcs.Pairs {
    85  		if Equal(ctx, pc.Pattern, pattern, 0) {
    86  			constraint = pc.Constraint
    87  			break
    88  		}
    89  	}
    90  
    91  	if constraint == nil {
    92  		constraint = &Vertex{
    93  			// See "Self-referencing patterns" in cycle.go
    94  			IsPatternConstraint: true,
    95  		}
    96  		pcs.Pairs = append(pcs.Pairs, PatternConstraint{
    97  			Pattern:    pattern,
    98  			Constraint: constraint,
    99  		})
   100  	} else {
   101  		found := false
   102  		constraint.VisitLeafConjuncts(func(x Conjunct) bool {
   103  			if x.x == c.x && x.Env.Up == c.Env.Up && x.Env.Vertex == c.Env.Vertex {
   104  				src := x.CloseInfo.defID
   105  				dst := c.CloseInfo.defID
   106  				found = true
   107  				n.addReplacement(replaceID{from: dst, to: src, add: true})
   108  				return false
   109  			}
   110  			return true
   111  		})
   112  		// The constraint already existed and the conjunct was already added.
   113  		if found {
   114  			return false
   115  		}
   116  	}
   117  
   118  	constraint.addConjunctUnchecked(c)
   119  	return true
   120  }
   121  
   122  // matchPattern reports whether f matches pattern. The result reflects
   123  // whether unification of pattern with f converted to a CUE value succeeds.
   124  // The caller should check separately whether f matches any other arcs
   125  // that are not covered by pattern.
   126  func matchPattern(ctx *OpContext, pattern Value, f Feature) bool {
   127  	if pattern == nil || !f.IsRegular() {
   128  		return false
   129  	}
   130  
   131  	// TODO(perf): this assumes that comparing an int64 against apd.Decimal
   132  	// is faster than converting this to a Num and using that for comparison.
   133  	// This may very well not be the case. But it definitely will be if we
   134  	// special-case integers that can fit in an int64 (or int32 if we want to
   135  	// avoid many bound checks), which we probably should. Especially when we
   136  	// allow list constraints, like [<10]: T.
   137  	var label Value
   138  	if f.IsString() && int64(f.Index()) != MaxIndex {
   139  		label = f.ToValue(ctx)
   140  	}
   141  
   142  	return matchPatternValue(ctx, pattern, f, label)
   143  }
   144  
   145  // matchPatternValue matches a concrete value against f. label must be the
   146  // CUE value that is obtained from converting f.
   147  //
   148  // This is an optimization an intended to be faster than regular CUE evaluation
   149  // for the majority of cases where pattern constraints are used.
   150  func matchPatternValue(ctx *OpContext, pattern Value, f Feature, label Value) (result bool) {
   151  	if v, ok := pattern.(*Vertex); ok {
   152  		v.unify(ctx, scalarKnown, finalize, false)
   153  	}
   154  	pattern = Unwrap(pattern)
   155  	label = Unwrap(label)
   156  
   157  	if pattern == label {
   158  		return true
   159  	}
   160  
   161  	k := IntKind
   162  	if f.IsString() {
   163  		k = StringKind
   164  	}
   165  	if !k.IsAnyOf(pattern.Kind()) {
   166  		return false
   167  	}
   168  
   169  	// Fast track for the majority of cases.
   170  	switch x := pattern.(type) {
   171  	case *Bottom:
   172  		// TODO: hoist and reuse with the identical code in optional.go.
   173  		if x == cycle {
   174  			err := ctx.NewPosf(pos(pattern), "cyclic pattern constraint")
   175  			ctx.vertex.VisitLeafConjuncts(func(c Conjunct) bool {
   176  				addPositions(err, c)
   177  				return true
   178  			})
   179  			ctx.AddBottom(&Bottom{
   180  				Err:  err,
   181  				Node: ctx.vertex,
   182  			})
   183  		}
   184  		if ctx.errs == nil {
   185  			ctx.AddBottom(x)
   186  		}
   187  		return false
   188  
   189  	case *Top:
   190  		return true
   191  
   192  	case *BasicType:
   193  		return x.K&k == k
   194  
   195  	case *BoundValue:
   196  		switch x.Kind() {
   197  		case StringKind:
   198  			if label == nil {
   199  				return false
   200  			}
   201  			str := label.(*String).Str
   202  			return x.validateStr(ctx, str)
   203  
   204  		case NumberKind:
   205  			return x.validateInt(ctx, int64(f.Index()))
   206  		}
   207  
   208  	case *Num:
   209  		if !f.IsInt() {
   210  			return false
   211  		}
   212  		yi := int64(f.Index())
   213  		xi, err := x.X.Int64()
   214  		return err == nil && xi == yi
   215  
   216  	case *String:
   217  		if label == nil {
   218  			return false
   219  		}
   220  		y, ok := label.(*String)
   221  		return ok && x.Str == y.Str
   222  
   223  	case *Conjunction:
   224  		for _, a := range x.Values {
   225  			if !matchPatternValue(ctx, a, f, label) {
   226  				return false
   227  			}
   228  		}
   229  		return true
   230  
   231  	case *Disjunction:
   232  		for _, a := range x.Values {
   233  			if matchPatternValue(ctx, a, f, label) {
   234  				return true
   235  			}
   236  		}
   237  		return false
   238  	}
   239  
   240  	// Slow track.
   241  	//
   242  	// TODO(perf): if a pattern tree has many values that are not handled in the
   243  	// fast track, it is probably more efficient to handle everything in the
   244  	// slow track. One way to signal this would be to have a "value thunk" at
   245  	// the root that causes the fast track to be bypassed altogether.
   246  
   247  	if label == nil {
   248  		label = f.ToValue(ctx)
   249  	}
   250  
   251  	n := ctx.newInlineVertex(nil, nil,
   252  		MakeConjunct(ctx.e, pattern, ctx.ci),
   253  		MakeConjunct(ctx.e, label, ctx.ci))
   254  	n.Finalize(ctx)
   255  	return n.Err(ctx) == nil
   256  }