Skip to content

Commit

Permalink
internal/core/export: support ResolveReferences
Browse files Browse the repository at this point in the history
Note that this does not fully implement what was requested
in #867, but this may be sufficient. Feedback welcome.

Issue #867

Change-Id: I54db0a623df69a6bfa1cc164c29032a5b20c4f5e
Signed-off-by: Marcel van Lohuizen <mpvl@golang.org>
  • Loading branch information
mpvl committed Nov 30, 2021
1 parent 0231e16 commit 31422a1
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 2 deletions.
197 changes: 197 additions & 0 deletions cue/syntax_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// 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 (
"strings"
"testing"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/format"
)

func TestSyntax(t *testing.T) {
o := func(opts ...cue.Option) []cue.Option {
return opts
}
_ = o
testCases := []struct {
name string
in string
path string
options []cue.Option
out string
}{{
name: "preseve docs",
in: `
// Aloha
hello: "world"
`,
options: o(cue.Docs(true)),
out: `
{
// Aloha
hello: "world"
}`,
}, {
name: "partially resolvable",
in: `
x: {}
t: {name: string}
output: [ ... {t & x.value}]
`,
options: o(cue.ResolveReferences(true)),

// TODO: note that this does not resolve t, even though it potentially
// could. The current implementation makes this rather hard. As the
// output would not be correct anyway, the question is whether this
// makes sense.
// One way to implement this would be for the evaluator to keep track
// of good and bad conjuncts, and then package them nicely in a Vertex
// so they remain accessible.
out: `
{
x: {}
t: {
name: string
}
output: [...t & x.value]
}`,
}, {
// Structural errors (and worse) are reported as is.
name: "structural error",
in: `
#List: {
value: _
next: #List
}
a: b: #List
`,
path: "a",
options: o(cue.ResolveReferences(true)),
out: `
{
b: _|_ // #List.next: structural cycle
}`,
}, {
name: "resolveReferences",
in: `
// User 1
v1: #Deployment: {
spec: {
replicas: int
containers: [...]
other: option: int
}
incomplete: {
// NOTE: the definition of "a" will be out of scope so this
// reference will not be resolvable.
// TODO: hoist the definition of "a" into a let expression.
x: a.x
y: 1 | 2
z: [1, 2][a.x]
}
// NOTE: structural cycles are eliminated from disjunctions. This
// means the semantics of the type is not preserved.
// TODO: should we change this?
recursive: #List
}
a: {}
#D: {}
#List: {
Value: _
Next: #List | *null
}
parameter: {
image: string
replicas: int
}
_mystring: string
resource: v1.#Deployment & {
spec: {
replicas: parameter.replicas
containers: [{
image: parameter.image
name: "main"
envs: [..._mystring]
}]
}
}
parameter: image: *"myimage" | string
parameter: replicas: *2 | >=1 & <5
// User 2
parameter: replicas: int
resource: spec: replicas: parameter.replicas
parameter: replicas: 3
`,
path: "resource",
options: o(cue.ResolveReferences(true)),
out: `
{
spec: {
replicas: 3
containers: [{
image: *"myimage" | string
name: "main"
envs: [...string]
}]
other: {
option: int
}
}
incomplete: {
x: a.x
y: 1 | 2
z: [1, 2][a.x]
}
recursive: {
Value: _
Next: null
}
}
`,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := cuecontext.New()

v := ctx.CompileString(tc.in)
v = v.LookupPath(cue.ParsePath(tc.path))

syntax := v.Syntax(tc.options...)
b, err := format.Node(syntax)
if err != nil {
t.Fatal(err)
}
got := strings.TrimSpace(string(b))
want := strings.TrimSpace(tc.out)
if got != want {
t.Errorf("got: %v; want %v", got, want)
}
})
}
}
19 changes: 17 additions & 2 deletions cue/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,7 @@ func (v Value) Syntax(opts ...Option) ast.Node {
ShowHidden: !o.omitHidden && !o.concrete,
ShowAttributes: !o.omitAttrs,
ShowDocs: o.docs,
ShowErrors: o.showErrors,
}

pkgID := v.instance().ID()
Expand Down Expand Up @@ -1007,7 +1008,7 @@ You could file a bug with the above information at:
// var expr ast.Expr
var err error
var f *ast.File
if o.concrete || o.final {
if o.concrete || o.final || o.resolveReferences {
// inst = v.instance()
var expr ast.Expr
expr, err = p.Value(v.idx, pkgID, v.v)
Expand Down Expand Up @@ -2006,6 +2007,7 @@ type options struct {
omitOptional bool
omitAttrs bool
resolveReferences bool
showErrors bool
final bool
ignoreClosedness bool // used for comparing APIs
docs bool
Expand Down Expand Up @@ -2062,7 +2064,20 @@ func DisallowCycles(disallow bool) Option {
// ResolveReferences forces the evaluation of references when outputting.
// This implies the input cannot have cycles.
func ResolveReferences(resolve bool) Option {
return func(p *options) { p.resolveReferences = resolve }
return func(p *options) {
p.resolveReferences = resolve

// ResolveReferences is implemented as a Value printer, rather than
// a definition printer, even though it should be more like the latter.
// To reflect this we convert incomplete errors to their original
// expression.
//
// TODO: ShowErrors mostly shows incomplete errors, even though this is
// just an approximation. There seems to be some inconsistencies as to
// when child errors are marked as such, making the conversion somewhat
// inconsistent. This option is conservative, though.
p.showErrors = true
}
}

// Raw tells Syntax to generate the value as is without any simplifications.
Expand Down
4 changes: 4 additions & 0 deletions internal/core/export/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ type Profile struct {
ShowAttributes bool

// ShowErrors treats errors as values and will not percolate errors up.
//
// TODO: convert this option to an error level instead, showing only
// errors below a certain severity.
ShowErrors bool

// Use unevaluated conjuncts for these error types
// IgnoreRecursive

Expand Down

0 comments on commit 31422a1

Please sign in to comment.