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 }