[tools] go/analysis/passes/modernize: rangeint: handle type parameter constraints

0 views
Skip to first unread message

Gopher Robot (Gerrit)

unread,
Apr 9, 2026, 4:09:58 PM (11 hours ago) Apr 9
to Madeline Kalil, goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Go LUCI, Alan Donovan, golang-co...@googlegroups.com

Gopher Robot submitted the change with unreviewed changes

Unreviewed changes

1 is the latest approved patch-set.
The change was submitted with unreviewed changes in the following files:

```
The name of the file: go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
Insertions: 19, Deletions: 27.

@@ -351,50 +351,42 @@
}
}

-type Integer interface {
- ~int | ~uint
-}
-
-// Regression tests for golang/go#78571: the loop index cannot be a type parameter
-// constrained by multiple distinct integer types.
-func issue78571[I Integer](n I) {
+// Regression tests for golang/go#78571: the loop index cannot be a type
+// parameter constrained by multiple distinct integer types, nor a type
+// parameter constrained by a non-integer type.
+func issue78571[I ~int | ~uint](limit I) {
var i I
- for i = 0; i < n; i++ { // nope: int and uint have different underlying types.
+ for i = 0; i < limit; i++ { // nope: int and uint have different underlying types.
println(i)
}
}

type MyInt int

-type SameUnderlying interface {
- int | MyInt
-}
-
-func issue78571_same[I SameUnderlying](n I) {
- var i I
- for i = range n { // want "for loop can be modernized using range over int"
+func issue78571_sameunderlying[T int | MyInt](limit T) {
+ var i T
+ for i = range limit { // want "for loop can be modernized using range over int"
println(i)
}
}

-type SingleTerm interface {
- ~int
-}
-
-func issue78571_single[I SingleTerm](n I) {
- var i I
- for i = range n { // want "for loop can be modernized using range over int"
+func issue78571_single[T ~int](limit T) {
+ var i T
+ for i = range limit { // want "for loop can be modernized using range over int"
println(i)
}
}

-type Float interface {
- ~float64
+func issue78571_float[T ~float64](limit T) {
+ var i T
+ for i = 0; i < limit; i++ { // nope: can't range over a float
+ println(i)
+ }
}

-func issue78571_float[I Float](n I) {
- var i I
- for i = 0; i < n; i++ { // nope: can't range over a float
+func issue78571_floats[T float32 | float64](limit T) {
+ var i T
+ for i = 0; i < limit; i++ { // nope: can't range over a float
println(i)
}
}
```
```
The name of the file: go/analysis/passes/modernize/rangeint.go
Insertions: 21, Deletions: 17.

@@ -9,6 +9,7 @@
"go/ast"
"go/token"
"go/types"
+ "log"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -215,30 +216,33 @@
}
}

- // The loop index (v) may not be a type parameter constrained by
+ // The loop index (v) must not be a type parameter constrained by
// multiple distinct integer types, or a type parameter constrained
// by non-integer types. Transforming such instances to a range loop
// would result in a compiler error.
// See golang/go#78571.
- if tparam, ok := v.Type().(*types.TypeParam); ok {
- terms, err := typeparams.NormalTerms(tparam.Constraint())
- if err != nil {
- return nil, err
+ terms, err := typeparams.NormalTerms(v.Type()) // NormalTerms works for any type
+ if err != nil {
+ log.Fatalf("internal error: cannot compute type set of loop var %v: %v", v, err)
+ }
+ if len(terms) != 0 {
+ // From the spec (https://go.dev/ref/spec#For_range):
+ // "If the type of the range expression is a type parameter, all
+ // types in its type set must have the same underlying type and the
+ // range expression must be valid for that type."
+ //
+ // Check if all terms have the same underlying type by comparing
+ // them to the first term.
+ u := terms[0].Type().Underlying()
+ // If the constraint has any non-integer terms, skip. (Range over
+ // float is not allowed.)
+ if !isInteger(u) {
+ continue nextLoop
}
- if len(terms) != 0 {
- // Check if all terms have an identical underlying type by
- // comparing them to the first term.
- u := terms[0].Type().Underlying()
- // If the constraint has any non-integer terms, skip. (Range over
- // float is not allowed.)
- if !isInteger(u) {
+ for _, term := range terms[1:] {
+ if !types.Identical(u, term.Type().Underlying()) {
continue nextLoop
}
- for _, term := range terms[1:] {
- if !types.Identical(u, term.Type().Underlying()) {
- continue nextLoop
- }
- }
}
}

```
```
The name of the file: go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
Insertions: 19, Deletions: 27.

@@ -352,50 +352,42 @@
}
}

-type Integer interface {
- ~int | ~uint
-}
-
-// Regression tests for golang/go#78571: the loop index cannot be a type parameter
-// constrained by multiple distinct integer types.
-func issue78571[I Integer](n I) {
+// Regression tests for golang/go#78571: the loop index cannot be a type
+// parameter constrained by multiple distinct integer types, nor a type
+// parameter constrained by a non-integer type.
+func issue78571[I ~int | ~uint](limit I) {
var i I
- for i = 0; i < n; i++ { // nope: int and uint have different underlying types.
+ for i = 0; i < limit; i++ { // nope: int and uint have different underlying types.
println(i)
}
}

type MyInt int

-type SameUnderlying interface {
- int | MyInt
-}
-
-func issue78571_same[I SameUnderlying](n I) {
- var i I
- for i = 0; i < n; i++ { // want "for loop can be modernized using range over int"
+func issue78571_sameunderlying[T int | MyInt](limit T) {
+ var i T
+ for i = 0; i < limit; i++ { // want "for loop can be modernized using range over int"
println(i)
}
}

-type SingleTerm interface {
- ~int
-}
-
-func issue78571_single[I SingleTerm](n I) {
- var i I
- for i = 0; i < n; i++ { // want "for loop can be modernized using range over int"
+func issue78571_single[T ~int](limit T) {
+ var i T
+ for i = 0; i < limit; i++ { // want "for loop can be modernized using range over int"
println(i)
}
}

-type Float interface {
- ~float64
+func issue78571_float[T ~float64](limit T) {
+ var i T
+ for i = 0; i < limit; i++ { // nope: can't range over a float
+ println(i)
+ }
}

-func issue78571_float[I Float](n I) {
- var i I
- for i = 0; i < n; i++ { // nope: can't range over a float
+func issue78571_floats[T float32 | float64](limit T) {
+ var i T
+ for i = 0; i < limit; i++ { // nope: can't range over a float
println(i)
}
}
```

Change information

Commit message:
go/analysis/passes/modernize: rangeint: handle type parameter constraints

The loop index of a range loop cannot be a type parameter constrained by multiple distinct integer types, nor a type parameter constrained by non-integer types.

This CL updates the rangeint modernizer to detect these cases and avoid suggesting a fix.

It also updates the documentation of typeparams.CoreTypes, whose concept was used in this implementation.

Fixes golang/go#78571
Change-Id: Id897c8f4be80c96cf258b71e7879721339cee7dd
Reviewed-by: Alan Donovan <adon...@google.com>
Auto-Submit: Madeline Kalil <mka...@google.com>
Files:
  • M go/analysis/passes/modernize/modernize.go
  • M go/analysis/passes/modernize/rangeint.go
  • M go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
  • M go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
  • M go/analysis/passes/modernize/unsafefuncs.go
  • M internal/typeparams/coretype.go
Change size: M
Delta: 6 files changed, 124 insertions(+), 30 deletions(-)
Branch: refs/heads/master
Submit Requirements:
  • requirement satisfiedCode-Review: +2 by Alan Donovan
  • requirement satisfiedTryBots-Pass: LUCI-TryBot-Result+1 by Go LUCI
Open in Gerrit
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: merged
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Id897c8f4be80c96cf258b71e7879721339cee7dd
Gerrit-Change-Number: 764660
Gerrit-PatchSet: 3
Gerrit-Owner: Madeline Kalil <mka...@google.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Gopher Robot <go...@golang.org>
Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
open
diffy
satisfied_requirement
Reply all
Reply to author
Forward
0 new messages