traefik / yaegi

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

interface assertion not work #1558

Open laushunyu opened 1 year ago

laushunyu commented 1 year ago

The following program sample.go triggers an unexpected result

package main

import (
    "fmt"
    "github.com/traefik/yaegi/interp"
    "github.com/traefik/yaegi/stdlib"
)

func main() {
    interpreter := interp.New(interp.Options{})
    interpreter.Use(stdlib.Symbols)
    expected := ""
    fmt.Printf(expected)

    _, err := interpreter.Compile(`package main

import "fmt"

type A struct {
}

func (a A) String() string {
    return "String"
}

func (a A) GoString() string {
    return "GoString"
}

func GetGoString() (fmt.GoStringer, bool) {
    var a fmt.Stringer = A{}
    goStr, ok := a.(fmt.GoStringer)
    return goStr, ok
}
`)
    if err != nil {
        panic(err)
    }

    mainSymbols := interpreter.Symbols("main")["main"]
    SelfSrc := mainSymbols["GetGoString"].Interface().(func() (fmt.GoStringer, bool))
    _, ok := SelfSrc()
    fmt.Println(ok)
}

Expected result

# go run .
true

Got

# go run .
false

Yaegi Version

v0.15.1

Additional Notes

hi, assertion from a interface to another interface is not working.

I haven't found the exact location in the source code where this bug occurs yet, but I can suggest some possible causes and solutions for the bug:

package main

import "fmt"

type A struct {
}

func (a A) String() string {
    return "String"
}

func (a A) GoString() string {
    return "GoString"
}

func main() {
    var a fmt.Stringer = A{}
    v := reflect.ValueOf(&a).Elem()
    target := reflect.TypeOf((*fmt.GoStringer)(nil)).Elem()

    fmt.Println(v.Type(), target)
    fmt.Println(v.CanConvert(target))

    // implementation of interface v
    fmt.Println(v.Elem().Type(), target)
    fmt.Println(v.Elem().CanConvert(target))
}

output:

fmt.Stringer fmt.GoStringer
false
main.A fmt.GoStringer
true

I think when trying to know whether v.CanConvert(), we should Elem the v if v.Kind == reflect.Interface.

laushunyu commented 1 year ago

it seem like that the assignee to a interface does not save it's impletation's value. so when converting to another interface, it not work?

in yaegi/interp/run.go:410 v = v.Elem() image

laushunyu commented 1 year ago

it seem like that the assignee to a interface does not save it's impletation's value. so when converting to another interface, it not work?

in yaegi/interp/run.go:410 v = v.Elem() image

the address of empty struct value is 0, so we cannot get the implementation. another field IType to store Type is nesserary. or the type of IValue should be reflect.Value

laushunyu commented 1 year ago

another case:

package main

import "fmt"

type A struct {
    Fuck string
    Shit int
}

func (a A) String() string {
    return "String"
}

func (a A) GoString() string {
    return "GoString"
}

type Stringer interface {
    String() string
}

func GetGoString() (fmt.GoStringer, bool) {
    var a Stringer = &A{}
    goStr, ok := a.(fmt.GoStringer)
    return goStr, ok
}

if define the interface in src package, it can compare every method (in interp/run.go:379) then return true; but interface in bin package(fmt.Stringer) is not work.

laushunyu commented 1 year ago

and

switch a.(type) {
case fmt.GoStringer:
}

is not working too, even if interface is in the same src package, i found that yaegi/interp/run.go:2997:

// match against 1 type: assign var to concrete value

dest value can also be a interface too, so we should compare all its method.

laushunyu commented 1 year ago

The commit 25f44d6 seem like had a wrong issue referenced.