golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.98k stars 17.53k forks source link

syscall/js: possibility of using String.prototype.* methods on strings #35917

Closed dmitshur closed 3 years ago

dmitshur commented 4 years ago

With GopherJS, it was possible to call the String.prototype.toLowerCase method on a JavaScript String:

https://gopherjs.github.io/playground/#/ekZpm7tuW4

It's possible I'm overlooking something trivial, but this doesn't seem possible with syscall/js API of WebAssembly. Consider the same program modified to use syscall/js:

package main

import (
    "fmt"
    "syscall/js"
)

func main() {
    body := js.Global().Get("document").Get("body")

    fmt.Println(body.Get("nodeName"))
    fmt.Println(body.Get("nodeName").Call("toLowerCase"))
    fmt.Println(body.Get("nodeName").Get("toLowerCase").Invoke())
}

Its output with Go 1.13.4 is:

BODY
panic: syscall/js: call of Value.Get on string

goroutine 1 [running]:
syscall/js.Value.Get(0x7ff800010000000e, 0x34d12, 0xb, 0x7ff800010000000e)
    /usr/local/go/src/syscall/js/js.go:252 +0x39
main.main()
    /tmp/tmp.CQimpW7L/main.go:12 +0xb

Depending on whether a JavaScript String type is considered a "JavaScript object" or not, this may be consistent with js.Value.Get documentation, which says:

Get returns the JavaScript property p of value v. It panics if v is not a JavaScript object.

I'm wondering if it's possible to use toLowerCase with syscall/js API? If not, should it be possible?

/cc @neelance

slimsag commented 4 years ago

Workaround: you can call JS String methods via the String.prototype itself, i.e. in JS:

String.prototype.toLowerCase.call("FOO")

In Go:

js.Global().Get("String").Get("prototype").Get("toLowerCase").Call("call", js.ValueOf("FOO"))

I have used this in gopherjs/vecty#251 in order to workaround this problem.

agnivade commented 4 years ago

If not, should it be possible?

I think it should. The current API relies on Reflect.get which only works on objects, and therefore fails on normal strings. With a little bit of code, and now with the argument spread syntax available, I was able to make it work.

fmt.Println(body.Get("nodeName").Call("toLowerCase"))
fmt.Println(body.Get("nodeName").Call("charAt", 3))

Not pasting the code here. But I can send a CL if Richard is okay with it.

neelance commented 4 years ago

This is because of JavaScript's autoboxing. string and String are two distinct types. The first is the primitive type, the second one is the boxed object type and only the second has methods. Autoboxing makes it so the expression

"foo".toUpperCase()

gets interpreted as

new String("foo").toUpperCase()

I do not yet see why syscall/js should emulate autoboxing. Is there any proper use case? In my opinion, for the examples above the proper solution is to use the .String() method to get the Go string and then use Go's functions for manipulating the string.

dmitshur commented 4 years ago

Thanks for the explanation.

I do not yet see why syscall/js should emulate autoboxing.

I agree, I don't think that would be a good change.

In my opinion, for the examples above the proper solution is to use the .String() method to get the Go string and then use Go's functions for manipulating the string.

It should also be possible to use JavaScript's toLowerCase explicitly, if one wants to (e.g., to avoid having to import strings package, or for other JavaScript functions where there isn't equivalent Go functionality).

It sounds like it is possible with one of these two ways:

var str string = "FOO"

js.Global().Get("String").New(str).Call("toLowerCase")

// or

js.Global().Get("String").Get("prototype").Get("toLowerCase").Call("call", str)

So nothing needs to be done. I'll close this if there aren't objections.

agnivade commented 4 years ago

It looks like there are no objections. ping @dmitshur.