]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: better error messages for calls
authorRobert Griesemer <gri@golang.org>
Fri, 21 Feb 2025 00:23:11 +0000 (16:23 -0800)
committerGopher Robot <gobot@golang.org>
Tue, 25 Feb 2025 14:21:12 +0000 (06:21 -0800)
Provide the exact error cause instead of reporting a missing
core type.

For #70128.

Change-Id: I34bd401115742883cb6aef7997477473b2464abb
Reviewed-on: https://go-review.googlesource.com/c/go/+/651256
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
src/cmd/compile/internal/types2/call.go
src/cmd/compile/internal/types2/under.go
src/go/types/call.go
src/go/types/under.go
src/internal/types/testdata/check/lookup1.go

index 7ddeaf2453e8e6553b3d7e15ced97078105a5221..3a73a6c2c3899783e5efc8a1ad94e0b9b25bf245 100644 (file)
@@ -242,10 +242,16 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind {
        // signature may be generic
        cgocall := x.mode == cgofunc
 
-       // a type parameter may be "called" if all types have the same signature
-       sig, _ := coreType(x.typ).(*Signature)
+       // If the operand type is a type parameter, all types in its type set
+       // must have a shared underlying type, which must be a signature.
+       var cause string
+       sig, _ := sharedUnder(check, x.typ, &cause).(*Signature)
        if sig == nil {
-               check.errorf(x, InvalidCall, invalidOp+"cannot call non-function %s", x)
+               if cause != "" {
+                       check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, cause)
+               } else {
+                       check.errorf(x, InvalidCall, invalidOp+"cannot call non-function %s", x)
+               }
                x.mode = invalid
                x.expr = call
                return statement
index a4c05d9272b4a135fb976f3991fdd0ace9d2364c..911687396b8e7268f0c9784f76b1abd16382c5e0 100644 (file)
@@ -40,6 +40,38 @@ func typeset(t Type, yield func(t, u Type) bool) {
        yield(t, under(t))
 }
 
+// If t is not a type parameter, sharedUnder returns the underlying type.
+// If t is a type parameter, sharedUnder returns the single underlying
+// type of all types in its type set if it exists.
+// 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 sharedUnder(check *Checker, t Type, cause *string) Type {
+       var s, su Type
+
+       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")
+               }
+               if su != nil && !Identical(su, u) {
+                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
+               }
+               // su == nil || Identical(su, u)
+               s, su = t, u
+               return true
+       })
+
+       return su
+}
+
 // 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
index 03163a9145ea4f1a14d996e24820c7b6d4f3dc9c..a839477b8c3f6bad646924b24cc388b7e45b5fd0 100644 (file)
@@ -244,10 +244,16 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
        // signature may be generic
        cgocall := x.mode == cgofunc
 
-       // a type parameter may be "called" if all types have the same signature
-       sig, _ := coreType(x.typ).(*Signature)
+       // If the operand type is a type parameter, all types in its type set
+       // must have a shared underlying type, which must be a signature.
+       var cause string
+       sig, _ := sharedUnder(check, x.typ, &cause).(*Signature)
        if sig == nil {
-               check.errorf(x, InvalidCall, invalidOp+"cannot call non-function %s", x)
+               if cause != "" {
+                       check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, cause)
+               } else {
+                       check.errorf(x, InvalidCall, invalidOp+"cannot call non-function %s", x)
+               }
                x.mode = invalid
                x.expr = call
                return statement
index 9bb0705af5b93151c327fe52d4a612b250821237..f72f929039fe0e14274eff9a9b5ddc95729217f4 100644 (file)
@@ -43,6 +43,38 @@ func typeset(t Type, yield func(t, u Type) bool) {
        yield(t, under(t))
 }
 
+// If t is not a type parameter, sharedUnder returns the underlying type.
+// If t is a type parameter, sharedUnder returns the single underlying
+// type of all types in its type set if it exists.
+// 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 sharedUnder(check *Checker, t Type, cause *string) Type {
+       var s, su Type
+
+       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")
+               }
+               if su != nil && !Identical(su, u) {
+                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
+               }
+               // su == nil || Identical(su, u)
+               s, su = t, u
+               return true
+       })
+
+       return su
+}
+
 // 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
index 048288db773f6400d24cefe56aa2f0a4dd4e8e45..d9f90ba46a0716e531a8bec2f4bc203d7344f73b 100644 (file)
@@ -71,3 +71,15 @@ func _() {
        _ = x.Form // ERROR "x.Form undefined (type big.Float has no field or method Form, but does have unexported field form)"
        _ = x.FOrm // ERROR "x.FOrm undefined (type big.Float has no field or method FOrm)"
 }
+
+func _[P any](x P) {
+       x /* ERROR "cannot call x (variable of type P constrained by any): no specific type" */ ()
+}
+
+func _[P int](x P) {
+       x /* ERROR "cannot call non-function x (variable of type P constrained by int)" */ ()
+}
+
+func _[P int | string](x P) {
+       x /* ERROR "cannot call x (variable of type P constrained by int | string): int and string have different underlying types" */ ()
+}