cue icon indicating copy to clipboard operation
cue copied to clipboard

cmd/go: cue get go ignores legitimate JSON map types

Open rogpeppe opened this issue 1 year ago • 0 comments

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

$ cue version
v0.9.0-alpha.3

Does this issue reproduce with the latest stable release?

Yes

What did you do?

# Sanity check the tests
exec go run main.go

exec cue get go --local test
cmp main_go_gen.cue main_go_gen.cue-want
-- main_go_gen.cue-want --
// Code generated by cue get go. DO NOT EDIT.

//cue:generate cue get go test

package main

#T1: {
	X: [string]: int
	Y: int

#T2: {
	X: [string]: int
	Y: string @go(,S)
}

#S: string
-- go.mod --
module test

-- main.go --
package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
)

type T1 struct {
	X map[int]string
	Y int
}

type T2 struct {
	X map[S]string
	Y S
}

type S int

func (s S) MarshalText() ([]byte, error) {
	return []byte(fmt.Sprint("p", int(s))), nil
}

func (s *S) UnmarshalText(data []byte) error {
	if len(data) == 0 || data[0] != 'p' {
		return fmt.Errorf("invalid S value")
	}
	i, err := strconv.Atoi(string(data[1:]))
	if err != nil {
		return err
	}
	*s = S(i)
	return nil
}

type T3 struct {
	X map[*int]string
	Y int
}

var tests = []struct {
	in        any
	wantError string
	want      string
}{{
	in: T1{
		X: map[int]string{
			123: "foo",
		},
		Y: 123,
	},
	want: `{"X":{"123":"foo"},"Y":123}`,
}, {
	in: T2{
		X: map[S]string{
			123: "foo",
		},
		Y: 123,
	},
	want: `{"X":{"p123":"foo"},"Y":"p123"}`,
}, {
	in: T3{
		X: map[*int]string{
			new(int): "foo",
		},
		Y: 123,
	},
	wantError: `json: unsupported type: map[*int]string`,
}}

func main() {
	for i, test := range tests {
		fatalf := func(f string, a ...any) {
			fmt.Printf("test %d failed: %s\n", i, fmt.Sprintf(f, a...))
			os.Exit(1)
		}
		data, err := json.Marshal(test.in)
		if test.wantError != "" {
			if err == nil {
				fatalf("expected error")
			}
			if got, want := err.Error(), test.wantError; got != want {
				fatalf("unexpected error; got %q want %q", got, want)
			}
			continue
		}
		if err != nil {
			fatalf("unexpected error: %v", err)
		}
		if got, want := string(data), test.want; got != want {
			fatalf("unexpected result; got %q want %q", got, want)
		}
	}
}

What did you expect to see?

A passing test.

What did you see instead?

# Sanity check the tests (0.510s)
> exec go run main.go
> exec cue get go --local test
> cmp main_go_gen.cue main_go_gen.cue-want
diff main_go_gen.cue main_go_gen.cue-want
--- main_go_gen.cue
+++ main_go_gen.cue-want
@@ -4,12 +4,13 @@
 
 package main
 
-#T1: Y: int
+#T1: {
+	X: [string]: int
+	Y: int
 
 #T2: {
+	X: [string]: int
 	Y: string @go(,S)
 }
 
 #S: string
-
-#T3: Y: int

FAIL: /tmp/testscript2542559523/x.txtar/script.txtar:5: main_go_gen.cue and main_go_gen.cue-want differ

The cue get go logic is omitting the X field in two cases where Go's encoding/json generates valid JSON for the types, and it is also generating CUE for the T3 type which can never be marshaled to JSON because the key type is not supported.

At the least, cue get go could generate [string]: ... fields for these maps, but it could probably do better than that when the key is numeric.

rogpeppe avatar Apr 26 '24 11:04 rogpeppe