Subject: bug#61368: [PATCH] Extend go-ts-mode with support for pre-filling return statements

From: Theodor Thornhill <[email-masked]>
Date: Wed, 08 Feb 2023 20:20:27 +0100

Evgeni Kolev <[email-masked]> writes:

> [CC Randy, author of go-ts-mode, Theo, who asked to keep the patches coming :)]
>

:-)

> This is a patch which adds support to go-ts-mode to insert
> context-aware return statements for the current function. The return
> statements are pre-filled with the Go zero values of the output
> parameters.
>
> For example, all these return statements are inserted by M-x
> go-ts-mode-insert-return:
>
> ```
> func f() (x, y, z int) {
>     return 0, 0, 0
> }
>
> func exotic() (bool, int, myStruct, *int, chan bool, error) {
>     return false, 0, myStruct{}, nil, nil, err
> }
>
> func closure(numbers []int) {
>     sort.Slice(numbers, func(i, j int) bool {
>         return false
>     })
> }
> ```

Cool!

>
> The command go-ts-mode-insert-return is experimentally bound to a key
> C-c C-r ("r" as return statement). It's a user error to run C-c C-r
> outside of a function body.
>
> Customization: when the output params contain "error" the pre-filled
> text is "err". A customization variable is added to allow the user to
> pick a different value, for example they might pick `fmt.Errorf("...:
> %w", err)` which will result in error wrapping:
> ```
> func f() (int, error) {
>     return 0, fmt.Errorf("...: %w", err)
> }
> ```
>
> Personally, I'll use yasnippet instead of C-c C-r. Something like this:
> ```
>  # -*- mode: snippet -*-
>  # name: ret
>  # key: ret
>  # type: command
>  # --
>
>  (let ((go-ts-mode-error-zero-value "${1:fmt.Errorf(\"${2:format}:
> %w\", err)}"))
>    (yas-expand-snippet (go-ts-mode-return)))
> ```
>
> I'm open to suggestions about how to best expose this functionality to
> the user. I think a snippet makes the most sense, but there's no
> standard way for major modes to expose snippets as far as I'm aware.
> It's possible to tweak C-c C-r to call (yas-expand-snippet) if
> available, otherwise call (insert). In general, I don't feel strong
> about the C-c C-r key binding, but I didn't have a better idea.
>

How about using tempo or skeleton as fallbacks when yasnippet isn't
installed? 

> Known bug: this example does not work, fails with error "Unknown Go
> type "[int]int"":
> ```
> func notOk() (int, map[int]int) {
> }
> ```
> The root cause is an issue with the tree-sitter-go parser. The bug has
> been reported here
> https://github.com/tree-sitter/tree-sitter-go/issues/107
>
> Known limitation: unfamiliar types are assumed to be structs. This
> assumption does not work for aliased types. For example:
> ```
> type MyInt = int
>
> func alias() MyInt {
>     return MyInt{}
> }
> ```
> Above the correct return statement is "return MyInt(0)". My assumption
> is that type aliases of primitive types are rare in Go codebases. In
> these rare cases, the user is expected to replace "MyInt{}" with
> "MyInt(0)", or not use C-c C-r at all.
>
> Inspired by: this emacs conf talk "08:21 Intelligent templates"
> https://emacsconf.org/2022/talks/treesitter/
>
> Feedback is more than welcome! In particular:
> - how to best expose the functionality - key binding, snippet (how
> exactly?), something else?
>

Personally I think this should be a contrib to gopls as a code action so
that others can benefit from it.  Is upstreaming it to gopls too hard?

Theo