Skip to content

Commit

Permalink
cue: support optional field lookup in LookupPath
Browse files Browse the repository at this point in the history
This is done by allowing Selectors to be converted
to an optional form (if they aren't already).

It also supports the use of the Selectors AnyIndex and
AnyString for getting `T` for  `[string]: T` and `[...T]`.

This also allows deprecating Elem and Template, which
will be done in a separate CL.

Change-Id: I6074382f12259c3dd87557471d80f82720a84779
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9347
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
  • Loading branch information
mpvl committed Apr 8, 2021
1 parent e70a1db commit 957003c
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 12 deletions.
48 changes: 42 additions & 6 deletions cue/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,18 @@ var (
anyLabel = Selector{sel: anySelector(adt.AnyRegular)}
)

// Optional converts sel into an optional equivalent.
// foo -> foo?
func (sel Selector) Optional() Selector {
return wrapOptional(sel)
}

type selector interface {
String() string

feature(ctx adt.Runtime) adt.Feature
kind() adt.FeatureType
optional() bool
}

// A Path is series of selectors to query a CUE value.
Expand Down Expand Up @@ -144,6 +151,17 @@ func (p Path) String() string {
return b.String()
}

// Optional returns the optional form of a Path. For instance,
// foo.bar --> foo?.bar?
//
func (p Path) Optional() Path {
q := make([]Selector, 0, len(p.path))
for _, s := range p.path {
q = appendSelector(q, wrapOptional(s))
}
return Path{path: q}
}

func toSelectors(expr ast.Expr) []Selector {
switch x := expr.(type) {
case *ast.Ident:
Expand Down Expand Up @@ -293,6 +311,7 @@ type scopedSelector struct {
func (s scopedSelector) String() string {
return s.name
}
func (scopedSelector) optional() bool { return false }

func (s scopedSelector) kind() adt.FeatureType {
switch {
Expand Down Expand Up @@ -331,6 +350,8 @@ func (d definitionSelector) String() string {
return string(d)
}

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

func (d definitionSelector) kind() adt.FeatureType {
return adt.DefinitionLabel
}
Expand All @@ -354,6 +375,7 @@ func (s stringSelector) String() string {
return str
}

func (s stringSelector) optional() bool { return false }
func (s stringSelector) kind() adt.FeatureType { return adt.StringLabel }

func (s stringSelector) feature(r adt.Runtime) adt.Feature {
Expand All @@ -376,6 +398,7 @@ func (s indexSelector) String() string {
}

func (s indexSelector) kind() adt.FeatureType { return adt.IntLabel }
func (s indexSelector) optional() bool { return false }

func (s indexSelector) feature(r adt.Runtime) adt.Feature {
return adt.Feature(s)
Expand All @@ -385,6 +408,7 @@ func (s indexSelector) feature(r adt.Runtime) adt.Feature {
type anySelector adt.Feature

func (s anySelector) String() string { return "_" }
func (s anySelector) optional() bool { return true }
func (s anySelector) kind() adt.FeatureType { return adt.Feature(s).Typ() }

func (s anySelector) feature(r adt.Runtime) adt.Feature {
Expand All @@ -398,16 +422,27 @@ func (s anySelector) feature(r adt.Runtime) adt.Feature {
// func ImportPath(s string) Selector {
// return importSelector(s)
// }
type optionalSelector struct {
selector
}

// type importSelector string
func wrapOptional(sel Selector) Selector {
if !sel.sel.optional() {
sel = Selector{optionalSelector{sel.sel}}
}
return sel
}

// func (s importSelector) String() string {
// return literal.String.Quote(string(s))
// func isOptional(sel selector) bool {
// _, ok := sel.(optionalSelector)
// return ok
// }

// func (s importSelector) feature(r adt.Runtime) adt.Feature {
// return adt.InvalidLabel
// }
func (s optionalSelector) optional() bool { return true }

func (s optionalSelector) String() string {
return s.selector.String() + "?"
}

// TODO: allow looking up in parent scopes?

Expand All @@ -429,6 +464,7 @@ type pathError struct {
}

func (p pathError) String() string { return p.Error.Error() }
func (p pathError) optional() bool { return false }
func (p pathError) kind() adt.FeatureType { return 0 }
func (p pathError) feature(r adt.Runtime) adt.Feature {
return adt.InvalidLabel
Expand Down
18 changes: 17 additions & 1 deletion cue/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ func resolveExpr(ctx *context, v *adt.Vertex, x ast.Expr) adt.Value {

// LookupPath reports the value for path p relative to v.
func (v Value) LookupPath(p Path) Value {
if v.v == nil {
return Value{}
}
n := v.v
ctx := v.ctx().opCtx

outer:
for _, sel := range p.path {
f := sel.sel.feature(v.idx.Runtime)
Expand All @@ -65,7 +70,18 @@ outer:
continue outer
}
}
// TODO: if optional, look up template for name.
if sel.sel.optional() {
x := &adt.Vertex{
Parent: v.v,
Label: sel.sel.feature(ctx),
}
n.MatchAndInsert(ctx, x)
if len(x.Conjuncts) > 0 {
x.Finalize(ctx)
n = x
continue
}
}

var x *adt.Bottom
if err, ok := sel.sel.(pathError); ok {
Expand Down
125 changes: 125 additions & 0 deletions cue/query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2021 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cue_test

import (
"bytes"
"testing"

"cuelang.org/go/cue"
"cuelang.org/go/internal/diff"
)

func TestLookupPath(t *testing.T) {
r := &cue.Runtime{}

testCases := []struct {
in string
path cue.Path
out string `test:"update"` // :nerdSnipe:
notExist bool `test:"update"` // :nerdSnipe:
}{{
in: `
[Name=string]: { a: Name }
`,
path: cue.MakePath(cue.Str("a")),
notExist: true,
}, {
in: `
#V: {
x: int
}
#X: {
[string]: int64
} & #V
v: #X
`,
path: cue.ParsePath("v.x"),
out: `int64`,
}, {
in: `
a: [...int]
`,
path: cue.MakePath(cue.Str("a"), cue.AnyIndex),
out: `int`,
}, {
in: `
[Name=string]: { a: Name }
`,
path: cue.MakePath(cue.AnyString, cue.Str("a")),
out: `string`,
}, {
in: `
[Name=string]: { a: Name }
`,
path: cue.MakePath(cue.Str("b").Optional(), cue.Str("a")),
out: `"b"`,
}, {
in: `
[Name=string]: { a: Name }
`,
path: cue.MakePath(cue.AnyString),
out: `{a: string}`,
}, {
in: `
a: [Foo=string]: [Bar=string]: { b: Foo+Bar }
`,
path: cue.MakePath(cue.Str("a"), cue.Str("b"), cue.Str("c")).Optional(),
out: `{b: "bc"}`,
}, {
in: `
a: [Foo=string]: b: [Bar=string]: { c: Foo }
a: foo: b: [Bar=string]: { d: Bar }
`,
path: cue.MakePath(cue.Str("a"), cue.Str("foo"), cue.Str("b"), cue.AnyString),
out: `{c: "foo", d: string}`,
}, {
in: `
[Name=string]: { a: Name }
`,
path: cue.MakePath(cue.Str("a")),
notExist: true,
}}
for _, tc := range testCases {
t.Run(tc.path.String(), func(t *testing.T) {
v := compileT(t, r, tc.in)

v = v.LookupPath(tc.path)

if exists := v.Exists(); exists != !tc.notExist {
t.Fatalf("exists: got %v; want: %v", exists, !tc.notExist)
} else if !exists {
return
}

w := compileT(t, r, tc.out)

if k, d := diff.Diff(v, w); k != diff.Identity {
b := &bytes.Buffer{}
diff.Print(b, d)
t.Error(b)
}
})
}
}

func compileT(t *testing.T, r *cue.Runtime, s string) cue.Value {
t.Helper()
inst, err := r.Compile("", s)
if err != nil {
t.Fatal(err)
}
return inst.Value()
}
1 change: 1 addition & 0 deletions internal/core/adt/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ func (f Feature) ToValue(ctx *OpContext) Value {
if !f.IsRegular() {
panic("not a regular label")
}
// TODO: Handle special regular values: invalid and AnyRegular.
if f.IsInt() {
return ctx.NewInt64(int64(f.Index()))
}
Expand Down
14 changes: 9 additions & 5 deletions internal/core/adt/optional.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,22 @@ outer:
}
}

if !arc.Label.IsRegular() {
f := arc.Label
if !f.IsRegular() {
return
}
if int64(f.Index()) == MaxIndex {
f = 0
}

var label Value
if o.types&HasComplexPattern != 0 && arc.Label.IsString() {
label = arc.Label.ToValue(c)
if o.types&HasComplexPattern != 0 && f.IsString() {
label = f.ToValue(c)
}

if len(o.Bulk) > 0 {
bulkEnv := *env
bulkEnv.DynamicLabel = arc.Label
bulkEnv.DynamicLabel = f
bulkEnv.Deref = nil
bulkEnv.Cycles = nil

Expand All @@ -56,7 +60,7 @@ outer:
// if matched && f.additional {
// continue
// }
if matchBulk(c, env, b, arc.Label, label) {
if matchBulk(c, env, b, f, label) {
matched = true
info := closeInfo.SpawnSpan(b.Value, ConstraintSpan)
arc.AddConjunct(MakeConjunct(&bulkEnv, b, info))
Expand Down

0 comments on commit 957003c

Please sign in to comment.