If an internal function is defined laterally, when passed to the external part and called back, the parameters are lost.
Author: linlexingCreated May 9, 2026Updated May 9, 2026
The following program sample.go triggers an unexpected result
package main
import (
"fmt"
"io"
"io/fs"
"path"
"reflect"
"sort"
"strings"
"time"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
var goScript_success = `
package main
import (
"fmt"
"export"
)
func init() {
export.API = func(s string) {
s1:=test
export.Call(s, s1)
return
}
}
func test(s string) string {
return s
}
`
var goScript_err = `
package main
import (
"fmt"
"export"
)
func init() {
export.API = func(s string) {
export.Call(s, test)
return
}
}
func test(s string) string {
return s
}
`
func Call(s string, cb func(s string) string) {
if cb == nil {
panic("cb is null")
}
// fmt.Printf("%#v,s:%s\n", cb, s)
rev := cb(s)
fmt.Printf("<<%s>>\n", rev)
}
var API func(s string)
func main() {
testScript(goScript_success)
testScript(goScript_err)
testFSScript(goScript_success)
testFSScript(goScript_err)
}
func testFSScript(str string) {
vfs := MapFS{
"_pkg/src/main/main.go": &MapFile{
Data: []byte(str),
},
}
i := interp.New(interp.Options{
GoPath: "./_pkg", //仅用于虚拟的文件系统
SourcecodeFilesystem: vfs,
})
if err := i.Use(stdlib.Symbols); err != nil {
panic(err)
}
demoSymbols := map[string]map[string]reflect.Value{}
demoSymbols["export/export"] = map[string]reflect.Value{
"Call": reflect.ValueOf(Call),
"API": reflect.ValueOf(&API).Elem(),
}
if err := i.Use(demoSymbols); err != nil {
panic(err)
}
if _, err := i.EvalPath("main"); err != nil {
panic(err)
}
if API != nil {
API("success")
} else {
panic("api is null")
}
}
func testScript(str string) {
i := interp.New(interp.Options{})
if err := i.Use(stdlib.Symbols); err != nil {
panic(err)
}
demoSymbols := map[string]map[string]reflect.Value{}
demoSymbols["export/export"] = map[string]reflect.Value{
"Call": reflect.ValueOf(Call),
"API": reflect.ValueOf(&API).Elem(),
}
if err := i.Use(demoSymbols); err != nil {
panic(err)
}
if _, err := i.Eval(str); err != nil {
panic(err)
}
if API != nil {
API("success")
} else {
panic("api is null")
}
}
//========================================================================
type MapFS map[string]*MapFile
// A MapFile describes a single file in a MapFS.
type MapFile struct {
Data []byte // file content
Mode fs.FileMode // FileInfo.Mode
ModTime time.Time // FileInfo.ModTime
Sys any // FileInfo.Sys
}
var _ fs.FS = MapFS(nil)
var _ fs.File = (*openMapFile)(nil)
// Open opens the named file.
func (fsys MapFS) Open(name string) (fs.File, error) {
//确保文件路径没有问题
name = strings.ReplaceAll(name, "\\", "/")
if !fs.ValidPath(name) {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}
file := fsys[name]
if file != nil && file.Mode&fs.ModeDir == 0 {
// Ordinary file
return &openMapFile{name, mapFileInfo{path.Base(name), file}, 0}, nil
}
// Directory, possibly synthesized.
// Note that file can be nil here: the map need not contain explicit parent directories for all its files.
// But file can also be non-nil, in case the user wants to set metadata for the directory explicitly.
// Either way, we need to construct the list of children of this directory.
var list []mapFileInfo
var elem string
var need = make(map[string]bool)
if name == "." {
elem = "."
for fname, f := range fsys {
i := strings.Index(fname, "/")
if i < 0 {
if fname != "." {
list = append(list, mapFileInfo{fname, f})
}
} else {
need[fname[:i]] = true
}
}
} else {
elem = name[strings.LastIndex(name, "/")+1:]
prefix := name + "/"
for fname, f := range fsys {
if strings.HasPrefix(fname, prefix) {
felem := fname[len(prefix):]
i := strings.Index(felem, "/")
if i < 0 {
list = append(list, mapFileInfo{felem, f})
} else {
need[fname[len(prefix):len(prefix)+i]] = true
}
}
}
// If the directory name is not in the map,
// and there are no children of the name in the map,
// then the directory is treated as not existing.
if file == nil && list == nil && len(need) == 0 {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}
}
for _, fi := range list {
delete(need, fi.name)
}
for name := range need {
list = append(list, mapFileInfo{name, &MapFile{Mode: fs.ModeDir}})
}
sort.Slice(list, func(i, j int) bool {
return list[i].name < list[j].name
})
if file == nil {
file = &MapFile{Mode: fs.ModeDir}
}
return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil
}
// fsOnly is a wrapper that hides all but the fs.FS methods,
// to avoid an infinite recursion when implementing special
// methods in terms of helpers that would use them.
// (In general, implementing these methods using the package fs helpers
// is redundant and unnecessary, but having the methods may make
// MapFS exercise more code paths when used in tests.)
type fsOnly struct{ fs.FS }
func (fsys MapFS) ReadFile(name string) ([]byte, error) {
return fs.ReadFile(fsOnly{fsys}, name)
}
func (fsys MapFS) Stat(name string) (fs.FileInfo, error) {
return fs.Stat(fsOnly{fsys}, name)
}
func (fsys MapFS) ReadDir(name string) ([]fs.DirEntry, error) {
return fs.ReadDir(fsOnly{fsys}, name)
}
func (fsys MapFS) Glob(pattern string) ([]string, error) {
return fs.Glob(fsOnly{fsys}, pattern)
}
type noSub struct {
MapFS
}
func (noSub) Sub() {} // not the fs.SubFS signature
func (fsys MapFS) Sub(dir string) (fs.FS, error) {
return fs.Sub(noSub{fsys}, dir)
}
// A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file.
type mapFileInfo struct {
name string
f *MapFile
}
func (i *mapFileInfo) Name() string { return i.name }
func (i *mapFileInfo) Size() int64 { return int64(len(i.f.Data)) }
func (i *mapFileInfo) Mode() fs.FileMode { return i.f.Mode }
func (i *mapFileInfo) Type() fs.FileMode { return i.f.Mode.Type() }
func (i *mapFileInfo) ModTime() time.Time { return i.f.ModTime }
func (i *mapFileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 }
func (i *mapFileInfo) Sys() any { return i.f.Sys }
func (i *mapFileInfo) Info() (fs.FileInfo, error) { return i, nil }
// An openMapFile is a regular (non-directory) fs.File open for reading.
type openMapFile struct {
path string
mapFileInfo
offset int64
}
func (f *openMapFile) Stat() (fs.FileInfo, error) { return &f.mapFileInfo, nil }
func (f *openMapFile) Close() error { return nil }
func (f *openMapFile) Read(b []byte) (int, error) {
if f.offset >= int64(len(f.f.Data)) {
return 0, io.EOF
}
if f.offset < 0 {
return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}
}
n := copy(b, f.f.Data[f.offset:])
f.offset += int64(n)
return n, nil
}
func (f *openMapFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
// offset += 0
case 1:
offset += f.offset
case 2:
offset += int64(len(f.f.Data))
}
if offset < 0 || offset > int64(len(f.f.Data)) {
return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid}
}
f.offset = offset
return offset, nil
}
func (f *openMapFile) ReadAt(b []byte, offset int64) (int, error) {
if offset < 0 || offset > int64(len(f.f.Data)) {
return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}
}
n := copy(b, f.f.Data[offset:])
if n < len(b) {
return n, io.EOF
}
return n, nil
}
// A mapDir is a directory fs.File (so also an fs.ReadDirFile) open for reading.
type mapDir struct {
path string
mapFileInfo
entry []mapFileInfo
offset int
}
func (d *mapDir) Stat() (fs.FileInfo, error) { return &d.mapFileInfo, nil }
func (d *mapDir) Close() error { return nil }
func (d *mapDir) Read(b []byte) (int, error) {
return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid}
}
func (d *mapDir) ReadDir(count int) ([]fs.DirEntry, error) {
n := len(d.entry) - d.offset
if n == 0 && count > 0 {
return nil, io.EOF
}
if count > 0 && n > count {
n = count
}
list := make([]fs.DirEntry, n)
for i := range list {
list[i] = &d.entry[d.offset+i]
}
d.offset += n
return list, nil
}Expected result
$ go run ./sample.go
<<success>>
<<success>>
<<success>>
<<success>>Got
<<success>>
<<>>
<<success>>
<<>>Yaegi Version
v0.16.1
Additional Notes
If an internal function is defined laterally, when passed to the external part and called back, the parameters are lost.
Source: traefik/yaegi