Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sorting a struct slice by a compare method removes and duplicates items #403

Closed
jmattheis opened this issue Jun 29, 2022 · 2 comments
Closed

Comments

@jmattheis
Copy link

The return value of the sort + map somehow returns 3x the bin entry and omits the log/test thing.

package main

import (
	"fmt"

	"github.com/dop251/goja"
)

type Thing struct { Name string `json:"name"` }

func main() {
	vm := goja.New()
	vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))

	vm.Set("read", func(call goja.FunctionCall) goja.Value {
		return vm.ToValue([]Thing{
			{Name: "log"},
			{Name: "etc"},
			{Name: "test"},
			{Name: "bin"},
		})
	})
	// This one works
	// vm.Set("read", func(call goja.FunctionCall) goja.Value {
	// 	x := func(name string) goja.Value {
	// 		o := vm.NewObject()
	// 		o.Set("name", name)
	// 		return o
	// 	}
	// 	return vm.NewArray(x("log"), x("etc"), x("test"), x("bin"))
	// })

	ret, err := vm.RunString(`
read().sort((a, b) => a.name.localeCompare(b.name)).map((x) => x.name);
`)
	if err != nil {
		panic(err)
	}
	fmt.Println(ret.Export())
	// outputs: [bin etc bin bin]
}
@dop251
Copy link
Owner

dop251 commented Jun 29, 2022

This is a very tricky one... With the current implementation (after #378) accessing an array or a struct element of a wrapped Go reflect value returns a reference to that element, not a copy. So when you do this:

a[0].name = '0';
a[1].name = '1';
let tmp = a[0];
a[0] = a[1];

tmp.name will be '1'., because assigning to a[0] changed the referenced value (which tmp also referring to).

Fixing this is non-trivial, because if a[0] returned a copy rather than a reference, a[0].name = '...' would not work as expected (it would create a copy of a[0], set its name and immediately lose the copy). Not to mention that any dereference (even just for reading a property) would result in a copy.

Same thing applies to nested structs, and considering anonymous fields are also nested, the effects would be even more bizarre.

It's easy to fix it so that it at least works for pointers (i.e. it would work for []*Thing), but with non-pointers the only thing I can think of is some kind of copy-on-write mechanism which would require to have a cache of already accessed array elements and struct fields and changing their underlying reflect value to a copy in case the element is overwritten.

If anyone has any suggestions, let me know.

@dop251 dop251 closed this as completed in b1618db Jul 5, 2022
@dop251
Copy link
Owner

dop251 commented Jul 5, 2022

Ok, I think this should sort it (and hopefully shouldn't break anything). Feedback is welcome as usual.

dop251 added a commit that referenced this issue Jul 5, 2022
Gabri3l pushed a commit to mongodb-forks/goja that referenced this issue Sep 1, 2022
Gabri3l pushed a commit to mongodb-forks/goja that referenced this issue Sep 1, 2022
Gabri3l added a commit to mongodb-forks/goja that referenced this issue Mar 15, 2023
* Fixed panic in newArrayFromIter when the iterator is already closed. Fixes dop251#375
* Fixed panic when parsing invalid object property keys. Fixes dop251#376.
* Fixed accidental shadowing in the else branches of type assertion
* Fixed defineProperty("length") for arrays. Improved detection of non-standard array configurations. Upgraded tc39 tests.
* Return true values of struct fields or reflect slice elements, rather than pointers to them. Closes dop251#378.
* Upgraded dependencies. Closes dop251#380.
* Implemented exponentiation expressions. Closes dop251#381.
* Enabled tests that use ** operator. Some array fixes as a result.
* Implemented nullish coalescing operator (??). Closes dop251#382.
* Implemented `{Array,String,%TypedArray%}.prototype.at` (dop251#384)e7c2872c8)
* Fixed callee expressions in optional chains. Fixes dop251#385.
* Do not use fmt.Sprintf() for plain error strings. Fixes dop251#388.
* Implemented 'copy-on-change' mechanism for inner compound values. Fixes dop251#403.
* Fixed objectGoReflect equality. See dop251#403
* Don't clear interrupt until the stack is empty (dop251#405)
* test: skip Promise based tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants