Skip to content

evaluator: self-referential expression fails inappropriately #2627

@rogpeppe

Description

@rogpeppe

What version of CUE are you using (cue version)?

$ cue version
cue version v0.0.0-20230928155306-a43792cc30bd

go version devel go1.21-af8f94e3c5 Tue Jul 11 21:30:51 2023 +0000
      -buildmode exe
       -compiler gc
  DefaultGODEBUG panicnil=1
     CGO_ENABLED 1
          GOARCH amd64
            GOOS linux
         GOAMD64 v1
             vcs git
    vcs.revision a43792cc30bd3d9bff2b2f4c73469b8faa2f8c48
        vcs.time 2023-09-28T15:53:06Z
    vcs.modified false

Does this issue reproduce with the latest stable release?

What did you do?

In some cases, CUE allows a cyclic dependency as long as there is a concrete value available to break the cycle.

A classic example is this:

x: y+1
y: x-1
x: 5

A shorter example of this is a single self-reference that ensures that a value
remains stable across some transformation, for example:

x: x+1-1
x: 5

or, an alternative way of constraining x to be a whole number:

x: math.Round(x)
x: 5

The above examples all work as expected, but some other examples do not.

Here's a set of test cases that I believe should all work:

exec cue export test0.cue
cmp stdout expect0.json

exec cue export test1.cue
cmp stdout expect1.json

exec cue export test2.cue
cmp stdout expect2.json

exec cue export test3.cue
cmp stdout expect3.json

exec cue export test4.cue
cmp stdout expect4.json

exec cue export test5.cue
cmp stdout expect5.json

exec cue export test6.cue
cmp stdout expect6.json

exec cue export test7.cue
cmp stdout expect7.json

-- test0.cue --
x: y+1
y: x-1
x: 5

-- expect0.json --
{
    "x": 5,
    "y": 4
}
-- test1.cue --
x: x+1-1
x: 5
-- expect1.json --
{
    "x": 5
}
-- test2.cue --
import "math"

x: math.Round(x)
x: 5
-- expect2.json --
{
    "x": 5
}
-- test3.cue --
// FAIL
import "list"

x: list.FlattenN(x, -1)
x: [1, 2, 3]
-- expect3.json --
{
    "x": [
        1,
        2,
        3
    ]
}
-- test4.cue --
import "encoding/json"

x: json.Marshal(json.Unmarshal(x))
x: "5"
-- expect4.json --
{
    "x": "5"
}
-- test5.cue --
import "encoding/json"

x: json.Marshal(json.Unmarshal(x))
x: #"{"a":5}"#
-- expect5.json --
{
    "x": "{\"a\":5}"
}
-- test6.cue --
// FAIL
import "encoding/json"

x: json.Unmarshal(json.Marshal(x))
x: a: 5
-- expect6.json --
{
    "x": {
        "a": 5
    }
}
-- test7.cue --
import "encoding/json"

x: json.Unmarshal(y)
y: json.Marshal(x)
y: #"{"a":5}"#
x: a: 5
-- expect7.json --
{
    "x": {
        "a": 5
    },
    "y": "{\"a\":5}"
}

What did you expect to see?

A passing test.

What did you see instead?

> exec cue export test0.cue
[stdout]
{
    "x": 5,
    "y": 4
}
> cmp stdout expect0.json
> exec cue export test1.cue
[stdout]
{
    "x": 5
}
> cmp stdout expect1.json
> exec cue export test2.cue
[stdout]
{
    "x": 5
}
> cmp stdout expect2.json
> exec cue export test3.cue
[stderr]
x: cannot add field 0: was already used:
    ./test3.cue:4:5
x: cannot add field 1: was already used:
    ./test3.cue:4:8
x: cannot add field 2: was already used:
    ./test3.cue:4:11
[exit status 1]
FAIL: /tmp/testscript1016914076/q.cue/script.txtar:10: unexpected command failure
> cmp stdout expect3.json
--- stdout
+++ expect3.json
@@ -0,0 +1,7 @@
+{
+    "x": [
+        1,
+        2,
+        3
+    ]
+}

FAIL: /tmp/testscript1016914076/q.cue/script.txtar:11: stdout and expect3.json differ
> exec cue export test4.cue
[stdout]
{
    "x": "5"
}
> cmp stdout expect4.json
> exec cue export test5.cue
[stdout]
{
    "x": "{\"a\":5}"
}
> cmp stdout expect5.json
> exec cue export test6.cue
[stderr]
cycle error
[exit status 1]
FAIL: /tmp/testscript1016914076/q.cue/script.txtar:19: unexpected command failure
> cmp stdout expect6.json
--- stdout
+++ expect6.json
@@ -0,0 +1,5 @@
+{
+    "x": {
+        "a": 5
+    }
+}

FAIL: /tmp/testscript1016914076/q.cue/script.txtar:20: stdout and expect6.json differ
> exec cue export test7.cue
[stdout]
{
    "x": {
        "a": 5
    },
    "y": "{\"a\":5}"
}
> cmp stdout expect7.json

test 3 and test 6 both fail, despite other similar cases working OK.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions