Repository: go
Issue: proposal: cmd/vet: add declarative support for identifying functions for printf check
Labels: Proposal

# Goals

Add support for `printf` (and `errorf`) for function pointers and interfaces:

```go
func Infof(string, any...)
func Errorf(string, any...)

func NewLogger() func(string, any...) {}
func SetLogger(func(string, any...))

type Logger interface {
  Infof(string, any...)
}

var infof func(string, any...)
```


# Why change is needed

Currently the implementation does work by (a) having either functions mapping a specified list (which can be provided to `vet` via commandline argument) or by (b) calling the `fmt.Printf` or `fmt.Print` function.

a) Providing a specific function mapping does not work with modules that are not imported within the source file. Also there is no config file which could be use provide this list project-wide or inherit it from imported modules. For the full mapping list, see [here](https://cs.opensource.google/go/x/tools/+/9ff31a5780b2d96e9f596fe09dc13f1510a84542:go/analysis/passes/printf/printf.go;l=345).

b) Calling the `fmt.Printf` or `fmt.Print` function directly does not work for arguments, return values, and interfaces. 


c) Also IDEs like VS Code and Goland add support for `vet`-based rules. Adding this feature to `vet` will likely also trigger changes in the IDE support.

For more details on the implementation, see [here](https://cs.opensource.google/go/x/tools/+/9ff31a5780b2d96e9f596fe09dc13f1510a84542:go/analysis/passes/printf/printf.go;l=44).


# Solutions

There are different ways to solve this issue. I would like to brainstorm different ways, so please feel free to add proposals in the comments.

## I) Named Arguments and Return Values

Use named arguments and return values by either using `printfFormat` or `errorfFormat` as argument name:

```go
func Infof(printfFormat string, any...)
func Errorf(errorfFormat string, any...)

func NewLogger() func(printfFormat string, any...) {}
func SetLogger(func(printfFormat string, any...))

type Logger interface {
  Infof(printfFormat string, any...)
}

var infof func(printfFormat string, any...)
```

Pros: Easy to use, unlikely to cause side effects with existing code, clean upgrade path for existing modules, does not increase required Go version
Cons: Bloated code, argument names where non should be required


## II) Add new `fmt.FormatString` and `errors.FormatString`

Add new `fmt.FormatString` and `errors.FormatString` types (which point to `string`) to declare the functions/methods as formatted:

```go
func Infof(fmt.FormatString, any...)
func Errorf(errors.FormatString, any...)

func NewLogger() func(fmt.FormatString, any...) {}
func SetLogger(func(fmt.FormatString, any...))

type Logger interface {
  Infof(fmt.FormatString, any...)
}

var infof func(fmt.FormatString, any...)
```

Pros: Safe from side effects with existing code
Cons: Impact on reflection, requires build tag on upgrading code, requires addition to core packages, requires upgrade to higer Go version

## III) Add declaration list to `go.mod`

Allow additional printf-like functions to be declared within the `go.mod` file and have those settings inherited from imported modules. Package names are resolved relative to the module root.

```
module hello/world

go 1.21

vet printf logging.Infof
vet errorf logging.Errorf

vet printf logging.InfofFunc
```

Examples:

```go
package logging

func Infof(string, any...)
func Errorf(string, any...)

type InfofFunc func(string, any...)

func NewLogger() InfofFunc {}
func SetLogger(InfofFunc)

type Logger interface {
  Infof(string, any...)
}

var infof InfofFunc
```

Pros: no side effects on existing code
Cons: requires upgrade to higher Go version

## IV) Add new `go.vet` config file

Allow additional printf-like functions to be declared within a new `go.vet` file and have those settings inherited from imported modules. Package names are resolved relative to the module root. (Similar to III but with less issues.)

The file should always be backward compatible and unknown and illegal entries should be ignored with a warning.

```
printf (
  logging.Infof
  logging.InfofFunc
)

errorf logging.Errorf
```

Pros: no side effects on existing code, clean upgrade path for existing modules, does not increase required Go version, could be used for additional `go tool vet` settings
Cons: new file added to Go universe



Please let me know what you think. 

Best Regards, Felix