traefik / yaegi

Yaegi is Another Elegant Go Interpreter

Home Page:https://pkg.go.dev/github.com/traefik/yaegi

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Variadic Function With Receiver fails when no argument is passed

taliesins opened this issue · comments

The following program sample.go triggers an unexpected result

package int

import (
	"fmt"
	"testing"
)

type Logger struct {
}

func (*Logger) WriteWithReceiver(name string, opts ...Option) {
	messageWritten := false
	for _, opt := range opts {
		if !messageWritten {
			messageWritten = true
			opt(name)
		}
	}
}

func WriteWithoutReceiver(name string, opts ...Option) {
	messageWritten := false
	for _, opt := range opts {
		if !messageWritten {
			messageWritten = true
			opt(name)
		}
	}
}

type Option func(name string)

func WithOption() Option {
	return func(name string) {
		fmt.Println(name)
	}
}

func TestVariadicFunctionWithReceiver(t *testing.T) {
	logger := &Logger{}

	//////////// fails here //////////////////
	//This is a relatively popular method
	logger.WriteWithReceiver("WriteWithReceiver - Nothing passed at all fails")
	fmt.Println("WriteWithReceiver - Nothing passed at all fails") //Enumerate empty args will not output a message
	//////////// fails here //////////////////

	logger.WriteWithReceiver("WriteWithReceiver - WithOption() works", WithOption())
	logger.WriteWithReceiver("WriteWithReceiver - WithOption(), WithOption() works", WithOption(), WithOption())
	oneOption := []Option{WithOption()}
	logger.WriteWithReceiver("WriteWithReceiver - []Option{WithOption()} works", oneOption...)
	twoOption := []Option{WithOption(), WithOption()}
	logger.WriteWithReceiver("WriteWithReceiver - []Option{WithOption(), WithOption()} works", twoOption...)
	emptyOption := []Option{}
	logger.WriteWithReceiver("WriteWithReceiver - []Option{} works", emptyOption...)
	fmt.Println("WriteWithReceiver - []Option{} works") //Enumerate empty args will not output a message
	var nilOption []Option
	logger.WriteWithReceiver("WriteWithReceiver - var nilOption []Option works", nilOption...)
	fmt.Println("WriteWithReceiver - var nilOption []Option works") //Enumerate empty args will not output a message
}

func TestVariadicFunctionWithoutReceiver(t *testing.T) {

	WriteWithoutReceiver("WriteWithoutReceiver - WithOption() works", WithOption())
	WriteWithoutReceiver("WriteWithoutReceiver - WithOption(), WithOption() works", WithOption(), WithOption())
	oneOption := []Option{WithOption()}
	WriteWithoutReceiver("WriteWithoutReceiver - []Option{WithOption()} works", oneOption...)
	twoOption := []Option{WithOption(), WithOption()}
	WriteWithoutReceiver("WriteWithoutReceiver - []Option{WithOption(), WithOption()} works", twoOption...)
	emptyOption := []Option{}
	WriteWithoutReceiver("WriteWithoutReceiver - []Option{} works", emptyOption...)
	fmt.Println("WriteWithoutReceiver - []Option{} works") //Enumerate empty args will not output a message
	var nilOption []Option
	WriteWithoutReceiver("WriteWithoutReceiver - var nilOption []Option works", nilOption...)
	fmt.Println("WriteWithoutReceiver - var nilOption []Option works") //Enumerate empty args will not output a message
	WriteWithoutReceiver("WriteWithoutReceiver - Nothing passed at all works")
	fmt.Println("WriteWithoutReceiver - Nothing passed at all works") //Enumerate empty args will not output a message
}

Expected result

C:\Users\taliesin.sisson\go\src\github.com\taliesins\traefik-plugin-oidc>yaegi test -v github.com/taliesins/traefik-plugin-oidc/int
=== RUN   TestVariadicFunctionWithReceiver
WriteWithReceiver - Nothing passed at all fails
WriteWithReceiver - WithOption() works
WriteWithReceiver - WithOption(), WithOption() works
WriteWithReceiver - []Option{WithOption()} works
WriteWithReceiver - []Option{WithOption(), WithOption()} works
WriteWithReceiver - []Option{} works
WriteWithReceiver - var nilOption []Option works
--- PASS: TestVariadicFunctionWithReceiver (0.00s)
=== RUN   TestVariadicFunctionWithoutReceiver
WriteWithoutReceiver - WithOption() works
WriteWithoutReceiver - WithOption(), WithOption() works
WriteWithoutReceiver - []Option{WithOption()} works
WriteWithoutReceiver - []Option{WithOption(), WithOption()} works
WriteWithoutReceiver - []Option{} works
WriteWithoutReceiver - var nilOption []Option works
WriteWithoutReceiver - Nothing passed at all works
--- PASS: TestVariadicFunctionWithoutReceiver (0.00s)
PASS

Got

C:\Users\taliesin.sisson\go\src\github.com\taliesins\traefik-plugin-oidc>yaegi test -v github.com/taliesins/traefik-plugin-oidc/int
=== RUN   TestVariadicFunctionWithReceiver
C:\Users\taliesin.sisson\go\src\github.com\taliesins\traefik-plugin-oidc\int\do_test.go:40:13: panic
--- FAIL: TestVariadicFunctionWithReceiver (0.00s)
panic: reflect: Call using *struct {} as type string [recovered]
        panic: reflect: Call using *struct {} as type string [recovered]
        panic: reflect: Call using *struct {} as type string

goroutine 7 [running]:
testing.tRunner.func1.2({0x12c2be0, 0xc0004184f0})
        C:/Program Files/Go/src/testing/testing.go:1396 +0x24e
testing.tRunner.func1()
        C:/Program Files/Go/src/testing/testing.go:1399 +0x39f
panic({0x12c2be0, 0xc0004184f0})
        C:/Program Files/Go/src/runtime/panic.go:884 +0x212
github.com/traefik/yaegi/interp.runCfg.func1()
        C:/Users/taliesin.sisson/go/pkg/mod/github.com/traefik/yaegi@v0.14.3/interp/run.go:192 +0x148
panic({0x12c2be0, 0xc0004184f0})
        C:/Program Files/Go/src/runtime/panic.go:884 +0x212
reflect.Value.call({0xc00008ab40?, 0xc00041b2c0?, 0x970e5f?}, {0x13eb30e, 0x4}, {0xc00041b2f0, 0x2, 0x0?})
        C:/Program Files/Go/src/reflect/value.go:440 +0x1abf
reflect.Value.Call({0xc00008ab40?, 0xc00041b2c0?, 0x9b5bb2?}, {0xc00041b2f0?, 0x13e9360?, 0x1?})
        C:/Program Files/Go/src/reflect/value.go:368 +0xbc
github.com/traefik/yaegi/interp.call.func9.2({0xc00041b2f0?, 0xc00041b2c0?, 0x5?})
        C:/Users/taliesin.sisson/go/pkg/mod/github.com/traefik/yaegi@v0.14.3/interp/run.go:1288 +0x3c
github.com/traefik/yaegi/interp.call.func9(0xc0000cc8f0)
        C:/Users/taliesin.sisson/go/pkg/mod/github.com/traefik/yaegi@v0.14.3/interp/run.go:1303 +0x122f
github.com/traefik/yaegi/interp.runCfg(0xc0003eb440, 0xc0000cc8f0, 0x0?, 0x13d5d40?)
        C:/Users/taliesin.sisson/go/pkg/mod/github.com/traefik/yaegi@v0.14.3/interp/run.go:200 +0x29d
github.com/traefik/yaegi/interp.genFunctionWrapper.func1.1({0xc000009650, 0x1, 0x1?})
        C:/Users/taliesin.sisson/go/pkg/mod/github.com/traefik/yaegi@v0.14.3/interp/run.go:1002 +0x4a5
testing.tRunner(0xc0001a31e0, 0xc00041b050)
        C:/Program Files/Go/src/testing/testing.go:1446 +0x10b
created by testing.(*T).Run
        C:/Program Files/Go/src/testing/testing.go:1493 +0x35f

C:\Users\taliesin.sisson\go\src\github.com\taliesins\traefik-plugin-oidc>

Yaegi Version

v0.14.3

Additional Notes

It is quite a popular use case in structured logging, to make use of a variadic function with a method receiver e.g.

func (log *Logger) Debug(msg string, fields ...encoder.Field) {
...
}

@mvertes I think this is something you could possibly fix easily.