mirror of
https://github.com/golang/go
synced 2024-11-26 19:41:19 -07:00
go/types, types2: better error message when missing method is unexported
Change lookupMethod such that "foldCase" means "ignore case and package" and analyze a lookup result further to determine if a method name was not exported, and report a better error message in that case. Fixes #59831. Change-Id: Ice6222e1fc00dba13caeda6c48971e8473d12da5 Reviewed-on: https://go-review.googlesource.com/c/go/+/549298 Run-TryBot: Robert Griesemer <gri@google.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
parent
f2d243db8f
commit
0b3dd75f6c
@ -1003,3 +1003,76 @@ type S struct{ A }
|
||||
t.Fatalf("got %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue59831(t *testing.T) {
|
||||
// Package a exports a type S with an unexported method m;
|
||||
// the tests check the error messages when m is not found.
|
||||
const asrc = `package a; type S struct{}; func (S) m() {}`
|
||||
apkg := mustTypecheck(asrc, nil, nil)
|
||||
|
||||
// Package b exports a type S with an exported method m;
|
||||
// the tests check the error messages when M is not found.
|
||||
const bsrc = `package b; type S struct{}; func (S) M() {}`
|
||||
bpkg := mustTypecheck(bsrc, nil, nil)
|
||||
|
||||
tests := []struct {
|
||||
imported *Package
|
||||
src, err string
|
||||
}{
|
||||
// tests importing a (or nothing)
|
||||
{apkg, `package a1; import "a"; var _ interface { M() } = a.S{}`,
|
||||
"a.S does not implement interface{M()} (missing method M) have m() want M()"},
|
||||
|
||||
{apkg, `package a2; import "a"; var _ interface { m() } = a.S{}`,
|
||||
"a.S does not implement interface{m()} (unexported method m)"}, // test for issue
|
||||
|
||||
{nil, `package a3; type S struct{}; func (S) m(); var _ interface { M() } = S{}`,
|
||||
"S does not implement interface{M()} (missing method M) have m() want M()"},
|
||||
|
||||
{nil, `package a4; type S struct{}; func (S) m(); var _ interface { m() } = S{}`,
|
||||
""}, // no error expected
|
||||
|
||||
{nil, `package a5; type S struct{}; func (S) m(); var _ interface { n() } = S{}`,
|
||||
"S does not implement interface{n()} (missing method n)"},
|
||||
|
||||
// tests importing b (or nothing)
|
||||
{bpkg, `package b1; import "b"; var _ interface { m() } = b.S{}`,
|
||||
"b.S does not implement interface{m()} (missing method m) have M() want m()"},
|
||||
|
||||
{bpkg, `package b2; import "b"; var _ interface { M() } = b.S{}`,
|
||||
""}, // no error expected
|
||||
|
||||
{nil, `package b3; type S struct{}; func (S) M(); var _ interface { M() } = S{}`,
|
||||
""}, // no error expected
|
||||
|
||||
{nil, `package b4; type S struct{}; func (S) M(); var _ interface { m() } = S{}`,
|
||||
"S does not implement interface{m()} (missing method m) have M() want m()"},
|
||||
|
||||
{nil, `package b5; type S struct{}; func (S) M(); var _ interface { n() } = S{}`,
|
||||
"S does not implement interface{n()} (missing method n)"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// typecheck test source
|
||||
conf := Config{Importer: importHelper{pkg: test.imported}}
|
||||
pkg, err := typecheck(test.src, &conf, nil)
|
||||
if err == nil {
|
||||
if test.err != "" {
|
||||
t.Errorf("package %s: got no error, want %q", pkg.Name(), test.err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if test.err == "" {
|
||||
t.Errorf("package %s: got %q, want not error", pkg.Name(), err.Error())
|
||||
}
|
||||
|
||||
// flatten reported error message
|
||||
errmsg := strings.ReplaceAll(err.Error(), "\n", " ")
|
||||
errmsg = strings.ReplaceAll(errmsg, "\t", "")
|
||||
|
||||
// verify error message
|
||||
if !strings.Contains(errmsg, test.err) {
|
||||
t.Errorf("package %s: got %q, want %q", pkg.Name(), errmsg, test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
|
||||
// and missingMethod (the latter doesn't care about struct fields).
|
||||
//
|
||||
// If foldCase is true, method names are considered equal if they are equal
|
||||
// with case folding.
|
||||
// with case folding, irrespective of which package they are in.
|
||||
//
|
||||
// The resulting object may not be fully type-checked.
|
||||
func lookupFieldOrMethodImpl(T Type, addressable bool, pkg *Package, name string, foldCase bool) (obj Object, index []int, indirect bool) {
|
||||
@ -343,6 +343,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
||||
ok = iota
|
||||
notFound
|
||||
wrongName
|
||||
unexported
|
||||
wrongSig
|
||||
ambigSel
|
||||
ptrRecv
|
||||
@ -388,6 +389,11 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
||||
f, _ = obj.(*Func)
|
||||
if f != nil {
|
||||
state = wrongName
|
||||
if f.name == m.name {
|
||||
// If the names are equal, f must be unexported
|
||||
// (otherwise the package wouldn't matter).
|
||||
state = unexported
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
@ -436,8 +442,9 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
||||
}
|
||||
case wrongName:
|
||||
fs, ms := check.funcString(f, false), check.funcString(m, false)
|
||||
*cause = check.sprintf("(missing method %s)\n\t\thave %s\n\t\twant %s",
|
||||
m.Name(), fs, ms)
|
||||
*cause = check.sprintf("(missing method %s)\n\t\thave %s\n\t\twant %s", m.Name(), fs, ms)
|
||||
case unexported:
|
||||
*cause = check.sprintf("(unexported method %s)", m.Name())
|
||||
case wrongSig:
|
||||
fs, ms := check.funcString(f, false), check.funcString(m, false)
|
||||
if fs == ms {
|
||||
@ -582,11 +589,12 @@ func fieldIndex(fields []*Var, pkg *Package, name string) int {
|
||||
}
|
||||
|
||||
// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
|
||||
// If foldCase is true, method names are considered equal if they are equal with case folding.
|
||||
// If foldCase is true, method names are considered equal if they are equal with case folding
|
||||
// and their packages are ignored (e.g., pkg1.m, pkg1.M, pkg2.m, and pkg2.M are all equal).
|
||||
func lookupMethod(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
|
||||
if name != "_" {
|
||||
for i, m := range methods {
|
||||
if (m.name == name || foldCase && strings.EqualFold(m.name, name)) && m.sameId(pkg, m.name) {
|
||||
if m.sameId(pkg, name) || foldCase && strings.EqualFold(m.name, name) {
|
||||
return i, m
|
||||
}
|
||||
}
|
||||
|
@ -1013,3 +1013,76 @@ type S struct{ A }
|
||||
t.Fatalf("got %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue59831(t *testing.T) {
|
||||
// Package a exports a type S with an unexported method m;
|
||||
// the tests check the error messages when m is not found.
|
||||
const asrc = `package a; type S struct{}; func (S) m() {}`
|
||||
apkg := mustTypecheck(asrc, nil, nil)
|
||||
|
||||
// Package b exports a type S with an exported method m;
|
||||
// the tests check the error messages when M is not found.
|
||||
const bsrc = `package b; type S struct{}; func (S) M() {}`
|
||||
bpkg := mustTypecheck(bsrc, nil, nil)
|
||||
|
||||
tests := []struct {
|
||||
imported *Package
|
||||
src, err string
|
||||
}{
|
||||
// tests importing a (or nothing)
|
||||
{apkg, `package a1; import "a"; var _ interface { M() } = a.S{}`,
|
||||
"a.S does not implement interface{M()} (missing method M) have m() want M()"},
|
||||
|
||||
{apkg, `package a2; import "a"; var _ interface { m() } = a.S{}`,
|
||||
"a.S does not implement interface{m()} (unexported method m)"}, // test for issue
|
||||
|
||||
{nil, `package a3; type S struct{}; func (S) m(); var _ interface { M() } = S{}`,
|
||||
"S does not implement interface{M()} (missing method M) have m() want M()"},
|
||||
|
||||
{nil, `package a4; type S struct{}; func (S) m(); var _ interface { m() } = S{}`,
|
||||
""}, // no error expected
|
||||
|
||||
{nil, `package a5; type S struct{}; func (S) m(); var _ interface { n() } = S{}`,
|
||||
"S does not implement interface{n()} (missing method n)"},
|
||||
|
||||
// tests importing b (or nothing)
|
||||
{bpkg, `package b1; import "b"; var _ interface { m() } = b.S{}`,
|
||||
"b.S does not implement interface{m()} (missing method m) have M() want m()"},
|
||||
|
||||
{bpkg, `package b2; import "b"; var _ interface { M() } = b.S{}`,
|
||||
""}, // no error expected
|
||||
|
||||
{nil, `package b3; type S struct{}; func (S) M(); var _ interface { M() } = S{}`,
|
||||
""}, // no error expected
|
||||
|
||||
{nil, `package b4; type S struct{}; func (S) M(); var _ interface { m() } = S{}`,
|
||||
"S does not implement interface{m()} (missing method m) have M() want m()"},
|
||||
|
||||
{nil, `package b5; type S struct{}; func (S) M(); var _ interface { n() } = S{}`,
|
||||
"S does not implement interface{n()} (missing method n)"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// typecheck test source
|
||||
conf := Config{Importer: importHelper{pkg: test.imported}}
|
||||
pkg, err := typecheck(test.src, &conf, nil)
|
||||
if err == nil {
|
||||
if test.err != "" {
|
||||
t.Errorf("package %s: got no error, want %q", pkg.Name(), test.err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if test.err == "" {
|
||||
t.Errorf("package %s: got %q, want not error", pkg.Name(), err.Error())
|
||||
}
|
||||
|
||||
// flatten reported error message
|
||||
errmsg := strings.ReplaceAll(err.Error(), "\n", " ")
|
||||
errmsg = strings.ReplaceAll(errmsg, "\t", "")
|
||||
|
||||
// verify error message
|
||||
if !strings.Contains(errmsg, test.err) {
|
||||
t.Errorf("package %s: got %q, want %q", pkg.Name(), errmsg, test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
|
||||
// and missingMethod (the latter doesn't care about struct fields).
|
||||
//
|
||||
// If foldCase is true, method names are considered equal if they are equal
|
||||
// with case folding.
|
||||
// with case folding, irrespective of which package they are in.
|
||||
//
|
||||
// The resulting object may not be fully type-checked.
|
||||
func lookupFieldOrMethodImpl(T Type, addressable bool, pkg *Package, name string, foldCase bool) (obj Object, index []int, indirect bool) {
|
||||
@ -345,6 +345,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
||||
ok = iota
|
||||
notFound
|
||||
wrongName
|
||||
unexported
|
||||
wrongSig
|
||||
ambigSel
|
||||
ptrRecv
|
||||
@ -390,6 +391,11 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
||||
f, _ = obj.(*Func)
|
||||
if f != nil {
|
||||
state = wrongName
|
||||
if f.name == m.name {
|
||||
// If the names are equal, f must be unexported
|
||||
// (otherwise the package wouldn't matter).
|
||||
state = unexported
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
@ -438,8 +444,9 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
||||
}
|
||||
case wrongName:
|
||||
fs, ms := check.funcString(f, false), check.funcString(m, false)
|
||||
*cause = check.sprintf("(missing method %s)\n\t\thave %s\n\t\twant %s",
|
||||
m.Name(), fs, ms)
|
||||
*cause = check.sprintf("(missing method %s)\n\t\thave %s\n\t\twant %s", m.Name(), fs, ms)
|
||||
case unexported:
|
||||
*cause = check.sprintf("(unexported method %s)", m.Name())
|
||||
case wrongSig:
|
||||
fs, ms := check.funcString(f, false), check.funcString(m, false)
|
||||
if fs == ms {
|
||||
@ -584,11 +591,12 @@ func fieldIndex(fields []*Var, pkg *Package, name string) int {
|
||||
}
|
||||
|
||||
// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
|
||||
// If foldCase is true, method names are considered equal if they are equal with case folding.
|
||||
// If foldCase is true, method names are considered equal if they are equal with case folding
|
||||
// and their packages are ignored (e.g., pkg1.m, pkg1.M, pkg2.m, and pkg2.M are all equal).
|
||||
func lookupMethod(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
|
||||
if name != "_" {
|
||||
for i, m := range methods {
|
||||
if (m.name == name || foldCase && strings.EqualFold(m.name, name)) && m.sameId(pkg, m.name) {
|
||||
if m.sameId(pkg, name) || foldCase && strings.EqualFold(m.name, name) {
|
||||
return i, m
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user