this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue: support iteration over pattern constraints

This makes it possible to inspect pattern constraints over struct
values. We add a new option, `Patterns`, which causes the iterator
returned by `Value.Fields` to produce pattern constraints as well as
other fields.

We introduce a new selector type, `patternSelector` which represents
the pattern, and add `Selector.Pattern` to return the actual pattern
value. We don't need to add a new selector type because the type used
by `AnyString` (`StringLabel | PatternConstraint`) already seems
appropriate, and `AnyString` is never produced by field iteration.

We don't add functionality for looking up by a pattern constraint
selector because it's not entirely clear what the semantics should be
(what does it mean for one noncrete value to equal another?)

Note: it would be nicer if `AnyString.Pattern` could return `_` but
that's unfortunately not possible because there's no way for it to
obtain a context. All the alternatives (for example adding a selector
type that only _indicates_ that it's a pattern constraint rather than
containing the pattern value) seem worse. A future API where `Value`
is not bound so tightly to a given `Context` would address this
concern (and many others besides).

Fixes #3684

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I826f8ba1aef6ceedbf0d058b25c6184682da88c3
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1224468
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>

+236 -64
+2 -14
cue/context.go
··· 373 373 ctx := c.ctx() 374 374 // TODO: is true the right default? 375 375 expr := convert.GoValueToValue(ctx, x, options.nilIsTop) 376 - var n *adt.Vertex 377 - if v, ok := expr.(*adt.Vertex); ok { 378 - n = v 379 - } else { 380 - n = &adt.Vertex{} 381 - n.AddConjunct(adt.MakeRootConjunct(nil, expr)) 382 - } 376 + n := exprToVertex(expr) 383 377 n.Finalize(ctx) 384 378 return c.make(n) 385 379 } ··· 399 393 if err != nil { 400 394 return c.makeError(err) 401 395 } 402 - var n *adt.Vertex 403 - if v, ok := expr.(*adt.Vertex); ok { 404 - n = v 405 - } else { 406 - n = &adt.Vertex{} 407 - n.AddConjunct(adt.MakeRootConjunct(nil, expr)) 408 - } 396 + n := exprToVertex(expr) 409 397 n.Finalize(ctx) 410 398 return c.make(n) 411 399 }
+38
cue/path.go
··· 28 28 "cuelang.org/go/internal" 29 29 "cuelang.org/go/internal/astinternal" 30 30 "cuelang.org/go/internal/core/adt" 31 + "cuelang.org/go/internal/core/runtime" 31 32 "github.com/cockroachdb/apd/v3" 32 33 ) 33 34 ··· 137 138 // String reports the CUE representation of a selector. 138 139 func (sel Selector) String() string { 139 140 return sel.sel.String() 141 + } 142 + 143 + // ErrNotAPattern is a sentinel error value indicating that a value is not a 144 + // pattern, which may be returned by [Selector.Pattern]. 145 + var ErrNotAPattern = newErrValue( 146 + Value{idx: runtime.New()}, 147 + &adt.Bottom{ 148 + Err: errors.Newf(token.NoPos, "selector is not a pattern"), 149 + Code: adt.EvalError, 150 + }, 151 + ) 152 + 153 + // Pattern returns the label pattern for a pattern constraint selector 154 + // returned by an iterator with the [Patterns] option enabled. 155 + // 156 + // For other selectors, it returns [ErrNotAPattern]. 157 + func (sel Selector) Pattern() Value { 158 + switch sel := sel.sel.(type) { 159 + case patternSelector: 160 + return sel.pattern 161 + } 162 + return ErrNotAPattern 140 163 } 141 164 142 165 // Unquoted returns the unquoted value of a string label. ··· 605 628 return adt.Feature(s) 606 629 } 607 630 631 + type patternSelector struct { 632 + pattern Value 633 + _labelType SelectorType 634 + } 635 + 636 + func (s patternSelector) String() string { return fmt.Sprintf("[%#v]", s.pattern) } 637 + func (s patternSelector) isConstraint() bool { return true } 638 + func (s patternSelector) labelType() SelectorType { return s._labelType } 639 + func (s patternSelector) constraintType() SelectorType { return PatternConstraint } 640 + func (s patternSelector) feature(r adt.Runtime) adt.Feature { 641 + // Only called for non-pattern selectors. 642 + panic("unreachable") 643 + } 644 + 608 645 // TODO: allow import paths to be represented? 609 646 // 610 647 // // ImportPath defines a lookup at the root of an instance. It must be the first ··· 613 650 // func ImportPath(s string) Selector { 614 651 // return importSelector(s) 615 652 // } 653 + 616 654 type constraintSelector struct { 617 655 selector 618 656 constraint SelectorType
+11
cue/query.go
··· 15 15 package cue 16 16 17 17 import ( 18 + "cuelang.org/go/cue/errors" 19 + "cuelang.org/go/cue/token" 18 20 "cuelang.org/go/internal/core/adt" 19 21 ) 20 22 ··· 49 51 50 52 outer: 51 53 for _, sel := range p.path { 54 + if _, ok := sel.sel.(patternSelector); ok { 55 + // It's not possible to look up pattern constraints. 56 + // TODO: could potentially relax that restriction. 57 + err := errors.Newf( 58 + token.NoPos, 59 + "cannot look up pattern constraints other than AnyString or AnyIndex", 60 + ) 61 + return newErrValue(makeValue(v.idx, n, parent), &adt.Bottom{Err: err}) 62 + } 52 63 f := sel.sel.feature(v.idx) 53 64 deref := n.DerefValue() 54 65 for _, a := range deref.Arcs {
+125 -35
cue/types.go
··· 90 90 // 91 91 // TODO: remove 92 92 type structValue struct { 93 - ctx *adt.OpContext 94 - v Value 95 - obj *adt.Vertex 96 - arcs []*adt.Vertex 93 + ctx *adt.OpContext 94 + v Value 95 + obj *adt.Vertex 96 + arcs []*adt.Vertex 97 + patterns []adt.PatternConstraint 97 98 } 98 99 99 100 type hiddenStructValue = structValue ··· 212 213 213 214 // An Iterator iterates over values. 214 215 type Iterator struct { 215 - val Value 216 - idx *runtime.Runtime 217 - ctx *adt.OpContext 218 - arcs []*adt.Vertex 219 - p int 220 - cur Value 221 - f adt.Feature 222 - arcType adt.ArcType 216 + val Value 217 + idx *runtime.Runtime 218 + ctx *adt.OpContext 219 + arcs []*adt.Vertex 220 + patterns []adt.PatternConstraint 221 + p int 222 + cur Value 223 + f adt.Feature 224 + arcType adt.ArcType 225 + isPattern bool 226 + isList bool 223 227 } 224 228 225 229 type hiddenIterator = Iterator 226 230 227 231 // Next advances the iterator to the next value and reports whether there was any. 228 232 // It must be called before the first call to [Iterator.Value] or [Iterator.Selector]. 233 + // 234 + // Note that pattern constraints will be produced by the iterator before 235 + // any other field. 229 236 func (i *Iterator) Next() bool { 230 - if i.p >= len(i.arcs) { 237 + switch { 238 + case i.p >= len(i.arcs)+len(i.patterns): 231 239 i.cur = Value{} 232 240 return false 241 + case i.p < len(i.patterns): 242 + i.isPattern = true 243 + i.arcType = adt.ArcNotPresent 244 + pattern := i.patterns[i.p] 245 + pattern.Constraint.Finalize(i.ctx) 246 + i.cur = makeValue(i.val.idx, pattern.Constraint, 247 + linkParent(i.val.parent_, i.val.v, pattern.Constraint), 248 + ) 249 + i.p++ 250 + return true 251 + 252 + default: 253 + arc := i.arcs[i.p-len(i.patterns)] 254 + arc.Finalize(i.ctx) 255 + i.isPattern = false 256 + i.f = arc.Label 257 + i.arcType = arc.ArcType 258 + i.cur = makeValue(i.val.idx, arc, linkParent(i.val.parent_, i.val.v, arc)) 259 + i.p++ 260 + return true 233 261 } 234 - arc := i.arcs[i.p] 235 - arc.Finalize(i.ctx) 236 - p := linkParent(i.val.parent_, i.val.v, arc) 237 - i.f = arc.Label 238 - i.arcType = arc.ArcType 239 - i.cur = makeValue(i.val.idx, arc, p) 240 - i.p++ 241 - return true 242 262 } 243 263 244 264 // Value returns the current value in the list. ··· 249 269 250 270 // Selector reports the field label of this iteration. 251 271 func (i *Iterator) Selector() Selector { 252 - sel := featureToSel(i.f, i.idx) 253 - // Only call wrapConstraint if there is any constraint type to wrap with. 254 - if ctype := fromArcType(i.arcType); ctype != 0 { 255 - sel = wrapConstraint(sel, ctype) 272 + if !i.isPattern { 273 + sel := featureToSel(i.f, i.idx) 274 + // Only call wrapConstraint if there is any constraint type to wrap with. 275 + if ctype := fromArcType(i.arcType); ctype != 0 { 276 + sel = wrapConstraint(sel, ctype) 277 + } 278 + return sel 256 279 } 257 - return sel 280 + pattern := exprToVertex(i.patterns[i.p-1].Pattern) 281 + pattern.Finalize(i.ctx) 282 + 283 + return Selector{ 284 + patternSelector{ 285 + pattern: makeValue(i.val.idx, pattern, 286 + linkParent(i.val.parent_, i.val.v, pattern), 287 + ), 288 + _labelType: i.patternSelectorType().LabelType(), 289 + }, 290 + } 291 + } 292 + 293 + func (i *Iterator) patternSelectorType() SelectorType { 294 + if i.isList { 295 + // Pattern constraints in lists are always indexes. 296 + return IndexLabel | PatternConstraint 297 + } 298 + return StringLabel | PatternConstraint 258 299 } 259 300 260 301 // Label reports the label of the value if i iterates over struct fields and "" ··· 278 319 279 320 // FieldType reports the type of the field. 280 321 func (i *Iterator) FieldType() SelectorType { 322 + if i.isPattern { 323 + return i.patternSelectorType() 324 + } 281 325 return featureToSelType(i.f, i.arcType) 282 326 } 283 327 ··· 614 658 } 615 659 616 660 func newValueRoot(idx *runtime.Runtime, ctx *adt.OpContext, x adt.Expr) Value { 661 + return newVertexRoot(idx, ctx, exprToVertex(x)) 662 + } 663 + 664 + func exprToVertex(x adt.Expr) *adt.Vertex { 617 665 if n, ok := x.(*adt.Vertex); ok { 618 - return newVertexRoot(idx, ctx, n) 666 + return n 619 667 } 620 - node := &adt.Vertex{} 621 - node.AddConjunct(adt.MakeRootConjunct(nil, x)) 622 - return newVertexRoot(idx, ctx, node) 668 + n := &adt.Vertex{} 669 + n.AddConjunct(adt.MakeRootConjunct(nil, x)) 670 + return n 623 671 } 624 672 625 673 func newChildValue(o *structValue, i int) Value { ··· 1021 1069 if v.v.HasEllipsis { 1022 1070 return true 1023 1071 } 1072 + if _, ok := sel.sel.(patternSelector); ok { 1073 + // We can always add a pattern constraint. 1074 + return true 1075 + } 1024 1076 c := v.ctx() 1025 1077 f := sel.sel.feature(c) 1026 1078 return v.v.Accept(c, f) ··· 1308 1360 switch b := v.v.Bottom(); { 1309 1361 case b != nil && b.IsIncomplete() && !o.concrete && !o.final: 1310 1362 1311 - // Allow scalar values if hidden or definition fields are requested. 1312 - case !o.omitHidden, !o.omitDefinitions: 1363 + // Allow scalar values if hidden or definition fields or patterns are requested. 1364 + case !o.omitHidden, !o.omitDefinitions, o.includePatterns: 1313 1365 default: 1314 1366 if err := v.checkKind(ctx, adt.StructKind); err != nil && !err.ChildError { 1315 1367 return structValue{}, err ··· 1357 1409 } 1358 1410 arcs = append(arcs, arc) 1359 1411 } 1360 - return structValue{ctx, orig, obj, arcs}, nil 1412 + var patterns []adt.PatternConstraint 1413 + if o.includePatterns && obj.PatternConstraints != nil { 1414 + patterns = obj.PatternConstraints.Pairs 1415 + } 1416 + return structValue{ctx, orig, obj, arcs, patterns}, nil 1361 1417 } 1362 1418 1363 1419 // Struct returns the underlying struct of a value or an error if the value ··· 1437 1493 // Fields creates an iterator over v's fields if v is a struct or an error 1438 1494 // otherwise. 1439 1495 func (v Value) Fields(opts ...Option) (*Iterator, error) { 1440 - o := options{omitDefinitions: true, omitHidden: true, omitOptional: true} 1496 + o := options{ 1497 + omitDefinitions: true, 1498 + omitHidden: true, 1499 + omitOptional: true, 1500 + } 1441 1501 o.updateOptions(opts) 1442 1502 ctx := v.ctx() 1443 1503 obj, err := v.structValOpts(ctx, o) ··· 1445 1505 return &Iterator{idx: v.idx, ctx: ctx}, v.toErr(err) 1446 1506 } 1447 1507 1448 - return &Iterator{idx: v.idx, ctx: ctx, val: v, arcs: obj.arcs}, nil 1508 + return &Iterator{ 1509 + idx: v.idx, 1510 + ctx: ctx, 1511 + val: v, 1512 + arcs: obj.arcs, 1513 + patterns: obj.patterns, 1514 + isList: v.Kind() == ListKind, 1515 + }, nil 1449 1516 } 1450 1517 1451 1518 // Lookup reports the value at a path starting from v. The empty path returns v ··· 1637 1704 for _, sel := range slices.Backward(p.path) { 1638 1705 switch sel.Type() { 1639 1706 case StringLabel | PatternConstraint: 1707 + if _, ok := sel.sel.(patternSelector); ok { 1708 + // TODO consider relaxing this restriction, in which case we'd really 1709 + // want a constructor for pattern selectors too. 1710 + return newErrValue(v, 1711 + mkErr(nil, 0, "cannot use pattern selector in FillPath"), 1712 + ) 1713 + } 1640 1714 expr = &adt.StructLit{Decls: []adt.Decl{ 1641 1715 &adt.BulkOptionalField{ 1642 1716 Filter: &adt.BasicType{K: adt.StringKind}, ··· 1963 2037 omitDefinitions bool 1964 2038 omitOptional bool 1965 2039 omitAttrs bool 2040 + includePatterns bool 1966 2041 inlineImports bool 1967 2042 showErrors bool 1968 2043 final bool ··· 2066 2141 return func(p *options) { 2067 2142 p.hasHidden = true 2068 2143 p.omitDefinitions = !include 2144 + } 2145 + } 2146 + 2147 + // Patterns indicates whether pattern constraints should be included 2148 + // when iterating over struct fields. This includes universal pattern 2149 + // constraints such as `[_]: int` or `[=~"^a"]: string` but 2150 + // not the ellipsis pattern as selected by [AnyString]: that 2151 + // can be found with [Value.LookupPath].LookupPath(cue.MakePath(cue.AnyString)). 2152 + func Patterns(include bool) Option { 2153 + // TODO we can include patterns, but there's no way 2154 + // of iterating over patterns _only_ which might be 2155 + // useful in some cases. Perhaps we could add: 2156 + // func Regular(include bool) Option 2157 + return func(p *options) { 2158 + p.includePatterns = include 2069 2159 } 2070 2160 } 2071 2161
+60 -15
cue/types_test.go
··· 15 15 package cue_test 16 16 17 17 import ( 18 - "bytes" 19 18 "fmt" 20 19 "io" 21 20 "math" ··· 862 861 res: "{reg:4,}", 863 862 }, { 864 863 value: `{a:1,b:2,c:int}`, 865 - err: "cannot convert incomplete value", 864 + res: "{a:1,b:2,c:int,}", 866 865 }, { 867 866 value: ` 868 867 step1: {} ··· 871 870 step3: {prefix: step2.value} 872 871 } 873 872 _hidden: 3`, 874 - res: `{step1:{},step2:{"prefix":3},}`, 873 + res: `{step1:,step2:prefix: 3,}`, 875 874 }, { 876 875 opts: []cue.Option{cue.Final()}, 877 876 value: ` ··· 905 904 value: `a: x, x: y, y: b: 1`, 906 905 path: "a", 907 906 res: `{b:1,}`, 907 + }, { 908 + opts: []cue.Option{cue.Patterns(true)}, 909 + value: `[=~"^a"]: 1`, 910 + res: `{[=~"^a"]:1,}`, 911 + }, { 912 + opts: []cue.Option{cue.Patterns(true)}, 913 + value: `[=~"^a"]: 1, [string]: 2`, 914 + res: `{[=~"^a"]:1,[string]:2,}`, 915 + }, { 916 + opts: []cue.Option{cue.Patterns(true)}, 917 + value: `[=~"^a"]: >1, a: 2`, 918 + res: `{[=~"^a"]:>1,a:2,}`, 919 + }, { 920 + opts: []cue.Option{cue.Patterns(true)}, 921 + value: `{[_]: 1}`, 922 + res: `{[_]:1,}`, 923 + }, { 924 + opts: []cue.Option{cue.Patterns(true)}, 925 + value: `{...}`, 926 + res: `{}`, 927 + }, { 928 + opts: []cue.Option{cue.Patterns(true)}, 929 + value: `{...}`, 930 + res: `{}`, 931 + }, { 932 + opts: []cue.Option{cue.Patterns(true)}, 933 + value: `{[1, 2, ...int], [string]: int}`, 934 + res: `{[>=2]:int,[string]:int,0:1,1:2,}`, 908 935 }} 909 936 for _, tc := range testCases { 910 - cuetdtest.SmallMatrix.Run(t, tc.value, func(t *testing.T, m *cuetdtest.M) { 937 + cuetdtest.SmallMatrix.Run(t, "", func(t *testing.T, m *cuetdtest.M) { 911 938 f := getValue(m, tc.value) 912 939 913 940 obj := f.LookupPath(cue.ParsePath(tc.path)) ··· 917 944 918 945 buf := []byte{'{'} 919 946 for iter.Next() { 920 - buf = append(buf, iter.Selector().String()...) 947 + sel := iter.Selector() 948 + if sel.ConstraintType() == cue.PatternConstraint { 949 + qt.Check(t, qt.Equals( 950 + sel.String(), 951 + fmt.Sprintf("[%#v]", sel.Pattern()), 952 + )) 953 + wantType := cue.StringLabel | cue.PatternConstraint 954 + if obj.Kind() == cue.ListKind { 955 + wantType = cue.IndexLabel | cue.PatternConstraint 956 + } 957 + qt.Check(t, qt.Equals(sel.Type(), wantType)) 958 + } 959 + buf = append(buf, sel.String()...) 921 960 buf = append(buf, ':') 922 - b, err := iter.Value().MarshalJSON() 923 - checkFatal(t, err, tc.err, "Obj.At") 924 - buf = append(buf, b...) 961 + v := iter.Value() 962 + checkFatal(t, v.Err(), tc.err, "Obj.At") 963 + buf = fmt.Appendf(buf, "%#v", v) 925 964 buf = append(buf, ',') 926 965 } 927 966 buf = append(buf, '}') ··· 931 970 932 971 iter, _ = obj.Fields(tc.opts...) 933 972 for iter.Next() { 934 - want, err := iter.Value().MarshalJSON() 935 - checkFatal(t, err, tc.err, "Obj.At2") 973 + v := iter.Value() 974 + checkFatal(t, v.Err(), tc.err, "Obj.At2") 975 + want := fmt.Sprintf("%#v", v) 936 976 937 - got, err := obj.LookupPath(cue.MakePath(iter.Selector())).MarshalJSON() 938 - checkFatal(t, err, tc.err, "Obj.At2") 939 - 940 - if !bytes.Equal(got, want) { 941 - t.Errorf("Lookup: got %q; want %q", got, want) 977 + got := obj.LookupPath(cue.MakePath(iter.Selector())) 978 + if iter.FieldType().ConstraintType() == cue.PatternConstraint { 979 + // Can't look up iterated pattern constraints. 980 + qt.Assert(t, qt.ErrorMatches( 981 + got.Err(), 982 + `.*cannot look up pattern constraints.*`, 983 + )) 984 + } else { 985 + checkFatal(t, err, tc.err, "Obj.At2") 986 + qt.Assert(t, qt.Equals(fmt.Sprintf("%#v", got), want)) 942 987 } 943 988 } 944 989 v := obj.LookupPath(cue.MakePath(cue.Str("non-existing")))