]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: better error messages for for-range clauses
authorRobert Griesemer <gri@golang.org>
Thu, 20 Feb 2025 23:01:36 +0000 (15:01 -0800)
committerGopher Robot <gobot@golang.org>
Tue, 25 Feb 2025 14:21:10 +0000 (06:21 -0800)
Provide the exact error cause instead of reporting a missing
core type.

For #70128.

Change-Id: I835698fa1f22382711bd54b974d2c87ee17e9065
Reviewed-on: https://go-review.googlesource.com/c/go/+/651215
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
TryBot-Bypass: Robert Griesemer <gri@google.com>

src/cmd/compile/internal/types2/compilersupport.go
src/cmd/compile/internal/types2/stmt.go
src/cmd/compile/internal/types2/under.go
src/go/types/stmt.go
src/go/types/under.go
src/internal/types/testdata/check/typeparams.go
src/internal/types/testdata/spec/range.go
src/internal/types/testdata/spec/range_int.go

index e98675f9c587a633ad4f06d281a185d702eeb03f..5a8b3b9498eade16d0743b30ec16ab31ac715e1a 100644 (file)
@@ -32,7 +32,7 @@ func CoreType(t Type) Type {
 // RangeKeyVal returns the key and value types for a range over typ.
 // It panics if range over typ is invalid.
 func RangeKeyVal(typ Type) (Type, Type) {
-       key, val, _, ok := rangeKeyVal(typ, nil)
+       key, val, _, ok := rangeKeyVal(nil, typ, nil)
        assert(ok)
        return key, val
 }
index 60955da4fc7a203e5f33e003b864e38fe649f24f..3f5412fbddea04bbc4ca70211777fb5ca754f864 100644 (file)
@@ -859,8 +859,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
        // determine key/value types
        var key, val Type
        if x.mode != invalid {
-               // Ranging over a type parameter is permitted if it has a core type.
-               k, v, cause, ok := rangeKeyVal(x.typ, func(v goVersion) bool {
+               k, v, cause, ok := rangeKeyVal(check, x.typ, func(v goVersion) bool {
                        return check.allowVersion(v)
                })
                switch {
@@ -992,19 +991,23 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
 }
 
 // rangeKeyVal returns the key and value type produced by a range clause
-// over an expression of type typ.
+// over an expression of type orig.
 // If allowVersion != nil, it is used to check the required language version.
 // If the range clause is not permitted, rangeKeyVal returns ok = false.
 // When ok = false, rangeKeyVal may also return a reason in cause.
-func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) {
+// The check parameter is only used in case of an error; it may be nil.
+func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) {
        bad := func(cause string) (Type, Type, string, bool) {
                return Typ[Invalid], Typ[Invalid], cause, false
        }
 
-       orig := typ
-       switch typ := arrayPtrDeref(coreType(typ)).(type) {
-       case nil:
-               return bad("no core type")
+       var cause1 string
+       rtyp := sharedUnderOrChan(check, orig, &cause1)
+       if rtyp == nil {
+               return bad(cause1)
+       }
+
+       switch typ := arrayPtrDeref(rtyp).(type) {
        case *Basic:
                if isString(typ) {
                        return Typ[Int], universeRune, "", true // use 'rune' name
@@ -1022,9 +1025,7 @@ func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, ca
        case *Map:
                return typ.key, typ.elem, "", true
        case *Chan:
-               if typ.dir == SendOnly {
-                       return bad("receive from send-only channel")
-               }
+               assert(typ.dir != SendOnly)
                return typ.elem, nil, "", true
        case *Signature:
                if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
index 6d7a234ef49cf1d62650bfddff7d2f247f95b754..a4c05d9272b4a135fb976f3991fdd0ace9d2364c 100644 (file)
@@ -40,6 +40,57 @@ func typeset(t Type, yield func(t, u Type) bool) {
        yield(t, under(t))
 }
 
+// If t is not a type parameter, sharedUnderOrChan returns the underlying type;
+// if that type is a channel type it must permit receive operations.
+// If t is a type parameter, sharedUnderOrChan returns the single underlying
+// type of all types in its type set if it exists, or, if the type set contains
+// only channel types permitting receive operations and with identical element
+// types, sharedUnderOrChan returns one of those channel types.
+// Otherwise the result is nil, and *cause reports the error if a non-nil cause
+// is provided.
+// The check parameter is only used if *cause reports an error; it may be nil.
+func sharedUnderOrChan(check *Checker, t Type, cause *string) Type {
+       var s, su Type
+       var sc *Chan
+
+       bad := func(s string) bool {
+               if cause != nil {
+                       *cause = s
+               }
+               su = nil
+               return false
+       }
+
+       typeset(t, func(t, u Type) bool {
+               if u == nil {
+                       return bad("no specific type")
+               }
+               c, _ := u.(*Chan)
+               if c != nil && c.dir == SendOnly {
+                       return bad(check.sprintf("receive from send-only channel %s", t))
+               }
+               if su == nil {
+                       s, su = t, u
+                       sc = c // possibly nil
+                       return true
+               }
+               // su != nil
+               if sc != nil && c != nil {
+                       if !Identical(sc.elem, c.elem) {
+                               return bad(check.sprintf("channels with different element types %s and %s", sc.elem, c.elem))
+                       }
+                       return true
+               }
+               // sc == nil
+               if !Identical(su, u) {
+                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
+               }
+               return true
+       })
+
+       return su
+}
+
 // If t is not a type parameter, coreType returns the underlying type.
 // If t is a type parameter, coreType returns the single underlying
 // type of all types in its type set if it exists, or nil otherwise. If the
index d6a9fdd2de315fbc77aa00e91b2a7fea46a49d76..5426c5e719d799ceeecf3733c46918205ab06370 100644 (file)
@@ -877,8 +877,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
        // determine key/value types
        var key, val Type
        if x.mode != invalid {
-               // Ranging over a type parameter is permitted if it has a core type.
-               k, v, cause, ok := rangeKeyVal(x.typ, func(v goVersion) bool {
+               k, v, cause, ok := rangeKeyVal(check, x.typ, func(v goVersion) bool {
                        return check.allowVersion(v)
                })
                switch {
@@ -1010,19 +1009,23 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
 }
 
 // rangeKeyVal returns the key and value type produced by a range clause
-// over an expression of type typ.
+// over an expression of type orig.
 // If allowVersion != nil, it is used to check the required language version.
 // If the range clause is not permitted, rangeKeyVal returns ok = false.
 // When ok = false, rangeKeyVal may also return a reason in cause.
-func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) {
+// The check parameter is only used in case of an error; it may be nil.
+func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) {
        bad := func(cause string) (Type, Type, string, bool) {
                return Typ[Invalid], Typ[Invalid], cause, false
        }
 
-       orig := typ
-       switch typ := arrayPtrDeref(coreType(typ)).(type) {
-       case nil:
-               return bad("no core type")
+       var cause1 string
+       rtyp := sharedUnderOrChan(check, orig, &cause1)
+       if rtyp == nil {
+               return bad(cause1)
+       }
+
+       switch typ := arrayPtrDeref(rtyp).(type) {
        case *Basic:
                if isString(typ) {
                        return Typ[Int], universeRune, "", true // use 'rune' name
@@ -1040,9 +1043,7 @@ func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, ca
        case *Map:
                return typ.key, typ.elem, "", true
        case *Chan:
-               if typ.dir == SendOnly {
-                       return bad("receive from send-only channel")
-               }
+               assert(typ.dir != SendOnly)
                return typ.elem, nil, "", true
        case *Signature:
                if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
index b4c2e342a02c933cd602452fd817664df3baa295..9bb0705af5b93151c327fe52d4a612b250821237 100644 (file)
@@ -43,6 +43,57 @@ func typeset(t Type, yield func(t, u Type) bool) {
        yield(t, under(t))
 }
 
+// If t is not a type parameter, sharedUnderOrChan returns the underlying type;
+// if that type is a channel type it must permit receive operations.
+// If t is a type parameter, sharedUnderOrChan returns the single underlying
+// type of all types in its type set if it exists, or, if the type set contains
+// only channel types permitting receive operations and with identical element
+// types, sharedUnderOrChan returns one of those channel types.
+// Otherwise the result is nil, and *cause reports the error if a non-nil cause
+// is provided.
+// The check parameter is only used if *cause reports an error; it may be nil.
+func sharedUnderOrChan(check *Checker, t Type, cause *string) Type {
+       var s, su Type
+       var sc *Chan
+
+       bad := func(s string) bool {
+               if cause != nil {
+                       *cause = s
+               }
+               su = nil
+               return false
+       }
+
+       typeset(t, func(t, u Type) bool {
+               if u == nil {
+                       return bad("no specific type")
+               }
+               c, _ := u.(*Chan)
+               if c != nil && c.dir == SendOnly {
+                       return bad(check.sprintf("receive from send-only channel %s", t))
+               }
+               if su == nil {
+                       s, su = t, u
+                       sc = c // possibly nil
+                       return true
+               }
+               // su != nil
+               if sc != nil && c != nil {
+                       if !Identical(sc.elem, c.elem) {
+                               return bad(check.sprintf("channels with different element types %s and %s", sc.elem, c.elem))
+                       }
+                       return true
+               }
+               // sc == nil
+               if !Identical(su, u) {
+                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
+               }
+               return true
+       })
+
+       return su
+}
+
 // If t is not a type parameter, coreType returns the underlying type.
 // If t is a type parameter, coreType returns the single underlying
 // type of all types in its type set if it exists, or nil otherwise. If the
index 5fd82a5aa079e7cef74a7c076148d3b6b1adec68..d4fd35645ff2b09e8fe1cae0759ff33edc3ebed4 100644 (file)
@@ -230,7 +230,7 @@ func _[
         for _, _ = range s1 {}
 
         var s2 S2
-        for range s2 /* ERRORx `cannot range over s2.*no core type` */ {}
+        for range s2 /* ERRORx `cannot range over s2.*\[\]int and \[10\]int have different underlying types` */ {}
 
         var a0 []int
         for range a0 {}
@@ -243,7 +243,7 @@ func _[
         for _, _ = range a1 {}
 
         var a2 A2
-        for range a2 /* ERRORx `cannot range over a2.*no core type` */ {}
+        for range a2 /* ERRORx `cannot range over a2.*\[10\]int and \[\]int have different underlying types` */ {}
 
         var p0 *[10]int
         for range p0 {}
@@ -256,7 +256,7 @@ func _[
         for _, _ = range p1 {}
 
         var p2 P2
-        for range p2 /* ERRORx `cannot range over p2.*no core type` */ {}
+        for range p2 /* ERRORx `cannot range over p2.*\*\[10\]int and \*\[\]int have different underlying types` */ {}
 
         var m0 map[string]int
         for range m0 {}
@@ -269,7 +269,7 @@ func _[
         for _, _ = range m1 {}
 
         var m2 M2
-        for range m2 /* ERRORx `cannot range over m2.*no core type` */ {}
+        for range m2 /* ERRORx `cannot range over m2.*map\[string\]int and map\[string\]string` */ {}
 }
 
 // type inference checks
index c0f579479f60ece6511bae00dee6d68092b23253..26406fca8adb2aca2ec2ab024a55bd8b1e25da20 100644 (file)
@@ -129,13 +129,23 @@ func test() {
        }
 }
 
+func _[T any](x T) {
+       for range x /* ERROR "cannot range over x (variable of type T constrained by any): no specific type" */ {
+       }
+}
+
+func _[T interface{int; string}](x T) {
+       for range x /* ERROR "cannot range over x (variable of type T constrained by interface{int; string} with empty type set): no specific type" */ {
+       }
+}
+
 func _[T int | string](x T) {
-       for range x /* ERROR "cannot range over x (variable of type T constrained by int | string): no core type" */ {
+       for range x /* ERROR "cannot range over x (variable of type T constrained by int | string): int and string have different underlying types" */ {
        }
 }
 
 func _[T int | int64](x T) {
-       for range x /* ERROR "cannot range over x (variable of type T constrained by int | int64): no core type" */ {
+       for range x /* ERROR "cannot range over x (variable of type T constrained by int | int64): int and int64 have different underlying types" */ {
        }
 }
 
index db3a78ffadcddcd1ec18ffcdf64f771dc65bc20f..81b8ed62290d82aa7989ccf94b5b7bf5096d2f22 100644 (file)
@@ -56,12 +56,12 @@ func _() {
 }
 
 func _[T int | string](x T) {
-       for range x /* ERROR "cannot range over x (variable of type T constrained by int | string): no core type" */ {
+       for range x /* ERROR "cannot range over x (variable of type T constrained by int | string): int and string have different underlying types" */ {
        }
 }
 
 func _[T int | int64](x T) {
-       for range x /* ERROR "cannot range over x (variable of type T constrained by int | int64): no core type" */ {
+       for range x /* ERROR "cannot range over x (variable of type T constrained by int | int64): int and int64 have different underlying types" */ {
        }
 }