Skip to content

Commit

Permalink
cue: more support for required fields for selectors
Browse files Browse the repository at this point in the history
Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I559d2ca73facfb618107a9b34397a33bc6f84152
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/552171
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
Unity-Result: CUEcueckoo <cueckoo@cuelang.org>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
  • Loading branch information
mpvl committed Apr 6, 2023
1 parent f85172a commit 0e16084
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 47 deletions.
109 changes: 83 additions & 26 deletions cue/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ const (
InvalidSelectorType SelectorType = 0
)

// fromArcType reports the constraint type for t.
func fromArcType(t adt.ArcType) SelectorType {
switch t {
case adt.ArcMember:
return 0
case adt.ArcOptional:
return OptionalConstraint
case adt.ArcRequired:
return RequiredConstraint
default:
panic("arc type not supported")
}
}

// LabelType reports the label type of t.
func (t SelectorType) LabelType() SelectorType {
return t & 0b0001_1111
Expand Down Expand Up @@ -134,7 +148,7 @@ func (sel Selector) Unquoted() string {
switch s := sel.sel.(type) {
case stringSelector:
return string(s)
case optionalSelector:
case constraintSelector:
return string(s.selector.(stringSelector))
}
panic(fmt.Sprintf("unreachable %T", sel.sel))
Expand All @@ -147,11 +161,12 @@ func (sel Selector) IsConstraint() bool {
return sel.Type().ConstraintType() != 0
}

// IsString reports whether sel represents an optional or regular member field.
// IsString reports whether sel represents an optional, required, or regular
// member field.
func (sel Selector) IsString() bool {
// TODO: consider deprecating this method. It is a bit wonkey now.
t := sel.Type()
t &^= OptionalConstraint
t &^= OptionalConstraint | RequiredConstraint
return t == StringLabel
}

Expand Down Expand Up @@ -212,12 +227,22 @@ var (
anyString = Selector{sel: anySelector(adt.AnyString)}
)

// Optional converts sel into an optional equivalent.
// Optional converts sel into an optional constraint equivalent.
// It's a no-op if the selector is already optional.
//
// foo -> foo?
// foo -> foo?
// foo! -> foo?
func (sel Selector) Optional() Selector {
return wrapOptional(sel)
return wrapConstraint(sel, OptionalConstraint)
}

// Required converts sel into a required constraint equivalent.
// It's a no-op if the selector is already a required constraint.
//
// foo -> foo!
// foo? -> foo!
func (sel Selector) Required() Selector {
return wrapConstraint(sel, RequiredConstraint)
}

type selector interface {
Expand All @@ -226,7 +251,7 @@ type selector interface {
feature(ctx adt.Runtime) adt.Feature
labelType() SelectorType
constraintType() SelectorType
optional() bool
isConstraint() bool
}

// A Path is series of selectors to query a CUE value.
Expand Down Expand Up @@ -309,7 +334,7 @@ func (p Path) String() string {
func (p Path) Optional() Path {
q := make([]Selector, 0, len(p.path))
for _, s := range p.path {
q = appendSelector(q, wrapOptional(s))
q = appendSelector(q, wrapConstraint(s, OptionalConstraint))
}
return Path{path: q}
}
Expand Down Expand Up @@ -463,7 +488,7 @@ type scopedSelector struct {
func (s scopedSelector) String() string {
return s.name
}
func (scopedSelector) optional() bool { return false }
func (scopedSelector) isConstraint() bool { return false }

func (s scopedSelector) labelType() SelectorType {
if strings.HasPrefix(s.name, "_#") {
Expand Down Expand Up @@ -497,7 +522,7 @@ func (d definitionSelector) String() string {
return string(d)
}

func (d definitionSelector) optional() bool { return false }
func (d definitionSelector) isConstraint() bool { return false }

func (d definitionSelector) labelType() SelectorType {
return DefinitionLabel
Expand All @@ -524,7 +549,7 @@ func (s stringSelector) String() string {
return str
}

func (s stringSelector) optional() bool { return false }
func (s stringSelector) isConstraint() bool { return false }
func (s stringSelector) labelType() SelectorType { return StringLabel }
func (s stringSelector) constraintType() SelectorType { return 0 }

Expand All @@ -550,7 +575,7 @@ func (s indexSelector) String() string {
func (s indexSelector) labelType() SelectorType { return IndexLabel }
func (s indexSelector) constraintType() SelectorType { return 0 }

func (s indexSelector) optional() bool { return false }
func (s indexSelector) isConstraint() bool { return false }

func (s indexSelector) feature(r adt.Runtime) adt.Feature {
return adt.Feature(s)
Expand All @@ -559,8 +584,8 @@ func (s indexSelector) feature(r adt.Runtime) adt.Feature {
// an anySelector represents a wildcard option of a particular type.
type anySelector adt.Feature

func (s anySelector) String() string { return "[_]" }
func (s anySelector) optional() bool { return true }
func (s anySelector) String() string { return "[_]" }
func (s anySelector) isConstraint() bool { return true }
func (s anySelector) labelType() SelectorType {
// FeatureTypes are numbered sequentially. SelectorType is a bitmap. As they
// are defined in the same order, we can go from FeatureType to SelectorType
Expand All @@ -582,34 +607,48 @@ func (s anySelector) feature(r adt.Runtime) adt.Feature {
// func ImportPath(s string) Selector {
// return importSelector(s)
// }
type optionalSelector struct {
type constraintSelector struct {
selector
constraint SelectorType
}

func (s optionalSelector) labelType() SelectorType {
func (s constraintSelector) labelType() SelectorType {
return s.selector.labelType()
}

func (s optionalSelector) constraintType() SelectorType {
return OptionalConstraint
func (s constraintSelector) constraintType() SelectorType {
return s.constraint
}

func wrapOptional(sel Selector) Selector {
if !sel.sel.optional() {
sel = Selector{optionalSelector{sel.sel}}
func wrapConstraint(s Selector, t SelectorType) Selector {
sel := s.sel
if c, ok := sel.(constraintSelector); ok {
if c.constraint == t {
return s
}
sel = c.selector // unwrap
}
return sel
return Selector{constraintSelector{sel, t}}
}

// func isOptional(sel selector) bool {
// _, ok := sel.(optionalSelector)
// return ok
// }

func (s optionalSelector) optional() bool { return true }
func (s constraintSelector) isConstraint() bool {
return true
}

func (s optionalSelector) String() string {
return s.selector.String() + "?"
func (s constraintSelector) String() string {
var suffix string
switch s.constraint {
case OptionalConstraint:
suffix = "?"
case RequiredConstraint:
suffix = "!"
}
return s.selector.String() + suffix
}

// TODO: allow looking up in parent scopes?
Expand All @@ -632,7 +671,7 @@ type pathError struct {
}

func (p pathError) String() string { return "" }
func (p pathError) optional() bool { return false }
func (p pathError) isConstraint() bool { return false }
func (p pathError) labelType() SelectorType { return InvalidSelectorType }
func (p pathError) constraintType() SelectorType { return 0 }
func (p pathError) feature(r adt.Runtime) adt.Feature {
Expand Down Expand Up @@ -671,3 +710,21 @@ func featureToSel(f adt.Feature, r adt.Runtime) Selector {
errors.Newf(token.NoPos, "unexpected feature type %v", f.Typ()),
}}
}

func featureToSelType(f adt.Feature, at adt.ArcType) (st SelectorType) {
switch f.Typ() {
case adt.StringLabel:
st = StringLabel
case adt.IntLabel:
st = IndexLabel
case adt.DefinitionLabel:
st = DefinitionLabel
case adt.HiddenLabel:
st = HiddenLabel
case adt.HiddenDefinitionLabel:
st = HiddenDefinitionLabel
default:
panic("unsupported arc type")
}
return st | fromArcType(at)
}
15 changes: 14 additions & 1 deletion cue/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,24 @@ var selectorTests = []struct {
isString: true,
isConstraint: true,
}, {
sel: Def("foo").Optional(),
sel: Str("foo").Required(),
stype: StringLabel | RequiredConstraint,
string: "foo!",
unquoted: "foo",
isString: true,
isConstraint: true,
}, {
sel: Def("foo").Required().Optional(),
stype: DefinitionLabel | OptionalConstraint,
string: "#foo?",
isDefinition: true,
isConstraint: true,
}, {
sel: Def("foo").Optional().Required(),
stype: DefinitionLabel | RequiredConstraint,
string: "#foo!",
isDefinition: true,
isConstraint: true,
}, {
sel: AnyString,
stype: StringLabel | PatternConstraint,
Expand Down
5 changes: 2 additions & 3 deletions cue/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ outer:
f := sel.sel.feature(v.idx)
for _, a := range n.Arcs {
if a.Label == f {
if a.IsConstraint() && !sel.sel.optional() {
if a.IsConstraint() && !sel.sel.isConstraint() {
break
}
parent = linkParent(parent, n, a)
n = a
continue outer
}
}
if sel.sel.optional() {
// pattern or additional constraints.
if sel.sel.isConstraint() {
x := &adt.Vertex{
Parent: n,
Label: sel.sel.feature(ctx),
Expand Down
34 changes: 22 additions & 12 deletions cue/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,14 @@ func unwrapJSONError(err error) errors.Error {

// An Iterator iterates over values.
type Iterator struct {
val Value
idx *runtime.Runtime
ctx *adt.OpContext
arcs []*adt.Vertex
p int
cur Value
f adt.Feature
isOpt bool
val Value
idx *runtime.Runtime
ctx *adt.OpContext
arcs []*adt.Vertex
p int
cur Value
f adt.Feature
arcType adt.ArcType
}

type hiddenIterator = Iterator
Expand All @@ -234,7 +234,7 @@ func (i *Iterator) Next() bool {
p := linkParent(i.val.parent_, i.val.v, arc)
i.cur = makeValue(i.val.idx, arc, p)
i.f = arc.Label
i.isOpt = arc.ArcType == adt.ArcOptional
i.arcType = arc.ArcType
i.p++
return true
}
Expand All @@ -247,7 +247,7 @@ func (i *Iterator) Value() Value {

// Selector reports the field label of this iteration.
func (i *Iterator) Selector() Selector {
return featureToSel(i.f, i.idx)
return wrapConstraint(featureToSel(i.f, i.idx), fromArcType(i.arcType))
}

// Label reports the label of the value if i iterates over struct fields and ""
Expand All @@ -271,7 +271,12 @@ func (i *hiddenIterator) IsHidden() bool {

// IsOptional reports if a field is optional.
func (i *Iterator) IsOptional() bool {
return i.isOpt
return i.arcType == adt.ArcOptional
}

// FieldType reports the type of the field.
func (i *Iterator) FieldType() SelectorType {
return featureToSelType(i.f, i.arcType)
}

// IsDefinition reports if a field is a definition.
Expand Down Expand Up @@ -1460,12 +1465,16 @@ type Struct struct {
type hiddenStruct = Struct

// FieldInfo contains information about a struct field.
//
// Deprecated: only used by deprecated functions.
type FieldInfo struct {
Selector string
Name string // Deprecated: use Selector
Pos int
Value Value

SelectorType SelectorType

IsDefinition bool
IsOptional bool
IsHidden bool
Expand All @@ -1479,12 +1488,13 @@ func (s *hiddenStruct) Len() int {
func (s *hiddenStruct) Field(i int) FieldInfo {
a := s.at(i)
opt := a.ArcType == adt.ArcOptional
selType := featureToSelType(a.Label, a.ArcType)
ctx := s.v.ctx()

v := makeChildValue(s.v, a)
name := s.v.idx.LabelStr(a.Label)
str := a.Label.SelectorString(ctx)
return FieldInfo{str, name, i, v, a.Label.IsDef(), opt, a.Label.IsHidden()}
return FieldInfo{str, name, i, v, selType, a.Label.IsDef(), opt, a.Label.IsHidden()}
}

// FieldByName looks up a field for the given name. If isIdent is true, it will
Expand Down
Loading

0 comments on commit 0e16084

Please sign in to comment.