From: Robert Griesemer Date: Thu, 20 Feb 2025 23:01:36 +0000 (-0800) Subject: go/types, types2: better error messages for for-range clauses X-Git-Tag: go1.25rc1~931 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=d45d502fbb989e140c979b16837b1c2126dd18ab;p=gostls13.git go/types, types2: better error messages for for-range clauses 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 Reviewed-by: Robert Findley Auto-Submit: Robert Griesemer TryBot-Bypass: Robert Griesemer --- diff --git a/src/cmd/compile/internal/types2/compilersupport.go b/src/cmd/compile/internal/types2/compilersupport.go index e98675f9c5..5a8b3b9498 100644 --- a/src/cmd/compile/internal/types2/compilersupport.go +++ b/src/cmd/compile/internal/types2/compilersupport.go @@ -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 } diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go index 60955da4fc..3f5412fbdd 100644 --- a/src/cmd/compile/internal/types2/stmt.go +++ b/src/cmd/compile/internal/types2/stmt.go @@ -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) { diff --git a/src/cmd/compile/internal/types2/under.go b/src/cmd/compile/internal/types2/under.go index 6d7a234ef4..a4c05d9272 100644 --- a/src/cmd/compile/internal/types2/under.go +++ b/src/cmd/compile/internal/types2/under.go @@ -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 diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index d6a9fdd2de..5426c5e719 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -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) { diff --git a/src/go/types/under.go b/src/go/types/under.go index b4c2e342a0..9bb0705af5 100644 --- a/src/go/types/under.go +++ b/src/go/types/under.go @@ -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 diff --git a/src/internal/types/testdata/check/typeparams.go b/src/internal/types/testdata/check/typeparams.go index 5fd82a5aa0..d4fd35645f 100644 --- a/src/internal/types/testdata/check/typeparams.go +++ b/src/internal/types/testdata/check/typeparams.go @@ -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 diff --git a/src/internal/types/testdata/spec/range.go b/src/internal/types/testdata/spec/range.go index c0f579479f..26406fca8a 100644 --- a/src/internal/types/testdata/spec/range.go +++ b/src/internal/types/testdata/spec/range.go @@ -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" */ { } } diff --git a/src/internal/types/testdata/spec/range_int.go b/src/internal/types/testdata/spec/range_int.go index db3a78ffad..81b8ed6229 100644 --- a/src/internal/types/testdata/spec/range_int.go +++ b/src/internal/types/testdata/spec/range_int.go @@ -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" */ { } }