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
GopherJS generics support #1013
Comments
I think it's planned for |
One of the helper functions requires generics to work correctly. Until they are properly supported we have to stub it out. Generics support is tracked in gopherjs#1013.
One of the helper functions requires generics to work correctly. Until they are properly supported we have to stub it out. Generics support is tracked in gopherjs#1013.
One of the helper functions requires generics to work correctly. Until they are properly supported we have to stub it out. Generics support is tracked in gopherjs#1013.
Even though the compiler was usually able to compile them into _something_, the code was likely incorrect in all cases. To prevent users from tripping up on that the compiler will return an error if it encounters type params until gopherjs#1013 is resolved.
Even though the compiler was usually able to compile them into _something_, the code was likely incorrect in all cases. To prevent users from tripping up on that the compiler will return an error if it encounters type params until gopherjs#1013 is resolved.
One of the helper functions requires generics to work correctly. Until they are properly supported we have to stub it out. Generics support is tracked in gopherjs#1013.
Even though the compiler was usually able to compile them into _something_, the code was likely incorrect in all cases. To prevent users from tripping up on that the compiler will return an error if it encounters type params until gopherjs#1013 is resolved.
Even though the compiler was usually able to compile them into _something_, the code was likely incorrect in all cases. To prevent users from tripping up on that the compiler will return an error if it encounters type params until gopherjs#1013 is resolved.
I've been doing some research on potential ways to support generics in gopherjs and I think it would be good for me to share my thoughts. This isn't a proper design document, but I hope it would help us start a discussion and perhaps make it easier for people to contribute towards this :) I'll start by sharing links to a few important resources I used:
These design docs deserve the most attention here, as they describe options the Go team considered for their own implementation. The first two docs present two ends of the spectrum: generate a specialized copy of executable code for each instantiation and generate one copy of universal code, delegating type-specific logic to helper functions passed via the dictionary. Ultimately, the Go team went with the hybrid approach as a trade off between binary size, compile time and complexity. I think for GopherJS the tradeoff looks significantly differently. For us, output size is a critical concern, but things like stack allocation or memory management are much less relevant, since we delegate those to the javascript runtime. Also on a technical level, stenciling requires the ability to add function instantiations to the imported packages and deduplicate them at the linking stage. This is solvable, but GopherJS is not very well set up for this and will require a significant refactoring to make it possible. This makes me strongly favor the dictionaly-based design. However, we can take advantage of our dynamic runtime environment and simplify our implementation even further by delaying generics instantiation to the runtime. That way, the compiler won't have to generate dictionaries and subdictionaries from the original design, or include them into the generated binary. Consider this non-generic type: type Foo struct{ f string }
func (f Foo) Bar() { println("bar") } Here is a slightly abbreviated version of the code it compiles into: Foo = $newType(0, $kindStruct, "main.Foo", true, "main", true, function(f_) { /* field initialization code */ });
Foo.ptr.prototype.Bar = function() { /* Foo.Bar() code */ };
Foo.methods = [{prop: "Bar", name: "Bar", pkg: "", typ: $funcType([], [], false)}];
Foo.init("main", [{prop: "f", name: "f", embedded: false, exported: false, typ: $String, tag: ""}]);
main = function() {
var f = new Foo.ptr(""); // Using the type!
$clone(f, Foo).Bar();
}; The important insight here is that we are already delaying type instantiation to the runtime, just before we begin program execution. Why not do the same for generic types, except do this when the generic type is demanded by the program? Consider the following generic type: type Foo[T any] struct{ f T }
func (f Foo[T]) Bar() { println("bar") } Instead of a concrete instantiation we could generate a factory function for it: Foo = function(T) {
var instance = $newType(0, $kindStruct, "main.Foo[" + T.typeString +"]", true, "main", true, function(f_) { /* field initialization code */ });
// Note that the method body closes over the type T and can use it for reflection/whatever purposes.
instance.ptr.prototype.Bar = function() { /* Foo.Bar() code */ };
instance.methods = [{prop: "Bar", name: "Bar", pkg: "", typ: $funcType([], [], false)}];
// Note "typ: T" on the next line — using type parameter!
instance.init("main", [{prop: "f", name: "f", embedded: false, exported: false, typ: T, tag: ""}]);
return instance;
}
main = function() {
var f = new (Foo($String).ptr)(""); // Calling the factory to get the constructor for the new operator.
$clone(f, Foo($String)).Bar();
}; Of course, this is a simplified example. In the real implementation the factory function will cache and reuse instances of a generic type to improve performance and avoid "same but different" kinds of type conflicts. Similar approach could be applied to standalone generic functions. Another issue we need to address is type-specific operations. The same operator may mean different things for different types, for example:
The original design solves this by introducing dictionaries that have a virtual table of functions that can be used with the generic type. In our implementation we can use the type instance itself in that role. For example we could define Finally, we also need to be able to construct types in generic code from passed type parameters, for example: func Baz[T any]() {
x = Foo[[]T]{}
//...
} That could be compiled into something like this: Baz = function(T) {
return function() {
x = new (Foo($silceType(T)))();
// ...
}
} So that's as far as I got. It's getting late here, so apologies for all the typos I'm sure I've made in this text |
nevkontakte commentedApr 5, 2021
•
edited
It appears that generic (a.k.a. type parameters support) is well underway in the upstream Go (dev.typeparams branch) and is likely to be included in 1.18. Which means GopherJS also needs to support this, ideally at the same time as Go 1.18, which I expect is a lot of work. Does anyone have even approximate sense of what it would take to support generics? @flimzy @dmitshur @neelance.
For now, I'm filing this issue to look for a volunteer (or several) to contribute this support. By myself I almost certainly won't be able to implement this, considering that we have several other important features from earlier releases to take care of, such as modules and
embed
.Important TODOs:
encoding/xml
tests once generics are supported.checkStringParseRoundTrip()
innet/netip/fuzz_test.go
.compareSlices()
override ingo/doc/doc_test.go
.TestIssue50208
inreflect
package.The text was updated successfully, but these errors were encountered: