cuelang.org/go@v0.10.1/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 if c.CloseInfo.cc == nil { 75 panic("constraint conjunct must have closeContext associated with it") 76 } 77 78 ctx := n.ctx 79 v := n.node 80 81 pcs := v.PatternConstraints 82 if pcs == nil { 83 pcs = &Constraints{} 84 v.PatternConstraints = pcs 85 } 86 87 var constraint *Vertex 88 for _, pc := range pcs.Pairs { 89 if Equal(ctx, pc.Pattern, pattern, 0) { 90 constraint = pc.Constraint 91 break 92 } 93 } 94 95 if constraint == nil { 96 constraint = &Vertex{} 97 pcs.Pairs = append(pcs.Pairs, PatternConstraint{ 98 Pattern: pattern, 99 Constraint: constraint, 100 }) 101 } else if constraint.hasConjunct(c) { 102 // The constraint already existed and the conjunct was already added. 103 return false 104 } 105 106 constraint.addConjunctUnchecked(c) 107 return true 108 } 109 110 // matchPattern reports whether f matches pattern. The result reflects 111 // whether unification of pattern with f converted to a CUE value succeeds. 112 func matchPattern(ctx *OpContext, pattern Value, f Feature) bool { 113 if pattern == nil || !f.IsRegular() { 114 return false 115 } 116 117 // TODO(perf): this assumes that comparing an int64 against apd.Decimal 118 // is faster than converting this to a Num and using that for comparison. 119 // This may very well not be the case. But it definitely will be if we 120 // special-case integers that can fit in an int64 (or int32 if we want to 121 // avoid many bound checks), which we probably should. Especially when we 122 // allow list constraints, like [<10]: T. 123 var label Value 124 if f.IsString() && int64(f.Index()) != MaxIndex { 125 label = f.ToValue(ctx) 126 } 127 128 return matchPatternValue(ctx, pattern, f, label) 129 } 130 131 // matchPatternValue matches a concrete value against f. label must be the 132 // CUE value that is obtained from converting f. 133 // 134 // This is an optimization an intended to be faster than regular CUE evaluation 135 // for the majority of cases where pattern constraints are used. 136 func matchPatternValue(ctx *OpContext, pattern Value, f Feature, label Value) (result bool) { 137 pattern = Unwrap(pattern) 138 label = Unwrap(label) 139 140 if pattern == label { 141 return true 142 } 143 144 k := IntKind 145 if f.IsString() { 146 k = StringKind 147 } 148 if !k.IsAnyOf(pattern.Kind()) { 149 return false 150 } 151 152 // Fast track for the majority of cases. 153 switch x := pattern.(type) { 154 case *Bottom: 155 // TODO: hoist and reuse with the identical code in optional.go. 156 if x == cycle { 157 err := ctx.NewPosf(pos(pattern), "cyclic pattern constraint") 158 for _, c := range ctx.vertex.Conjuncts { 159 addPositions(err, c) 160 } 161 ctx.AddBottom(&Bottom{ 162 Err: err, 163 }) 164 } 165 if ctx.errs == nil { 166 ctx.AddBottom(x) 167 } 168 return false 169 170 case *Top: 171 return true 172 173 case *BasicType: 174 return x.K&k == k 175 176 case *BoundValue: 177 switch x.Kind() { 178 case StringKind: 179 if label == nil { 180 return false 181 } 182 str := label.(*String).Str 183 return x.validateStr(ctx, str) 184 185 case NumberKind: 186 return x.validateInt(ctx, int64(f.Index())) 187 } 188 189 case *Num: 190 if !f.IsInt() { 191 return false 192 } 193 yi := int64(f.Index()) 194 xi, err := x.X.Int64() 195 return err == nil && xi == yi 196 197 case *String: 198 if label == nil { 199 return false 200 } 201 y, ok := label.(*String) 202 return ok && x.Str == y.Str 203 204 case *Conjunction: 205 for _, a := range x.Values { 206 if !matchPatternValue(ctx, a, f, label) { 207 return false 208 } 209 } 210 return true 211 212 case *Disjunction: 213 for _, a := range x.Values { 214 if matchPatternValue(ctx, a, f, label) { 215 return true 216 } 217 } 218 return false 219 } 220 221 // Slow track. 222 // 223 // TODO(perf): if a pattern tree has many values that are not handled in the 224 // fast track, it is probably more efficient to handle everything in the 225 // slow track. One way to signal this would be to have a "value thunk" at 226 // the root that causes the fast track to be bypassed altogether. 227 228 if label == nil { 229 label = f.ToValue(ctx) 230 } 231 232 n := ctx.newInlineVertex(nil, nil, 233 MakeConjunct(ctx.e, pattern, ctx.ci), 234 MakeConjunct(ctx.e, label, ctx.ci)) 235 n.Finalize(ctx) 236 return n.Err(ctx) == nil 237 }