Skip to content
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

x/tools/go/analysis/passes/printf: simplify fmt.Sprintf("%s", x) -> x.String() et al #73027

Closed
kwjw opened this issue Mar 25, 2025 · 5 comments
Closed
Labels
FeatureRequest Issues asking for a new feature that does not need a proposal. gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository.
Milestone

Comments

@kwjw
Copy link

kwjw commented Mar 25, 2025

There are surprisingly many calls like

fmt.Sprintf("%s", x) // x implements Stringer
// write x.String() instead

fmt.Sprintf("%s", b) // b is []byte
// write string(b) instead

fmt.Sprintf("%s", err) // err implement error
// write err.Error() instead

fmt.Errorf("%s", x) // x is a string or implements Stringer
// write errors.New(x) instead

// or even,

fmt.Sprintf("%s", y.Name) // y.Name is a string
// write y.Name instead

As I write this, I feel that somewhere in the history of Go this surely has come up before, but I couldn't find anything in the issue tracker or with some quick code searches.

In Google there seem to be thousands of calls like these. A rewrite would make the code more readable and more performant, so why not nudge users in this direction?

@gopherbot gopherbot added Tools This label describes issues relating to any tools in the x/tools repository. gopls Issues related to the Go language server, gopls. labels Mar 25, 2025
@gopherbot gopherbot added this to the Unreleased milestone Mar 25, 2025
@gabyhelp gabyhelp added the FeatureRequest Issues asking for a new feature that does not need a proposal. label Mar 25, 2025
@adonovan
Copy link
Member

It's true that in many cases these expressions could be simplified, but the benefits seem fairly marginal. Let's take them in turn:

  1. fmt.Sprintf("%s", x) // x implements Stringer; write x.String() instead
    This transformation is not sound: you have to know the dynamic type of x; if it implements fmt.Formatter, then the behavior may differ from x.String().

  2. fmt.Sprintf("%s", b) // b is []byte; write string(b) instead
    This one is a win for clarity and potentially a win for performance since it avoids escaping the byte slice (though most byte slices escape, I suspect).

  3. fmt.Sprintf("%s", err) // err implement error; write err.Error() instead
    Not safe, for the same reason as case 1.

  4. fmt.Errorf("%s", x) // x is a string or implements Stringer; write errors.New(x) instead
    If x is a string then errors.New is equivalent, but in Google Go readability reviews the style question has long been settled that we shouldn't correct fmt.Errorf to errors.New; it's just not worth the trouble. If x is a Stringer than errors.New cannot be used (without a call to String, but see case 1).

  5. fmt.Sprintf("%s", y.Name) // y.Name is a string; write y.Name instead
    This one seems like a win for clarity and performance; the Sprintf call is completely redundant, and perhaps just a mistake.

I wonder how common cases 5 and 2 are. I suspect rare, compared to uses of %s within a larger format string.

@adonovan adonovan changed the title x/tools/gopls/internal/analysis: add check for fmt.Sprintf("%s", x) -> x.String() x/tools/go/analysis/passes/printf: simplify fmt.Sprintf("%s", x) -> x.String() et al Mar 25, 2025
@gabyhelp
Copy link

@kwjw
Copy link
Author

kwjw commented Mar 25, 2025

This transformation is not sound: you have to know the dynamic type of x; if it implements fmt.Formatter, then the behavior may differ from x.String()

Could 1 and 4 be rewritten when x has a statically known type? (With errors we are rarely given a statically known type in these expressions, so there's nothing to be done for 3.) Or do we want to leave open the possibility that someone intends to later make their type implement Formatter?

I wonder how common cases 5 and 2 are. I suspect rare, compared to uses of %s within a larger format string.

I found a few dozen packages matching Printf("%s", "literal") or Printf("%s", "concatenated" + x).

There are also many hundreds of calls like Sprintf("%s", str), although many of these are members of some pattern alongside other calls. A contrived example

var (
  x = fmt.Sprintf("prefix-%s", a)
  y = fmt.Sprintf("prefix-%s-postfix", b)
  z = fmt.Sprintf("%s", c)
)

fmt.Sprintf("%s", x.GetXXXXXX()) is a somewhat common way to get the string value for a protobuf enum field.

fmt.Sprintf("%s", err) is also very common (and usually is meant to say err.Error(), I'm sure), but as you pointed out, this one can't be soundly rewritten.

@adonovan
Copy link
Member

Could 1 and 4 be rewritten when x has a statically known type? (With errors we are rarely given a statically known type in these expressions, so there's nothing to be done for 3.)

Yes, if you know the type the transformation is sound.

Or do we want to leave open the possibility that someone intends to later make their type implement Formatter?

I'm not worried about that.

I found a few dozen packages matching Printf("%s", "literal") or Printf("%s", "concatenated" + x).

Both of those are redundant with Print, but they're really not mistakes--perhaps the author is following a local style rule of "prefer Printf over Print{,ln}"--so they're out of scope for vet's printf checker. Ditto your other examples.

@kwjw
Copy link
Author

kwjw commented Mar 25, 2025

perhaps the author is following a local style rule of "prefer Printf over Print{,ln}"

That's a good call, something I hadn't considered. There is enough room for doubt in each of these cases that we should probably leave these calls alone.

@kwjw kwjw closed this as not planned Won't fix, can't repro, duplicate, stale Mar 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
FeatureRequest Issues asking for a new feature that does not need a proposal. gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests

4 participants