From 549a88fa53c4d7d5ad702cdc90b3f0c763deb12e Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 20 Feb 2025 16:23:11 -0800 Subject: [PATCH] go/types, types2: better error messages for calls 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 Auto-Submit: Robert Griesemer LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/call.go | 12 ++++++-- src/cmd/compile/internal/types2/under.go | 32 ++++++++++++++++++++ src/go/types/call.go | 12 ++++++-- src/go/types/under.go | 32 ++++++++++++++++++++ src/internal/types/testdata/check/lookup1.go | 12 ++++++++ 5 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 7ddeaf2453..3a73a6c2c3 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -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 diff --git a/src/cmd/compile/internal/types2/under.go b/src/cmd/compile/internal/types2/under.go index a4c05d9272..911687396b 100644 --- a/src/cmd/compile/internal/types2/under.go +++ b/src/cmd/compile/internal/types2/under.go @@ -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 diff --git a/src/go/types/call.go b/src/go/types/call.go index 03163a9145..a839477b8c 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -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 diff --git a/src/go/types/under.go b/src/go/types/under.go index 9bb0705af5..f72f929039 100644 --- a/src/go/types/under.go +++ b/src/go/types/under.go @@ -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 diff --git a/src/internal/types/testdata/check/lookup1.go b/src/internal/types/testdata/check/lookup1.go index 048288db77..d9f90ba46a 100644 --- a/src/internal/types/testdata/check/lookup1.go +++ b/src/internal/types/testdata/check/lookup1.go @@ -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" */ () +} -- 2.50.0