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

From: Evgeni Kolev <[email-masked]>
Date: Wed, 8 Feb 2023 17:27:58 +0200

[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
    })
}
```

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.

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?

Evgeni