traefik / yaegi

Yaegi is Another Elegant Go Interpreter
https://pkg.go.dev/github.com/traefik/yaegi
Apache License 2.0
6.94k stars 343 forks source link

incomplete type error with generics #1491

Closed mpl closed 1 year ago

mpl commented 1 year ago

The following program sample.go triggers an unexpected result

package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "net/netip"
    "reflect"
)

func unmarshalJSON[T any](b []byte, x *[]T) error {
    if *x != nil {
        return errors.New("already initialized")
    }
    if len(b) == 0 {
        return nil
    }
    return json.Unmarshal(b, x)
}

func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
    return SliceView[T, V]{x}
}

type StructView[T any] interface {
    Valid() bool
    AsStruct(T) anyStruct
}

type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
    ж []T
}

type ViewCloner[T any, V StructView[T]] interface {
    View() V
    Clone() T
}

func (s SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(s.ж) }

func (s *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &s.ж) }

type Slice[T any] struct {
    ж []T
}

func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }

func (v *Slice[T]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) }

func StructOf[T any](x []T) Slice[T] {
    return Slice[T]{x}
}

type IPPrefixSlice struct {
    ж Slice[netip.Prefix]
}

type viewStruct struct {
    Int        int
    Strings    Slice[string]
    StringsPtr *Slice[string] `json:",omitempty"`
}

type viewClonerImpl string

func (vci viewClonerImpl) View() structViewImpl {
    return structViewImpl(vci)
}

func (vci viewClonerImpl) Clone() viewClonerImpl {
    return vci + "-clone"
}

type anyStruct struct {
    f interface{}
}

type structViewImpl string

func (sv structViewImpl) Valid() bool {
    return true
}

func (sv structViewImpl) AsStruct(T viewClonerImpl) anyStruct {
    return anyStruct{
        f: T,
    }
}

func main() {
    ss := StructOf([]string{"bar"})
    in := viewStruct{
        Int:        1234,
        Strings:    ss,
        StringsPtr: &ss,
    }

    var buf bytes.Buffer
    encoder := json.NewEncoder(&buf)
    encoder.SetIndent("", "")
    err1 := encoder.Encode(&in)
    b := buf.Bytes()
    var got viewStruct
    err2 := json.Unmarshal(b, &got)
    println(err1 == nil, err2 == nil, reflect.DeepEqual(got, in))

    t := viewClonerImpl("hello2")
    sov := SliceOfViews[viewClonerImpl, structViewImpl]([]viewClonerImpl{t})
    println(sov.ж[0].View())
    println(sov.ж[0].Clone().View())

}

// Output:
// true true true
// hello2
// hello2-clone

Expected result

% go run ./issue-1460.go 
true true true
hello2
hello2-clone

Got

% yaegi run ./issue-1460.go
panic: ./issue-1460.go:30:53: incomplete type SliceView[main.viewClonerImpl,main.structViewImpl] [recovered]
    panic: ./issue-1460.go:109:9: CFG post-order panic: ./issue-1460.go:30:53: incomplete type SliceView[main.viewClonerImpl,main.structViewImpl]

goroutine 1 [running]:
github.com/traefik/yaegi/interp.(*Interpreter).cfg.func2.1()
    /Users/mpl/src/github.com/traefik/yaegi/interp/cfg.go:536 +0x78
panic({0x1a72300, 0xc0002152f0})
    /Users/mpl/go1/src/runtime/panic.go:890 +0x262
github.com/traefik/yaegi/interp.(*itype).frameType(0x1?)
    /Users/mpl/src/github.com/traefik/yaegi/interp/type.go:2140 +0x1bf
github.com/traefik/yaegi/interp.(*itype).frameType(0x1a77000?)
    /Users/mpl/src/github.com/traefik/yaegi/interp/type.go:2159 +0x136
github.com/traefik/yaegi/interp.(*scope).add(0xc0002d14d0, 0x2500a68?)
    /Users/mpl/src/github.com/traefik/yaegi/interp/scope.go:210 +0x7b
github.com/traefik/yaegi/interp.(*Interpreter).cfg.func1(0xc00049ab40)
    /Users/mpl/src/github.com/traefik/yaegi/interp/cfg.go:445 +0x2d2c
github.com/traefik/yaegi/interp.(*node).Walk(0xc00049ab40, 0xc000466b98, 0xc000466be0)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:284 +0x34
github.com/traefik/yaegi/interp.(*Interpreter).cfg(0xc0002c8000, 0xc00049ab40, 0xc0002d14d0, {0xc00020b9f8, 0x4}, {0xc00020b9f8, 0x4})
    /Users/mpl/src/github.com/traefik/yaegi/interp/cfg.go:62 +0x2b4
github.com/traefik/yaegi/interp.genType(0x38?, 0xc0002d0090, {0xc00025c940, 0x38}, 0xc00044b0e0, {0xc000480da0, 0x2, 0x2}, {0xc000480d90, 0x2, ...})
    /Users/mpl/src/github.com/traefik/yaegi/interp/type.go:1142 +0x4a5
github.com/traefik/yaegi/interp.nodeType2(0xc0002c8000, 0xc0002d0090, 0xc000496120, {0xc000480d90, 0x1, 0x2})
    /Users/mpl/src/github.com/traefik/yaegi/interp/type.go:886 +0x59a9
github.com/traefik/yaegi/interp.nodeType2(0xc0002c8000, 0xc0002d0090, 0xc000493320, {0xc000207ca0, 0x0, 0x1})
    /Users/mpl/src/github.com/traefik/yaegi/interp/type.go:756 +0x671e
github.com/traefik/yaegi/interp.nodeType(0x8?, 0x2500a68?, 0x8?)
    /Users/mpl/src/github.com/traefik/yaegi/interp/type.go:399 +0x25
github.com/traefik/yaegi/interp.(*Interpreter).cfg.func1(0xc000492fc0)
    /Users/mpl/src/github.com/traefik/yaegi/interp/cfg.go:407 +0x1a79
github.com/traefik/yaegi/interp.(*node).Walk(0xc000492fc0, 0xc000468b10, 0xc000468b58)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:284 +0x34
github.com/traefik/yaegi/interp.(*Interpreter).cfg(0xc0002c8000, 0xc000492fc0, 0xc0002d0090, {0xc00020b9f8, 0x4}, {0xc00020b9f8, 0x4})
    /Users/mpl/src/github.com/traefik/yaegi/interp/cfg.go:62 +0x2b4
github.com/traefik/yaegi/interp.(*Interpreter).cfg.func2(0xc000447680)
    /Users/mpl/src/github.com/traefik/yaegi/interp/cfg.go:1037 +0xd148
github.com/traefik/yaegi/interp.(*node).Walk(0xc000447680, 0xc000469990, 0xc0004699d8)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:291 +0xad
github.com/traefik/yaegi/interp.(*node).Walk(0xc000447440, 0xc000469990, 0xc0004699d8)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:288 +0x75
github.com/traefik/yaegi/interp.(*node).Walk(0xc000440a20, 0xc000469990, 0xc0004699d8)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:288 +0x75
github.com/traefik/yaegi/interp.(*node).Walk(0xc000440360, 0xc000469990, 0xc0004699d8)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:288 +0x75
github.com/traefik/yaegi/interp.(*node).Walk(0xc0002d47e0, 0xc000469990, 0xc0004699d8)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:288 +0x75
github.com/traefik/yaegi/interp.(*Interpreter).cfg(0xc0002c8000, 0xc0002d47e0, 0xc0002d1320, {0xc00020b9f8, 0x4}, {0xc00020b9f8, 0x4})
    /Users/mpl/src/github.com/traefik/yaegi/interp/cfg.go:62 +0x2b4
github.com/traefik/yaegi/interp.(*Interpreter).CompileAST(0xc0002c8000, {0x1c89688?, 0xc000282280?})
    /Users/mpl/src/github.com/traefik/yaegi/interp/program.go:97 +0x1ad
github.com/traefik/yaegi/interp.(*Interpreter).compileSrc(0xc0002c8000, {0xc0003fdb00?, 0x0?}, {0x7ff7bfeff69a?, 0xc0003fdb00?}, 0xae?)
    /Users/mpl/src/github.com/traefik/yaegi/interp/program.go:64 +0xb8
github.com/traefik/yaegi/interp.(*Interpreter).eval(0xc0002c8000, {0xc0003fdb00?, 0x8ae?}, {0x7ff7bfeff69a?, 0xc0003fc900?}, 0xae?)
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:552 +0x28
github.com/traefik/yaegi/interp.(*Interpreter).EvalPath(0xc0002c8000, {0x7ff7bfeff69a, 0xf})
    /Users/mpl/src/github.com/traefik/yaegi/interp/interp.go:510 +0xab
main.runFile(0x7ff7bfeff69a?, {0x7ff7bfeff69a, 0xf}, 0x0)
    /Users/mpl/src/github.com/traefik/yaegi/cmd/yaegi/run.go:153 +0xee
main.run({0xc0000361d0?, 0x1, 0x1})
    /Users/mpl/src/github.com/traefik/yaegi/cmd/yaegi/run.go:116 +0xbec
main.main()
    /Users/mpl/src/github.com/traefik/yaegi/cmd/yaegi/yaegi.go:133 +0xcf

Yaegi Version

on top of https://github.com/traefik/yaegi/pull/1489

Additional Notes

I built upon the ./_test/issue-1460.go file in https://github.com/traefik/yaegi/pull/1489 , so that the defined generic types would actually be instantiated and used in the main.

I also changed some small details like the signature of the AsStruct func (just so that it would make more sense), but I do not think they are relevant to the failure.

mpl commented 1 year ago

Interestingly, if I revert my "little changes" to the AsStruct method signature, we get a different failure, so they are more important than I thought:

package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "net/netip"
    "reflect"
)

func unmarshalJSON[T any](b []byte, x *[]T) error {
    if *x != nil {
        return errors.New("already initialized")
    }
    if len(b) == 0 {
        return nil
    }
    return json.Unmarshal(b, x)
}

func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
    return SliceView[T, V]{x}
}

type StructView[T any] interface {
    Valid() bool
    // AsStruct(T) anystruct
    AsStruct() T
}

type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
    ж []T
}

type ViewCloner[T any, V StructView[T]] interface {
    View() V
    Clone() T
}

func (s SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(s.ж) }

func (s *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &s.ж) }

type Slice[T any] struct {
    ж []T
}

func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }

func (v *Slice[T]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) }

func StructOf[T any](x []T) Slice[T] {
    return Slice[T]{x}
}

type IPPrefixSlice struct {
    ж Slice[netip.Prefix]
}

type viewStruct struct {
    Int        int
    Strings    Slice[string]
    StringsPtr *Slice[string] `json:",omitempty"`
}

type viewClonerImpl string

func (vci viewClonerImpl) View() structViewImpl {
    return structViewImpl(vci)
}

func (vci viewClonerImpl) Clone() viewClonerImpl {
    return vci + "-clone"
}

/*
type anyStruct struct {
    f interface{}
}
*/

type structViewImpl string

func (sv structViewImpl) Valid() bool {
    return true
}

/*
func (sv structViewImpl) AsStruct(T viewClonerImpl) anyStruct {
    return anyStruct{
        f: T,
    }
}
*/

func (sv structViewImpl) AsStruct() viewClonerImpl {
    return viewClonerImpl(sv)
}

func main() {
    ss := StructOf([]string{"bar"})
    in := viewStruct{
        Int:        1234,
        Strings:    ss,
        StringsPtr: &ss,
    }

    var buf bytes.Buffer
    encoder := json.NewEncoder(&buf)
    encoder.SetIndent("", "")
    err1 := encoder.Encode(&in)
    b := buf.Bytes()
    var got viewStruct
    err2 := json.Unmarshal(b, &got)
    println(err1 == nil, err2 == nil, reflect.DeepEqual(got, in))

    t := viewClonerImpl("hello2")
    sov := SliceOfViews[viewClonerImpl, structViewImpl]([]viewClonerImpl{t})
    println(sov.ж[0].View())
    println(sov.ж[0].Clone().View())

}

// Output:
// true true true
// hello2
// hello2-clone
% yaegi run ./issue-1460.go
run: ./issue-1460.go:118:9: not a generic type: func([]main.T) main.SliceView
mvertes commented 1 year ago

This is now fixed by #1489, including the last variant you proposed.

mvertes commented 1 year ago

I'm sorry, I closed it too early as #1489 is not yet merged