]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: better error message when missing method is unexported
authorRobert Griesemer <gri@golang.org>
Wed, 13 Dec 2023 01:52:36 +0000 (17:52 -0800)
committerGopher Robot <gobot@golang.org>
Wed, 13 Dec 2023 17:54:56 +0000 (17:54 +0000)
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>
src/cmd/compile/internal/types2/issues_test.go
src/cmd/compile/internal/types2/lookup.go
src/go/types/issues_test.go
src/go/types/lookup.go

index a8b70b89642f18a1be482983fa6679727d632971..a8893cf6de36ee8c9acb5aa82d04dc5dda395a39 100644 (file)
@@ -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)
+               }
+       }
+}
index 014a5489cdaa38c8b636628ce9f7069dba19041e..bc47c150607635dbb0a310670ef0b711729d35f4 100644 (file)
@@ -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
                        }
                }
index 91631fe9c77e6d48f004c95c674776e8d6e86e82..b4c8218bc4f75667cfd1f25168dbcceadaa77a1e 100644 (file)
@@ -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)
+               }
+       }
+}
index 05d30c178a11ef7f36b9352b02d5f39c6aee5cdf..7723c435652785fd11df9b0547a7daca7fab733c 100644 (file)
@@ -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
                        }
                }