Skip to content

Rudimentary support for passing type parameters into generic functions #1161

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

Merged
merged 2 commits into from
Oct 24, 2022

Conversation

nevkontakte
Copy link
Member

Instead of generating an independent function instance for every combination of type parameters at compile time we construct generic function instances at runtime using "generic factory functions". Such a factory takes type params as arguments and returns a concrete instance of the function for the given type params (type param values are captured by the returned function as a closure and can be used as necessary).

Here is an abbreviated example of how a generic function is compiled and called:

// Go:
func F[T any](t T) {}
f(1)

// JS:
F = function(T){ return function(t) {}; };
F($Int)(1);

This approach minimizes the size of the generated JS source, which is critical for the client-side use case, at the cost of runtime
performance. See #1013 (comment) for the detailed description.

Note that the implementation in this commit is far from complete:

  • Generic function instances are not cached.
  • Generic types are not supported.
  • Declaring types dependent on type parameters doesn't work correctly.
  • Operators (such as +) do not work correctly with generic arguments.

Updates #1013.

…neration.

These functions provide mappings from Go entities (variables, types,
functions, etc.) to JS identifiers assigned to them. This commit adds
documentation comments and renames a couple of methods to better match
their role.
@nevkontakte nevkontakte requested a review from flimzy October 15, 2022 18:10
@nevkontakte
Copy link
Member Author

/cc @paralin for additional review.

@nevkontakte
Copy link
Member Author

With this PR 24 out of 134 typeparams test cases pass, up from 14.

@paralin

This comment was marked as outdated.

@nevkontakte
Copy link
Member Author

@paralin what exactly are you trying to build? Standard library tests seem to be passing successfully on CI and the canonical TodoMVC app builds for me as well 😕

@paralin
Copy link
Contributor

paralin commented Oct 15, 2022

@nevkontakte have you tried go:embed yet?

@nevkontakte
Copy link
Member Author

I don't have any gopherjs apps that use it yet, but this seems to work for me:

$ go install github.com/gopherjs/gopherjs && gopherjs test embed/internal/embedtest
PASS
ok      embed/internal/embedtest        0.325s

@paralin
Copy link
Contributor

paralin commented Oct 15, 2022

@nevkontakte Okay sorry - my Go install was broken, after using a from-source go 1.18.7:

https://github.com/aperturerobotics/controllerbus/tree/master/example/hello-world

cd ./example/hello-world
gopherjs run -v ./*.go

panic: interface conversion: ast.Expr is *ast.IndexExpr, not *ast.Ident

goroutine 1 [running]:
github.com/gopherjs/gopherjs/compiler/astutil.FuncKey(...)
        /gopherjs/compiler/astutil/astutil.go:72
github.com/gopherjs/gopherjs/build.parseAndAugment({0xae87d8, 0xc0002044c0}, 0xc001114550, 0x0, 0xedadd1c4a?)
        /gopherjs/build/build.go:255 +0x15f1
github.com/gopherjs/gopherjs/build.(*Session).BuildPackage(0xc0002230e0, 0xc001114550)
        /gopherjs/build/build.go:695 +0x352
github.com/gopherjs/gopherjs/build.(*Session).buildImportPathWithSrcDir(0xc0002230e0, {0xc001e0af41?, 0x0?}, {0xc00022e3c0?, 0xe
add1c4a?})
        /gopherjs/build/build.go:635 +0x96
github.com/gopherjs/gopherjs/build.(*Session).BuildPackage(0xc0002230e0, 0xc001418140)
        /gopherjs/build/build.go:666 +0xb97
github.com/gopherjs/gopherjs/build.(*Session).buildImportPathWithSrcDir(0xc0002230e0, {0xc000d05a81?, 0xdde720?}, {0xc0012881b0?
 0xedadd1c4a?})
        /gopherjs/build/build.go:635 +0x96
github.com/gopherjs/gopherjs/build.(*Session).BuildPackage(0xc0002230e0, 0xc001e3de00)
        /gopherjs/build/build.go:666 +0xb97
github.com/gopherjs/gopherjs/build.(*Session).buildImportPathWithSrcDir(0xc0002230e0, {0xc000cc6091?, 0x0?}, {0xc000036140?, 0xe
add1c4a?})
        /gopherjs/build/build.go:635 +0x96
github.com/gopherjs/gopherjs/build.(*Session).BuildPackage(0xc0002230e0, 0xc001f86050)
        /gopherjs/build/build.go:666 +0xb97
github.com/gopherjs/gopherjs/build.(*Session).buildImportPathWithSrcDir(0xc0002230e0, {0xc000218bc1?, 0xdde720?}, {0xc000218400?
 0x346311bbd64?})
        /gopherjs/build/build.go:635 +0x96
github.com/gopherjs/gopherjs/build.(*Session).BuildPackage(0xc0002230e0, 0xc0002262d0)
        /gopherjs/build/build.go:666 +0xb97
github.com/gopherjs/gopherjs/build.(*Session).BuildFiles(0xc0002230e0, {0xc000214880, 0x7, 0x8}, {0xc000242060, 0x5e}, {0xc00021
140?, 0xb?})
        /gopherjs/build/build.go:604 +0xc85

I get this error with a more complicated package as well.

Thanks for your hard work on this!

@nevkontakte
Copy link
Member Author

nevkontakte commented Oct 15, 2022

@paralin maybe something has changed in reflectlite between 1.18.5 (which our CI currently tests against) and 1.18.7 you have. This kind of breakages a rare for patch-level versions, but not impossible. Could you file a separate issue for this?

@paralin
Copy link
Contributor

paralin commented Oct 15, 2022

@nevkontakte The reflect error is gone after I fixed my Go install. Now it's the new error:

panic: interface conversion: ast.Expr is *ast.IndexExpr, not *ast.Ident

github.com/gopherjs/gopherjs/compiler/astutil.FuncKey(...)
        /gopherjs/compiler/astutil/astutil.go:72

@nevkontakte
Copy link
Member Author

@paralin that error is probably because you are trying to compile a generic type, which is not supported yet. This PR only adds some support for generic functions, and even that is incomplete :)

Instead of generating an independent function instance for every
combination of type parameters at compile time we construct generic
function instances at runtime using "generic factory functions". Such a
factory takes type params as arguments and returns a concrete instance
of the function for the given type params (type param values are
captured by the returned function as a closure and can be used as
necessary).

Here is an abbreviated example of how a generic function is compiled and
called:

```
// Go:
func F[T any](t T) {}
f(1)

// JS:
F = function(T){ return function(t) {}; };
F($Int)(1);
```

This approach minimizes the size of the generated JS source, which is
critical for the client-side use case, at the cost of runtime
performance. See gopherjs#1013 (comment)
for the detailed description.

Note that the implementation in this commit is far from complete:

  - Generic function instances are not cached.
  - Generic types are not supported.
  - Declaring types dependent on type parameters doesn't work correctly.
  - Operators (such as `+`) do not work correctly with generic
    arguments.
@paralin
Copy link
Contributor

paralin commented Oct 15, 2022

@nevkontakte Ah ok, I thought there were no generic types in that particular package (hello-world) but I guess there might be somewhere.

@nevkontakte nevkontakte merged commit 73a9b7b into gopherjs:generics Oct 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants