Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

incomplete type error with generics #1491

Closed
mpl opened this issue Nov 18, 2022 · 3 comments
Closed

incomplete type error with generics #1491

mpl opened this issue Nov 18, 2022 · 3 comments
Labels
area/core bug Something isn't working

Comments

@mpl
Copy link
Collaborator

mpl commented Nov 18, 2022

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 #1489

Additional Notes

I built upon the ./_test/issue-1460.go file in #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
Copy link
Collaborator Author

mpl commented Nov 18, 2022

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

@mpl mpl added bug Something isn't working area/core labels Dec 26, 2022
@mvertes
Copy link
Member

mvertes commented Feb 7, 2023

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

@mvertes mvertes closed this as completed Feb 7, 2023
@mvertes mvertes reopened this Feb 7, 2023
@mvertes
Copy link
Member

mvertes commented Feb 7, 2023

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

@mvertes mvertes closed this as completed Feb 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/core bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants