1
0
mirror of https://github.com/golang/go synced 2024-11-26 22:01:27 -07:00

cmd/compile: add missing method info for method with correct name except for case

When being used by the compiler, augment the types2 missing method
message with extra info, if a method is missing, but a method with the
correct name except for case (i.e. equal via string.EqualFold()) is
present. In that case, print out the wanted method and the method that
is present (that has the wrong case).

In the 1.17 compiler, we don't do this case-folding check when assigning
an interface to an interface, so I didn't add that check, but we could
add that.

Fixes #48471

Change-Id: Ic54549c1f66297c9221d979d49c1daa719aa66cd
Reviewed-on: https://go-review.googlesource.com/c/go/+/363437
Trust: Dan Scales <danscales@google.com>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Dan Scales 2021-11-11 10:28:17 -08:00
parent c8d6ee12d5
commit 429d1e0155
2 changed files with 40 additions and 12 deletions

View File

@ -52,7 +52,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// not have found it for T (see also issue 8590).
if t := asNamed(T); t != nil {
if p, _ := safeUnderlying(t).(*Pointer); p != nil {
obj, index, indirect = lookupFieldOrMethod(p, false, pkg, name)
obj, index, indirect = lookupFieldOrMethod(p, false, false, pkg, name)
if _, ok := obj.(*Func); ok {
return nil, nil, false
}
@ -60,7 +60,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
}
}
return lookupFieldOrMethod(T, addressable, pkg, name)
return lookupFieldOrMethod(T, addressable, false, pkg, name)
}
// TODO(gri) The named type consolidation and seen maps below must be
@ -69,7 +69,9 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// indirectly via different packages.)
// lookupFieldOrMethod should only be called by LookupFieldOrMethod and missingMethod.
func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
// If checkFold is true, the lookup for methods will include looking for any method
// which case-folds to the same as 'name' (used for giving helpful error messages).
func lookupFieldOrMethod(T Type, addressable, checkFold bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
// WARNING: The code in this function is extremely subtle - do not modify casually!
if name == "_" {
@ -127,7 +129,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
seen[named] = true
// look for a matching attached method
if i, m := lookupMethod(named.methods, pkg, name); m != nil {
if i, m := lookupMethodFold(named.methods, pkg, name, checkFold); m != nil {
// potential match
// caution: method may not have a proper signature yet
index = concat(e.index, i)
@ -178,7 +180,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
case *Interface:
// look for a matching method
if i, m := t.typeSet().LookupMethod(pkg, name); m != nil {
if i, m := lookupMethodFold(t.typeSet().methods, pkg, name, checkFold); m != nil {
assert(m.typ != nil)
index = concat(e.index, i)
if obj != nil || e.multiples {
@ -189,7 +191,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
}
case *TypeParam:
if i, m := t.iface().typeSet().LookupMethod(pkg, name); m != nil {
if i, m := lookupMethodFold(t.iface().typeSet().methods, pkg, name, checkFold); m != nil {
assert(m.typ != nil)
index = concat(e.index, i)
if obj != nil || e.multiples {
@ -315,6 +317,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
if !static {
continue
}
// We don't do any case-fold check if V is an interface.
return m, f
}
@ -345,13 +348,20 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
// A concrete type implements T if it implements all methods of T.
for _, m := range T.typeSet().methods {
// TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)?
obj, _, _ := lookupFieldOrMethod(V, false, m.pkg, m.name)
// TODO(gri) should this be calling LookupFieldOrMethod instead (and why not)?
obj, _, _ := lookupFieldOrMethod(V, false, false, m.pkg, m.name)
// Check if *V implements this method of T.
if obj == nil {
ptr := NewPointer(V)
obj, _, _ = lookupFieldOrMethod(ptr, false, m.pkg, m.name)
obj, _, _ = lookupFieldOrMethod(ptr, false, false, m.pkg, m.name)
if obj != nil {
return m, obj.(*Func)
}
// If we didn't find the exact method (even with pointer
// receiver), look to see if there is a method that
// matches m.name with case-folding.
obj, _, _ := lookupFieldOrMethod(V, false, true, m.pkg, m.name)
if obj != nil {
return m, obj.(*Func)
}
@ -410,7 +420,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
// where m is missing from V, but required by T. It puts the reason in parentheses,
// and may include more have/want info after that. If non-nil, wrongType is a relevant
// method that matches in some way. It may have the correct name, but wrong type, or
// it may have a pointer receiver.
// it may have a pointer receiver, or it may have the correct name except wrong case.
func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string {
var r string
var mname string
@ -527,3 +537,21 @@ func lookupMethod(methods []*Func, pkg *Package, name string) (int, *Func) {
}
return -1, nil
}
// lookupMethodFold is like lookupMethod, but if checkFold is true, it matches a method
// name if the names are equal with case folding.
func lookupMethodFold(methods []*Func, pkg *Package, name string, checkFold bool) (int, *Func) {
if name != "_" {
for i, m := range methods {
if m.name != name && !(checkFold && strings.EqualFold(m.name, name)) {
continue
}
// Use m.name, since we've already checked that m.name and
// name are equal with folding.
if m.sameId(pkg, m.name) {
return i, m
}
}
}
return -1, nil
}

View File

@ -29,11 +29,11 @@ func g() {
var i I
i = new(T) // ERROR "cannot use new\(T\) \(.*type \*T\) as type I in assignment:\n\t\*T does not implement I \(missing M method\)"
i = I(new(T)) // ERROR "cannot convert new\(T\) \(.*type \*T\) to type I:\n\t\*T does not implement I \(missing M method\)"
i = new(T2) // ERROR "cannot use new\(T2\) \(.*type \*T2\) as type I in assignment:\n\t\*T2 does not implement I \(missing M method\)"
i = new(T2) // ERROR "cannot use new\(T2\) \(.*type \*T2\) as type I in assignment:\n\t\*T2 does not implement I \(missing M method\)\n\t\thave m\(int\)\n\t\twant M\(int\)"
i = new(T3) // ERROR "cannot use new\(T3\) \(.*type \*T3\) as type I in assignment:\n\t\*T3 does not implement I \(wrong type for M method\)\n\t\thave M\(string\)\n\t\twant M\(int\)"
i = T4{} // ERROR "cannot use T4\{\} \(.*type T4\) as type I in assignment:\n\tT4 does not implement I \(M method has pointer receiver\)"
i = new(I) // ERROR "cannot use new\(I\) \(.*type \*I\) as type I in assignment:\n\t\*I does not implement I \(\*I is pointer to interface, not interface\)"
_ = i.(*T2) // ERROR "impossible type assertion: i.\(\*T2\)\n\t\*T2 does not implement I \(missing M method\)"
_ = i.(*T2) // ERROR "impossible type assertion: i.\(\*T2\)\n\t\*T2 does not implement I \(missing M method\)\n\t\thave m\(int\)\n\t\twant M\(int\)"
_ = i.(*T3) // ERROR "impossible type assertion: i.\(\*T3\)\n\t\*T3 does not implement I \(wrong type for M method\)\n\t\thave M\(string\)\n\t\twant M\(int\)"
var t *T4
t = i // ERROR "cannot use i \(variable of type I\) as type \*T4 in assignment:\n\tneed type assertion"