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/gopls/internal/analysis: add "modernizer" analyzers #70815

Open
13 of 17 tasks
adonovan opened this issue Dec 12, 2024 · 41 comments
Open
13 of 17 tasks

x/tools/gopls/internal/analysis: add "modernizer" analyzers #70815

adonovan opened this issue Dec 12, 2024 · 41 comments
Assignees
Labels
Analysis Issues related to static analysis (vet, x/tools/go/analysis) FeatureRequest Issues asking for a new feature that does not need a proposal. gopls/analysis Issues related to running analysis in gopls gopls Issues related to the Go language server, gopls. Refactoring Issues related to refactoring tools Tools This label describes issues relating to any tools in the x/tools repository.
Milestone

Comments

@adonovan
Copy link
Member

adonovan commented Dec 12, 2024

Between generics, iterators, min and max, new standard packages such as slices, maps, and cmp, there are a large number of new ways to write Go code more concisely and no less clearly. We should develop "modernizer" analyzer(s) that identify such opportunities and offer suggested fixes.

Many of these are already implemented in staticcheck (see S1000 et seq); we would just need to enable them.

When the appropriate fix is to inline a call to a deprecated function whose body illustrates the recommended approach (e.g. a call to a more modern function, as in the case of the functions in the golang.org/x/exp/maps package), these will be handled by annotating the deprecated function as prescribed by proposal #32816, and letting the inliner take care of it.

Examples:

  • b.N → b.Loop in Benchmarks (https://go.dev/cl/638337)
  • interface{} → any (CL 636276)
  • explicit loops → slices.{Equal,Compare,Index,Contains}{,Func}. The challenge is treating the return false and return true steps in the slices package as wildcards that match "equal and opposite" actions--not just return statements--in the user code. (CL 640576)
  • explicit loops → maps.{Clone,Collect.Insert,Copy} (https://go.dev/cl/638875)
  • maps.Keys(m) + slices.Collect (or loop equivalent of Keys+Collect) + sort -> slices.Sorted(maps.Keys(m)). Eliminate temporaries when used once in range _. Downside: de-optimizes preallocation of correct array size; perhaps best left until resolution of proposal: maps: helper to loop over the maps in predictable order #68598.
  • various append + s[::] combinations -> slices.Grow, Clip, Concat & Clone (https://go.dev/cl/637735), etc.
  • append(s[:i], s[i+k:]...) -> slices.Delete(s, i, i+k), where k is a non-negative constant
  • exp/{slices,maps} -> std {slices,maps} (see CL 635680 + issue cmd/fix: automate migrations for simple deprecations #32816)
  • exp/maps.Clear → clear etc (https://go.dev/cl/635680)
  • if/else → min/max (CL 635777)
  • awkward logic in slice.Sort's less function → if cmp := cmp.Compare(x, y); cmp != 0 { return cmp < 0 }
  • sort.Slice -> slices.Sort, avoiding the less func when it is the natural order (https://go.dev/cl/635681)
  • sort.Slice -> slices.SortFunc, eliminating the indices
  • json:omitempty → json:omitzero for basic types
  • SetFinalizer → AddCleanup
  • use strings.Lines instead of strings.Split("\n") (but: "\r"?!) or text.scanner(strings.NewReader)/Scan/Text
  • []byte(fmt.Sprintf...)fmt.Appendf(nil, ...) (https://go.dev/cl/638436)
  • implementing {Text,Binary}Appender for types that currently implement {Text,Binary}Marshaler
  • replacing ctx, cancel := context.WithCancel(context.Background()); defer cancel() in tests with ctx := t.Context().
  • use cipher.NewGCMWithRandomNonce where possible (example)
  • reflect.DeepEqual(x, y []comparable) -> slices.Equal(x, y)
  • for range strings.Split -> for range strings.SplitSeq (CL 647438)
  • for i := 0; i < n; i++ {} => for i := range n {} (CL 646916)

They should of course all respect the effective version of Go determined by go.mod and build tags.

@aclements

@adonovan adonovan added gopls/analysis Issues related to running analysis in gopls Analysis Issues related to static analysis (vet, x/tools/go/analysis) Refactoring Issues related to refactoring tools labels Dec 12, 2024
@gopherbot gopherbot added the gopls Issues related to the Go language server, gopls. label Dec 12, 2024
@zephyrtronium
Copy link
Contributor

Would it make sense for some of these to be depreciations instead? Particularly looking at x/exp/maps.Clear and the go/* upgrades.

@seankhliao seankhliao changed the title gopls/internal/analysis: add "modernizer" analyzers x/tools/gopls/internal/analysis: add "modernizer" analyzers Dec 13, 2024
@gopherbot gopherbot added the Tools This label describes issues relating to any tools in the x/tools repository. label Dec 13, 2024
@gopherbot gopherbot added this to the Unreleased milestone Dec 13, 2024
@adonovan
Copy link
Member Author

Would it make sense for some of these to be depreciations instead? Particularly looking at x/exp/maps.Clear and the go/* upgrades.

Even if we were to deprecate maps.Clear (which is perfectly fine function), users would still want a way to migrate their code away from it. So I see deprecation as independent. And in many cases the pattern being "modernized" is not just a simple use of one symbol that could be deprecated.

@findleyr findleyr modified the milestones: Unreleased, gopls/backlog Dec 13, 2024
@findleyr findleyr added the FeatureRequest Issues asking for a new feature that does not need a proposal. label Dec 13, 2024
@aclements
Copy link
Member

Some of these (maybe all 😄 ) are subtler than they appear on the tin.

b.N → b.Loop in Benchmarks

The core of this is just replacing for range b.N with for b.Loop(), but a major point of b.Loop() is that it allows other simplifications, like you don't have to reset the timer after setup, or go out of your way to cache complex setup steps, or stop the timer after the loop for expensive cleanup steps.

SetFinalizer → AddCleanup

Here you have to identify the underlying "resource" that's being freed by the finalizer since that's what AddCleanup attaches.

use strings.Lines

There are so many patterns for splitting a string into lines right now. I'm really curious how we'd pick up on that.

implementing {Text,Binary}Appender for types that currently implement {Text,Binary}Marshaler

In addition to creating Append methods, ideally this should also rewrite MarshalText/MarshalBinary to use the new Append methods.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/635777 mentions this issue: gopls/internal/analysis/modernize: quick fixes to modernize Go code

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/635680 mentions this issue: slices,maps: delegate go go1.21 packages of the same name

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/635681 mentions this issue: gopls/internal/analysis/modernize: sort.Slice -> slices.Sort

gopherbot pushed a commit to golang/exp that referenced this issue Dec 15, 2024
This change replaces the body of almost every function in the package
with a call to its corresponding std function of the same name
and--assumedly--semantics.

If proposal golang/go#32816 is accepted, we will annotate each function
with a "//go:fix inline" comment so that automated tooling
such as golang.org/x/tools/internal/refactor/inline/analyzer
can automatically inline the wrappers.

(This CL is deemed to fix golang/go#70717 because it reduces maps.Clear
to the same status as all the other maps functions.)

Updates golang/go#32816
Updates golang/go#70815
Fixes   golang/go#70717

Change-Id: I85246df07f903af97673b80024acdcae057b9f63
Reviewed-on: https://go-review.googlesource.com/c/exp/+/635680
Auto-Submit: Alan Donovan <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/636276 mentions this issue: gopls/internal/analysis/modernize: replace interface{} with any

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/637735 mentions this issue: gopls/internal/analysis/modernize: append(zerocap, s...) -> slices.Clone(s)

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/638337 mentions this issue: gopls/internal/analysis/modernize: b.N -> b.Loop()

gopherbot pushed a commit to golang/tools that referenced this issue Dec 23, 2024
This CL is the start of an analyzer that offers a suite of code
transformations to modernize Go code, by taking advantage of
newer features of the language when it would clarify the code
to do so. Because it reports diagnostics on correct code,
its severity is downgraded to INFO.

Only one is implemented so far: the replacement of if/else
conditional assignments by a call to min or max.

+ relnote and test

Updates golang/go#70815

Change-Id: I686db57c2b95dfa399b44e5c6fdc478272cee084
Reviewed-on: https://go-review.googlesource.com/c/tools/+/635777
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
gopherbot pushed a commit to golang/tools that referenced this issue Dec 23, 2024
This CL adds a modernizer for calls to sort.Slice, where
the less function is the natural order:

   sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
   =>
   slices.Sort(s)

(Ditto SliceStable -> SortStable.)

The foldingrange test was tweaked to avoid triggering this fix.

Updates golang/go#70815

Change-Id: I755351c77aab8ddf7f21369c0f980e8d3334fc01
Reviewed-on: https://go-review.googlesource.com/c/tools/+/635681
Reviewed-by: Robert Findley <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Auto-Submit: Alan Donovan <[email protected]>
gopherbot pushed a commit to golang/tools that referenced this issue Dec 23, 2024
Also, modernize our tests in this manner.

Also, remove existing "useany" analyzer, which is subsumed by
this one. The fact that useany was off by default suggest that
perhaps modernize may need to be as well; but perhaps severity=INFO
will suffice.

Updates golang/go#70815

Change-Id: Iba1662993fb8a1c50b2d843ad9425bbddafc927f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/636276
Auto-Submit: Alan Donovan <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/638595 mentions this issue: gopls/internal/analysis/modernize: simplify append(base, s...)

gopherbot pushed a commit to golang/tools that referenced this issue Dec 23, 2024
This CL adds the first modernizer for the "slices" package.
It offers to replace an append (or tower of nested appends)
to a clipped slice by a call to slices.Concat or slices.Clone.

Examples:

append(append(append(x[:0:0], a...), b...), c...) -> slices.Concat(a, b, c)
append(append(slices.Clip(a), b...)               -> slices.Concat(a, b)
append([]T{}, a...)                               -> slices.Clone(a)
append([]string(nil), os.Environ()...)            -> os.Environ()

It takes care not to offer fixes that would eliminate
potentially important side effects such as to y's array in
this example:

  append(y[:0], x...)  -> slices.Clone(x)      (unsound)

However, it does not attempt to preserve the nilness of base
in append(base, empty...) when the second argument is empty.

Updates golang/go#70815

Change-Id: I5ea13bbf8bfd0b78a9fb71aef4c14848b860cc3a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/637735
Reviewed-by: Robert Findley <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
gopherbot pushed a commit to golang/tools that referenced this issue Dec 24, 2024
This CL adds a modernizer for go1.24's testing.B.Loop.
It replaces for/range loops over b.N by b.Loop() and
deletes any preceding calls to b.{Start,Stop,Reset}Timer.

Updates golang/go#70815

Change-Id: I504c9fed20beaec3c085c64f9ce42d3f6ad435d8
Reviewed-on: https://go-review.googlesource.com/c/tools/+/638337
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/638875 mentions this issue: gopls/internal/analysis/modernize: for...{m[k]=v} -> maps.Collect et al

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/638436 mentions this issue: gopls/internal/analysis/modernize: string formatting

@adonovan
Copy link
Member Author

Thanks for the exemplary bug report. I forgot to check the operator used by the AssignStmt--too easily done.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/642076 mentions this issue: gopls/internal/analysis/modernize: fix bug in mapsloop

gopherbot pushed a commit to golang/tools that referenced this issue Jan 10, 2025
A loop body of m[k] += v was spuriously matched because I
forgot to check the assignment operator.

+ test

Updates golang/go#70815

Change-Id: If74dcbb0ba920ebd475b1d0bd9191c9b44661a1a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/642076
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
Auto-Submit: Alan Donovan <[email protected]>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/642078 mentions this issue: gopls/internal/analysis/modernize: fix bug in minmax

gopherbot pushed a commit to golang/tools that referenced this issue Jan 10, 2025
Fix another bug related to not checking AssignStmt.Op.

+ test

Updates golang/go#70815

Change-Id: I426d27b4879d30f9fecb4d0f131b3c1a0b07773b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/642078
Reviewed-by: Robert Findley <[email protected]>
Auto-Submit: Alan Donovan <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
gopherbot pushed a commit to golang/tools that referenced this issue Jan 13, 2025
- Document Analyzer.Severity to describe heuristics for how severity
  should be determined.
- Filter out hint diagnostics for closed files. VS Code already
  suppresses hint diagnostics from the Problems tab, but other clients
  do not. This change makes the visibility of Hint diagnostics more
  similar across clients.
- Downgrade 'modernize' to Hint level severity.

Updates golang/go#70815

Change-Id: If93b57d25ed3eb8dc253a3c7ef016c4148086dc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/641796
Reviewed-by: Alan Donovan <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
gopherbot pushed a commit to golang/tools that referenced this issue Jan 13, 2025
This CL adds a modernizer pass for slices.Contains{,Func}.

Example:

func assignTrueBreak(slice []int, needle int) {
	found := false
	for _, elem := range slice { // want "Loop can be simplified using strings.Contains"
		if elem == needle {
			found = true
			break
		}
	}
	print(found)
}

=>

func assignTrueBreak(slice []int, needle int) {
	found := slices.Contains(slice, needle)
	print(found)
}

Updates golang/go#70815

Change-Id: I72ad1c099481b6c9ae6f732e2d81674a98b79a9f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/640576
Auto-Submit: Alan Donovan <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Commit-Queue: Alan Donovan <[email protected]>
@dsnet
Copy link
Member

dsnet commented Jan 14, 2025

Would #69820 be a good candidate for a modenizer?

@adonovan
Copy link
Member Author

adonovan commented Jan 14, 2025

Would #69820 be a good candidate for a modenizer?

It's certainly an easy thing to implement. I'm not sure how frequent it is. The summary is:

var slice []T
const k
*(*[k]T)(slice)  =>  [k]T(slice)  // when used as rvalue

https://go.dev/cl/642815 contains a quick stab.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/642815 mentions this issue: gopls/internal/analysis/modernize: simplify *(*[k]T)(x) -> ([k]T)(x)

@findleyr
Copy link
Member

@adonovan I encountered a bug in the slices.Contains modernizer today.

Modernizing using slices.Contains here results in an error:
https://cs.opensource.google/go/x/tools/+/master:gopls/internal/golang/highlight.go;l=348;drc=344e48255740736de8c8277e9a286cf3231c7e13

		case *ast.CallExpr:
			// If cursor is an arg in a callExpr, we don't want control flow highlighting.
			if i > 0 {
				for _, arg := range n.Args {
					if arg == path[i-1] {
						return
					}
				}
			}

Error is: S (type []ast.Expr) does not satisfy ~[]E.
For now, I think we need to check that the types in the match condition are identical.

@adonovan
Copy link
Member Author

Thanks; I split this out into its own issue: #71313

gopherbot pushed a commit to golang/tools that referenced this issue Jan 17, 2025
Adds a new modernizer that suggests removing
or replacing instances of "omitempty" on struct
fields with "omitzero."

Example (before):

type Foo struct {
  A struct{ b int } `json:"name,omitempty"
}

Example (after - replace):

type Foo struct {
  A struct{ b int } `json:"name,omitzero"
}

Example (after - remove):

type Foo struct {
  A struct{ b int } `json:"name"
}

Updates golang/go#70815

Change-Id: I7d651880340d24929ea5cae4751557a1f60e5f8e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/640041
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
@findleyr
Copy link
Member

Thanks; I split this out into its own issue: #71313

Made that a sub-issue of this one. Neat! We're living in the future.

gopherbot pushed a commit to golang/tools that referenced this issue Jan 17, 2025
Adds a new modernizer that suggests replacing instances
of append(s[:i], s[i+k:]...) with slices.Delete(s, i, i+k)

Handles other variations like append(s[:i-1], s[i:]...)
and append(s[:i+2], s[i+3:]...)

Updates golang/go#70815

Change-Id: I71981d6e8d6973bca17153b95f2eb9f1f229522d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/641357
Reviewed-by: Alan Donovan <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
@josharian
Copy link
Contributor

A modernization I often do by hand is using {strings,bytes}.Cut{,Prefix,Suffix}.

@adonovan
Copy link
Member Author

Would [array to slice conversions] be a good candidate for a modenizer?
https://go.dev/cl/642815 contains a quick stab.

It doesn't turn up a single instance in the entire k8s codebase (~3MLoC), so the juice is not worth the squeeze.

@dsnet
Copy link
Member

dsnet commented Jan 22, 2025

It doesn't turn up a single instance in the entire k8s codebase (~3MLoC), so the juice is not worth the squeeze.

😅 Thank you for testing that out.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/646916 mentions this issue: gopls/internal/analysis/modernize: for i := 0; i < n; i++ -> range n

@apocelipes
Copy link
Contributor

Would cmp.Compare(str1, str2) -> strings.Compare(str1, str2) be a good candidate for a modenizer? The strings.Compare is more efficient and seems to have clearer semantics.

@DmitriyMV
Copy link
Contributor

@apocelipes see #71270

gopherbot pushed a commit to golang/tools that referenced this issue Feb 6, 2025
This CL adds a modernizer for 3-clause for loops that offers a
fix to replace them with go1.22 "range n" loops.

Updates golang/go#70815

Change-Id: I347179b8a308f380a9a894aa811ced66f7605df1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/646916
Auto-Submit: Alan Donovan <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/647355 mentions this issue: gopls/internal/analysis/modernize/cmd/modernize: create

gopherbot pushed a commit to golang/tools that referenced this issue Feb 6, 2025
By moving the main.go file, gopls users will be able to run

$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix

after the gopls release to apply all modernizer fixes en masse.

Updates golang/go#70815

Change-Id: I25352b7b77653c177310dfea7a050741949890f9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/647355
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
@earthboundkid
Copy link
Contributor

When I tried to run this, I got an error. Is this expected?

$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@1320197d6c7ed6da48496e5e311c7ee76701e035
 -fix
go: golang.org/x/tools/[email protected] requires go >= 1.23.4; switching to go1.23.6
go: golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@1320197d6c7ed6da48496e5e311c7ee76701e035 (in golang.org/x/tools/[email protected]):
        The go.mod file for the module providing named packages contains one or
        more replace directives. It must not contain directives that would cause
        it to be interpreted differently than if it were the main module.

@adonovan
Copy link
Member Author

adonovan commented Feb 7, 2025

Yes, it is expected: gopls release tags are on a branch that doesn't have the replace golang.org/x/tools => ../ directive, allowing them to be go installed. You'll have to check out the x/tools repo and build it from there, or wait for the release.

Also: If building from head, beware that the three-way merge CL hasn't landed yet, so -fix may make a mess of things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Analysis Issues related to static analysis (vet, x/tools/go/analysis) FeatureRequest Issues asking for a new feature that does not need a proposal. gopls/analysis Issues related to running analysis in gopls gopls Issues related to the Go language server, gopls. Refactoring Issues related to refactoring tools Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests