Repository: d5/tengo (go)

Issue:
panic: interface conversion: tengo.Object is *tengo.CompiledFunction, not *tengo.String
Having two source modules where the one uses the others gives panic.
```
package main

import (
	"fmt"

	"github.com/d5/tengo/v2"
	"github.com/d5/tengo/v2/stdlib"
)

func main() {

	script := tengo.NewScript([]byte(`
		module_a := import("module_a")
		module_b := import("module_b")
		module_b()
	`))
	mods := stdlib.GetModuleMap("json", "fmt", "math", "times")

	mods.AddSourceModule("module_a", []byte(`
		fubar := func(arr, low, n) {
		    i := low + 1
		    for i < n + 1 {
		    }
		}

		module_a := {
		    "fo": func(){}
		}

		export module_a
	`))

	mods.AddSourceModule("module_b", []byte(
		`export func(){
			module_a := import("module_a")
		}
	`))
	script.SetImports(mods)

	compiledFunc, err := script.Compile()
	if err != nil {
		panic(err)
	}

	fmt.Println("RUN")
	err = compiledFunc.Run()
	if err != nil {
		panic(err)
	}
}
```

Pull request:
fix a bug in bytecode optimization code
Fixes #289 

As @ozanh pointed out, this is a bug in Bytecode optimization code. While removing duplicate constants, some indexes were re-mapped 2+ times if duplicate CompiledFunctions exist.

@ozanh @geseq let me know what you guys think.

Patch:
diff --git a/bytecode.go b/bytecode.go
index cfd0d0b..f3049ce 100644
--- a/bytecode.go
+++ b/bytecode.go
@@ -97,6 +97,7 @@ func (b *Bytecode) RemoveDuplicates() {
 	var deduped []Object
 
 	indexMap := make(map[int]int) // mapping from old constant index to new index
+	fns := make(map[*CompiledFunction]int)
 	ints := make(map[int64]int)
 	strings := make(map[string]int)
 	floats := make(map[float64]int)
@@ -106,9 +107,14 @@ func (b *Bytecode) RemoveDuplicates() {
 	for curIdx, c := range b.Constants {
 		switch c := c.(type) {
 		case *CompiledFunction:
-			// add to deduped list
-			indexMap[curIdx] = len(deduped)
-			deduped = append(deduped, c)
+			if newIdx, ok := fns[c]; ok {
+				indexMap[curIdx] = newIdx
+			} else {
+				newIdx = len(deduped)
+				fns[c] = newIdx
+				indexMap[curIdx] = newIdx
+				deduped = append(deduped, c)
+			}
 		case *ImmutableMap:
 			modName := inferModuleName(c)
 			newIdx, ok := immutableMaps[modName]


Test patch:
diff --git a/vm_test.go b/vm_test.go
index d451726..0adefe7 100644
--- a/vm_test.go
+++ b/vm_test.go
@@ -2654,6 +2654,25 @@ export func(a) {
 	expectRun(t, `out = import("mod0")`,
 		Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { break } } }`),
 		tengo.UndefinedValue)
+
+	// duplicate compiled functions
+	// NOTE: module "mod" has a function with some local variable, and it's
+	//  imported twice by the main script. That causes the same CompiledFunction
+	//  put in constants twice and the Bytecode optimization (removing duplicate
+	//  constants) should still work correctly.
+	expectRun(t, `
+m1 := import("mod")
+m2 := import("mod")
+out = m1.x
+	`,
+		Opts().Module("mod", `
+f1 := func(a, b) {
+	c := a + b + 1
+	return a + b + 1
+}
+export { x: 1 }
+`),
+		1)
 }
 
 func TestModuleBlockScopes(t *testing.T) {