Pārlūkot izejas kodu

Fixed Export() value for arrow functions and classes. Added Runtime.AssertConstructor. Closes #419.

Dmitry Panov 3 gadi atpakaļ
vecāks
revīzija
7adb499685
4 mainītis faili ar 123 papildinājumiem un 34 dzēšanām
  1. 11 11
      func.go
  2. 64 1
      func_test.go
  3. 47 22
      runtime.go
  4. 1 0
      value.go

+ 11 - 11
func.go

@@ -65,10 +65,6 @@ func (f *nativeFuncObject) export(*objectExportCtx) interface{} {
 	return f.f
 }
 
-func (f *nativeFuncObject) exportType() reflect.Type {
-	return reflect.TypeOf(f.f)
-}
-
 func (f *funcObject) _addProto(n unistring.String) Value {
 	if n == "prototype" {
 		if _, exists := f.values[n]; !exists {
@@ -184,6 +180,10 @@ func (f *classFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return f.Call, true
 }
 
+func (f *classFuncObject) export(*objectExportCtx) interface{} {
+	return f.Call
+}
+
 func (f *classFuncObject) createInstance(args []Value, newTarget *Object) (instance *Object) {
 	if f.derived {
 		if ctor := f.prototype.self.assertConstructor(); ctor != nil {
@@ -315,12 +315,12 @@ func (f *baseJsFuncObject) call(call FunctionCall, newTarget Value) Value {
 	return f._call(call.Arguments, newTarget, nilSafe(call.This))
 }
 
-func (f *funcObject) export(*objectExportCtx) interface{} {
+func (f *baseJsFuncObject) export(*objectExportCtx) interface{} {
 	return f.Call
 }
 
-func (f *funcObject) exportType() reflect.Type {
-	return reflect.TypeOf(f.Call)
+func (f *baseFuncObject) exportType() reflect.Type {
+	return reflectTypeFunc
 }
 
 func (f *baseJsFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
@@ -331,14 +331,14 @@ func (f *funcObject) assertConstructor() func(args []Value, newTarget *Object) *
 	return f.construct
 }
 
-func (f *arrowFuncObject) exportType() reflect.Type {
-	return reflect.TypeOf(f.Call)
-}
-
 func (f *arrowFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return f.Call, true
 }
 
+func (f *arrowFuncObject) export(*objectExportCtx) interface{} {
+	return f.Call
+}
+
 func (f *baseFuncObject) init(name unistring.String, length Value) {
 	f.baseObject.init()
 

+ 64 - 1
func_test.go

@@ -1,6 +1,10 @@
 package goja
 
-import "testing"
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
 
 func TestFuncProto(t *testing.T) {
 	const SCRIPT = `
@@ -41,3 +45,62 @@ func TestFuncPrototypeRedefine(t *testing.T) {
 
 	testScript(SCRIPT, valueTrue, t)
 }
+
+func TestFuncExport(t *testing.T) {
+	vm := New()
+	typ := reflect.TypeOf((func(FunctionCall) Value)(nil))
+
+	f := func(expr string, t *testing.T) {
+		v, err := vm.RunString(expr)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if actualTyp := v.ExportType(); actualTyp != typ {
+			t.Fatalf("Invalid export type: %v", actualTyp)
+		}
+		ev := v.Export()
+		if actualTyp := reflect.TypeOf(ev); actualTyp != typ {
+			t.Fatalf("Invalid export value: %v", ev)
+		}
+	}
+
+	t.Run("regular function", func(t *testing.T) {
+		f("(function() {})", t)
+	})
+
+	t.Run("arrow function", func(t *testing.T) {
+		f("(()=>{})", t)
+	})
+
+	t.Run("method", func(t *testing.T) {
+		f("({m() {}}).m", t)
+	})
+
+	t.Run("class", func(t *testing.T) {
+		f("(class {})", t)
+	})
+}
+
+func ExampleAssertConstructor() {
+	vm := New()
+	res, err := vm.RunString(`
+		(class C {
+			constructor(x) {
+				this.x = x;
+			}
+		})
+	`)
+	if err != nil {
+		panic(err)
+	}
+	if ctor, ok := AssertConstructor(res); ok {
+		obj, err := ctor(nil, vm.ToValue("Test"))
+		if err != nil {
+			panic(err)
+		}
+		fmt.Print(obj.Get("x"))
+	} else {
+		panic("Not a constructor")
+	}
+	// Output: Test
+}

+ 47 - 22
runtime.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
-	"github.com/dop251/goja/file"
 	"go/ast"
 	"hash/maphash"
 	"math"
@@ -18,6 +17,7 @@ import (
 	"golang.org/x/text/collate"
 
 	js_ast "github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/parser"
 	"github.com/dop251/goja/unistring"
 )
@@ -2346,36 +2346,18 @@ func (r *Runtime) New(construct Value, args ...Value) (o *Object, err error) {
 type Callable func(this Value, args ...Value) (Value, error)
 
 // AssertFunction checks if the Value is a function and returns a Callable.
+// Note, for classes this returns a callable and a 'true', however calling it will always result in a TypeError.
+// For classes use AssertConstructor().
 func AssertFunction(v Value) (Callable, bool) {
 	if obj, ok := v.(*Object); ok {
 		if f, ok := obj.self.assertCallable(); ok {
 			return func(this Value, args ...Value) (ret Value, err error) {
-				defer func() {
-					if x := recover(); x != nil {
-						if ex, ok := x.(*uncatchableException); ok {
-							err = ex.err
-							if len(obj.runtime.vm.callStack) == 0 {
-								obj.runtime.leaveAbrupt()
-							}
-						} else {
-							panic(x)
-						}
-					}
-				}()
-				ex := obj.runtime.vm.try(func() {
+				err = obj.runtime.runWrapped(func() {
 					ret = f(FunctionCall{
 						This:      this,
 						Arguments: args,
 					})
 				})
-				if ex != nil {
-					err = ex
-				}
-				vm := obj.runtime.vm
-				vm.clearStack()
-				if len(vm.callStack) == 0 {
-					obj.runtime.leave()
-				}
 				return
 			}, true
 		}
@@ -2383,6 +2365,49 @@ func AssertFunction(v Value) (Callable, bool) {
 	return nil, false
 }
 
+// Constructor is a type that can be used to call constructors. The first argument (newTarget) can be nil
+// which sets it to the constructor function itself.
+type Constructor func(newTarget *Object, args ...Value) (*Object, error)
+
+// AssertConstructor checks if the Value is a constructor and returns a Constructor.
+func AssertConstructor(v Value) (Constructor, bool) {
+	if obj, ok := v.(*Object); ok {
+		if ctor := obj.self.assertConstructor(); ctor != nil {
+			return func(newTarget *Object, args ...Value) (ret *Object, err error) {
+				err = obj.runtime.runWrapped(func() {
+					ret = ctor(args, newTarget)
+				})
+				return
+			}, true
+		}
+	}
+	return nil, false
+}
+
+func (r *Runtime) runWrapped(f func()) (err error) {
+	defer func() {
+		if x := recover(); x != nil {
+			if ex, ok := x.(*uncatchableException); ok {
+				err = ex.err
+				if len(r.vm.callStack) == 0 {
+					r.leaveAbrupt()
+				}
+			} else {
+				panic(x)
+			}
+		}
+	}()
+	ex := r.vm.try(f)
+	if ex != nil {
+		err = ex
+	}
+	r.vm.clearStack()
+	if len(r.vm.callStack) == 0 {
+		r.leave()
+	}
+	return
+}
+
 // IsUndefined returns true if the supplied Value is undefined. Note, it checks against the real undefined, not
 // against the global object's 'undefined' property.
 func IsUndefined(v Value) bool {

+ 1 - 0
value.go

@@ -50,6 +50,7 @@ var (
 	reflectTypeMap    = reflect.TypeOf(map[string]interface{}{})
 	reflectTypeArray  = reflect.TypeOf([]interface{}{})
 	reflectTypeString = reflect.TypeOf("")
+	reflectTypeFunc   = reflect.TypeOf((func(FunctionCall) Value)(nil))
 )
 
 var intCache [256]Value