aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2024-01-24 12:42:11 -0800
committerGopher Robot <gobot@golang.org>2024-01-26 14:04:26 +0000
commit8e02e7b26af46a1d113057ac49ad440a39a45d58 (patch)
treebf9a31128347256bcfa152b452b9c29cf5220035
parentd278d5bbdddd0e976c272d1dd3ecc41eeb37daf9 (diff)
downloadgo-8e02e7b26af46a1d113057ac49ad440a39a45d58.tar.gz
go-8e02e7b26af46a1d113057ac49ad440a39a45d58.zip
go/types, types2: use existing case-insensitive lookup (remove TODO)
Rather than implementing a new, less complete mechanism to check if a selector exists with different capitalization, use the existing mechanism in lookupFieldOrMethodImpl by making it available for internal use. Pass foldCase parameter all the way trough to Object.sameId and thus make it consistently available where Object.sameId is used. From sameId, factor out samePkg functionality into stand-alone predicate. Do better case distinction when reporting an error for an undefined selector expression. Cleanup. Change-Id: I7be3cecb4976a4dce3264c7e0c49a320c87101e9 Reviewed-on: https://go-review.googlesource.com/c/go/+/558315 Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Auto-Submit: Robert Griesemer <gri@google.com> Run-TryBot: Robert Griesemer <gri@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
-rw-r--r--src/cmd/compile/internal/types2/builtins.go2
-rw-r--r--src/cmd/compile/internal/types2/call.go27
-rw-r--r--src/cmd/compile/internal/types2/expr.go2
-rw-r--r--src/cmd/compile/internal/types2/lookup.go33
-rw-r--r--src/cmd/compile/internal/types2/object.go23
-rw-r--r--src/cmd/compile/internal/types2/predicates.go12
-rw-r--r--src/cmd/compile/internal/types2/scope.go34
-rw-r--r--src/cmd/compile/internal/types2/unify.go2
-rw-r--r--src/go/types/builtins.go2
-rw-r--r--src/go/types/call.go28
-rw-r--r--src/go/types/expr.go2
-rw-r--r--src/go/types/lookup.go33
-rw-r--r--src/go/types/object.go23
-rw-r--r--src/go/types/predicates.go12
-rw-r--r--src/go/types/scope.go34
-rw-r--r--src/go/types/unify.go2
-rw-r--r--src/internal/types/testdata/check/issues0.go2
-rw-r--r--src/internal/types/testdata/check/lookup.go73
-rw-r--r--test/fixedbugs/issue22794.go6
19 files changed, 223 insertions, 129 deletions
diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go
index 60f6d7f415..bb89246b7d 100644
--- a/src/cmd/compile/internal/types2/builtins.go
+++ b/src/cmd/compile/internal/types2/builtins.go
@@ -720,7 +720,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
base := derefStructPtr(x.typ)
sel := selx.Sel.Value
- obj, index, indirect := LookupFieldOrMethod(base, false, check.pkg, sel)
+ obj, index, indirect := lookupFieldOrMethod(base, false, check.pkg, sel, false)
switch obj.(type) {
case nil:
check.errorf(x, MissingFieldOrMethod, invalidArg+"%s has no single field %s", base, sel)
diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go
index 32cd80f74f..0ad58e0772 100644
--- a/src/cmd/compile/internal/types2/call.go
+++ b/src/cmd/compile/internal/types2/call.go
@@ -10,7 +10,6 @@ import (
"cmd/compile/internal/syntax"
. "internal/types/errors"
"strings"
- "unicode"
)
// funcInst type-checks a function instantiation.
@@ -799,7 +798,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName
goto Error
}
- obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel)
+ obj, index, indirect = lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel, false)
if obj == nil {
// Don't report another error if the underlying type was invalid (go.dev/issue/49541).
if !isValid(under(x.typ)) {
@@ -826,18 +825,19 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName
why = check.interfacePtrError(x.typ)
} else {
why = check.sprintf("type %s has no field or method %s", x.typ, sel)
- // Check if capitalization of sel matters and provide better error message in that case.
- // TODO(gri) This code only looks at the first character but LookupFieldOrMethod has an
- // (internal) mechanism for case-insensitive lookup. Should use that instead.
- if len(sel) > 0 {
- var changeCase string
- if r := rune(sel[0]); unicode.IsUpper(r) {
- changeCase = string(unicode.ToLower(r)) + sel[1:]
- } else {
- changeCase = string(unicode.ToUpper(r)) + sel[1:]
+ // check if there's a field or method with different capitalization
+ if obj, _, _ = lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel, true); obj != nil {
+ var what string // empty or description with trailing space " " (default case, should never be reached)
+ switch obj.(type) {
+ case *Var:
+ what = "field "
+ case *Func:
+ what = "method "
}
- if obj, _, _ = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil {
- why += ", but does have " + changeCase
+ if samePkg(obj.Pkg(), check.pkg) || obj.Exported() {
+ why = check.sprintf("%s, but does have %s%s", why, what, obj.Name())
+ } else if obj.Name() == sel {
+ why = check.sprintf("%s%s is not exported", what, obj.Name())
}
}
}
@@ -854,7 +854,6 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName
// method expression
m, _ := obj.(*Func)
if m == nil {
- // TODO(gri) should check if capitalization of sel matters and provide better error message in that case
check.errorf(e.Sel, MissingFieldOrMethod, "%s.%s undefined (type %s has no method %s)", x.expr, sel, x.typ, sel)
goto Error
}
diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go
index 124d9701d6..9504207f24 100644
--- a/src/cmd/compile/internal/types2/expr.go
+++ b/src/cmd/compile/internal/types2/expr.go
@@ -1184,7 +1184,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e syntax.Expr, hint Ty
check.errorf(kv, InvalidLitField, "invalid field name %s in struct literal", kv.Key)
continue
}
- i := fieldIndex(utyp.fields, check.pkg, key.Value)
+ i := fieldIndex(utyp.fields, check.pkg, key.Value, false)
if i < 0 {
check.errorf(kv.Key, MissingLitField, "unknown field %s in struct literal of type %s", key.Value, base)
continue
diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go
index bc47c15060..15e80a0b1b 100644
--- a/src/cmd/compile/internal/types2/lookup.go
+++ b/src/cmd/compile/internal/types2/lookup.go
@@ -9,7 +9,6 @@ package types2
import (
"bytes"
"cmd/compile/internal/syntax"
- "strings"
)
// Internal use of LookupFieldOrMethod: If the obj result is a method
@@ -46,7 +45,12 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
if T == nil {
panic("LookupFieldOrMethod on nil type")
}
+ return lookupFieldOrMethod(T, addressable, pkg, name, false)
+}
+// lookupFieldOrMethod is like LookupFieldOrMethod but with the additional foldCase parameter
+// (see Object.sameId for the meaning of foldCase).
+func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string, foldCase bool) (obj Object, index []int, indirect bool) {
// Methods cannot be associated to a named pointer type.
// (spec: "The type denoted by T is called the receiver base type;
// it must not be a pointer or interface type and it must be declared
@@ -56,7 +60,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// not have found it for T (see also go.dev/issue/8590).
if t := asNamed(T); t != nil {
if p, _ := t.Underlying().(*Pointer); p != nil {
- obj, index, indirect = lookupFieldOrMethodImpl(p, false, pkg, name, false)
+ obj, index, indirect = lookupFieldOrMethodImpl(p, false, pkg, name, foldCase)
if _, ok := obj.(*Func); ok {
return nil, nil, false
}
@@ -64,7 +68,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
}
}
- obj, index, indirect = lookupFieldOrMethodImpl(T, addressable, pkg, name, false)
+ obj, index, indirect = lookupFieldOrMethodImpl(T, addressable, pkg, name, foldCase)
// If we didn't find anything and if we have a type parameter with a core type,
// see if there is a matching field (but not a method, those need to be declared
@@ -73,7 +77,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
const enableTParamFieldLookup = false // see go.dev/issue/51576
if enableTParamFieldLookup && obj == nil && isTypeParam(T) {
if t := coreType(T); t != nil {
- obj, index, indirect = lookupFieldOrMethodImpl(t, addressable, pkg, name, false)
+ obj, index, indirect = lookupFieldOrMethodImpl(t, addressable, pkg, name, foldCase)
if _, ok := obj.(*Var); !ok {
obj, index, indirect = nil, nil, false // accept fields (variables) only
}
@@ -82,8 +86,8 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
return
}
-// lookupFieldOrMethodImpl is the implementation of LookupFieldOrMethod.
-// Notably, in contrast to LookupFieldOrMethod, it won't find struct fields
+// lookupFieldOrMethodImpl is the implementation of lookupFieldOrMethod.
+// Notably, in contrast to lookupFieldOrMethod, it won't find struct fields
// in base types of defined (*Named) pointer types T. For instance, given
// the declaration:
//
@@ -92,12 +96,9 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// lookupFieldOrMethodImpl won't find the field f in the defined (*Named) type T
// (methods on T are not permitted in the first place).
//
-// Thus, lookupFieldOrMethodImpl should only be called by LookupFieldOrMethod
+// Thus, lookupFieldOrMethodImpl should only be called by lookupFieldOrMethod
// 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, 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) {
// WARNING: The code in this function is extremely subtle - do not modify casually!
@@ -167,7 +168,7 @@ func lookupFieldOrMethodImpl(T Type, addressable bool, pkg *Package, name string
case *Struct:
// look for a matching field and collect embedded types
for i, f := range t.fields {
- if f.sameId(pkg, name) {
+ if f.sameId(pkg, name, foldCase) {
assert(f.typ != nil)
index = concat(e.index, i)
if obj != nil || e.multiples {
@@ -577,10 +578,11 @@ func concat(list []int, i int) []int {
}
// fieldIndex returns the index for the field with matching package and name, or a value < 0.
-func fieldIndex(fields []*Var, pkg *Package, name string) int {
+// See Object.sameId for the meaning of foldCase.
+func fieldIndex(fields []*Var, pkg *Package, name string, foldCase bool) int {
if name != "_" {
for i, f := range fields {
- if f.sameId(pkg, name) {
+ if f.sameId(pkg, name, foldCase) {
return i
}
}
@@ -589,12 +591,11 @@ 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
-// and their packages are ignored (e.g., pkg1.m, pkg1.M, pkg2.m, and pkg2.M are all equal).
+// See Object.sameId for the meaning of foldCase.
func lookupMethod(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
if name != "_" {
for i, m := range methods {
- if m.sameId(pkg, name) || foldCase && strings.EqualFold(m.name, name) {
+ if m.sameId(pkg, name, foldCase) {
return i, m
}
}
diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go
index 251587224b..e48a4895a7 100644
--- a/src/cmd/compile/internal/types2/object.go
+++ b/src/cmd/compile/internal/types2/object.go
@@ -9,6 +9,7 @@ import (
"cmd/compile/internal/syntax"
"fmt"
"go/constant"
+ "strings"
"unicode"
"unicode/utf8"
)
@@ -50,7 +51,9 @@ type Object interface {
setParent(*Scope)
// sameId reports whether obj.Id() and Id(pkg, name) are the same.
- sameId(pkg *Package, name string) bool
+ // If foldCase is true, 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).
+ sameId(pkg *Package, name string, foldCase bool) bool
// scopePos returns the start position of the scope of this Object
scopePos() syntax.Pos
@@ -163,26 +166,24 @@ func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ =
func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
func (obj *object) setScopePos(pos syntax.Pos) { obj.scopePos_ = pos }
-func (obj *object) sameId(pkg *Package, name string) bool {
+func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool {
+ // If we don't care about capitalization, we also ignore packages.
+ if foldCase && strings.EqualFold(obj.name, name) {
+ return true
+ }
// spec:
// "Two identifiers are different if they are spelled differently,
// or if they appear in different packages and are not exported.
// Otherwise, they are the same."
- if name != obj.name {
+ if obj.name != name {
return false
}
// obj.Name == name
if obj.Exported() {
return true
}
- // not exported, so packages must be the same (pkg == nil for
- // fields in Universe scope; this can only happen for types
- // introduced via Eval)
- if pkg == nil || obj.pkg == nil {
- return pkg == obj.pkg
- }
- // pkg != nil && obj.pkg != nil
- return pkg.path == obj.pkg.path
+ // not exported, so packages must be the same
+ return samePkg(obj.pkg, pkg)
}
// less reports whether object a is ordered before object b.
diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go
index 7a096e3d97..bb2b53a942 100644
--- a/src/cmd/compile/internal/types2/predicates.go
+++ b/src/cmd/compile/internal/types2/predicates.go
@@ -205,6 +205,16 @@ func hasNil(t Type) bool {
return false
}
+// samePkg reports whether packages a and b are the same.
+func samePkg(a, b *Package) bool {
+ // package is nil for objects in universe scope
+ if a == nil || b == nil {
+ return a == b
+ }
+ // a != nil && b != nil
+ return a.path == b.path
+}
+
// An ifacePair is a node in a stack of interface type pairs compared for identity.
type ifacePair struct {
x, y *Interface
@@ -269,7 +279,7 @@ func (c *comparer) identical(x, y Type, p *ifacePair) bool {
g := y.fields[i]
if f.embedded != g.embedded ||
!c.ignoreTags && x.Tag(i) != y.Tag(i) ||
- !f.sameId(g.pkg, g.name) ||
+ !f.sameId(g.pkg, g.name, false) ||
!c.identical(f.typ, g.typ, p) {
return false
}
diff --git a/src/cmd/compile/internal/types2/scope.go b/src/cmd/compile/internal/types2/scope.go
index 25bde6a794..b75e5cbaf7 100644
--- a/src/cmd/compile/internal/types2/scope.go
+++ b/src/cmd/compile/internal/types2/scope.go
@@ -273,20 +273,20 @@ func resolve(name string, obj Object) Object {
// stub implementations so *lazyObject implements Object and we can
// store them directly into Scope.elems.
-func (*lazyObject) Parent() *Scope { panic("unreachable") }
-func (*lazyObject) Pos() syntax.Pos { panic("unreachable") }
-func (*lazyObject) Pkg() *Package { panic("unreachable") }
-func (*lazyObject) Name() string { panic("unreachable") }
-func (*lazyObject) Type() Type { panic("unreachable") }
-func (*lazyObject) Exported() bool { panic("unreachable") }
-func (*lazyObject) Id() string { panic("unreachable") }
-func (*lazyObject) String() string { panic("unreachable") }
-func (*lazyObject) order() uint32 { panic("unreachable") }
-func (*lazyObject) color() color { panic("unreachable") }
-func (*lazyObject) setType(Type) { panic("unreachable") }
-func (*lazyObject) setOrder(uint32) { panic("unreachable") }
-func (*lazyObject) setColor(color color) { panic("unreachable") }
-func (*lazyObject) setParent(*Scope) { panic("unreachable") }
-func (*lazyObject) sameId(pkg *Package, name string) bool { panic("unreachable") }
-func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") }
-func (*lazyObject) setScopePos(pos syntax.Pos) { panic("unreachable") }
+func (*lazyObject) Parent() *Scope { panic("unreachable") }
+func (*lazyObject) Pos() syntax.Pos { panic("unreachable") }
+func (*lazyObject) Pkg() *Package { panic("unreachable") }
+func (*lazyObject) Name() string { panic("unreachable") }
+func (*lazyObject) Type() Type { panic("unreachable") }
+func (*lazyObject) Exported() bool { panic("unreachable") }
+func (*lazyObject) Id() string { panic("unreachable") }
+func (*lazyObject) String() string { panic("unreachable") }
+func (*lazyObject) order() uint32 { panic("unreachable") }
+func (*lazyObject) color() color { panic("unreachable") }
+func (*lazyObject) setType(Type) { panic("unreachable") }
+func (*lazyObject) setOrder(uint32) { panic("unreachable") }
+func (*lazyObject) setColor(color color) { panic("unreachable") }
+func (*lazyObject) setParent(*Scope) { panic("unreachable") }
+func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") }
+func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") }
+func (*lazyObject) setScopePos(syntax.Pos) { panic("unreachable") }
diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go
index 8218939b68..6838f270c1 100644
--- a/src/cmd/compile/internal/types2/unify.go
+++ b/src/cmd/compile/internal/types2/unify.go
@@ -608,7 +608,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
g := y.fields[i]
if f.embedded != g.embedded ||
x.Tag(i) != y.Tag(i) ||
- !f.sameId(g.pkg, g.name) ||
+ !f.sameId(g.pkg, g.name, false) ||
!u.nify(f.typ, g.typ, emode, p) {
return false
}
diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go
index 901573661b..ae2bca25f0 100644
--- a/src/go/types/builtins.go
+++ b/src/go/types/builtins.go
@@ -719,7 +719,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
base := derefStructPtr(x.typ)
sel := selx.Sel.Name
- obj, index, indirect := LookupFieldOrMethod(base, false, check.pkg, sel)
+ obj, index, indirect := lookupFieldOrMethod(base, false, check.pkg, sel, false)
switch obj.(type) {
case nil:
check.errorf(x, MissingFieldOrMethod, invalidArg+"%s has no single field %s", base, sel)
diff --git a/src/go/types/call.go b/src/go/types/call.go
index 79852d4523..5435e45f25 100644
--- a/src/go/types/call.go
+++ b/src/go/types/call.go
@@ -12,7 +12,6 @@ import (
"go/token"
. "internal/types/errors"
"strings"
- "unicode"
)
// funcInst type-checks a function instantiation.
@@ -801,7 +800,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, w
goto Error
}
- obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel)
+ obj, index, indirect = lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel, false)
if obj == nil {
// Don't report another error if the underlying type was invalid (go.dev/issue/49541).
if !isValid(under(x.typ)) {
@@ -828,19 +827,19 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, w
why = check.interfacePtrError(x.typ)
} else {
why = check.sprintf("type %s has no field or method %s", x.typ, sel)
- // Check if capitalization of sel matters and provide better error message in that case.
- // TODO(gri) This code only looks at the first character but LookupFieldOrMethod should
- // have an (internal) mechanism for case-insensitive lookup that we should use
- // instead (see types2).
- if len(sel) > 0 {
- var changeCase string
- if r := rune(sel[0]); unicode.IsUpper(r) {
- changeCase = string(unicode.ToLower(r)) + sel[1:]
- } else {
- changeCase = string(unicode.ToUpper(r)) + sel[1:]
+ // check if there's a field or method with different capitalization
+ if obj, _, _ = lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel, true); obj != nil {
+ var what string // empty or description with trailing space " " (default case, should never be reached)
+ switch obj.(type) {
+ case *Var:
+ what = "field "
+ case *Func:
+ what = "method "
}
- if obj, _, _ = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil {
- why += ", but does have " + changeCase
+ if samePkg(obj.Pkg(), check.pkg) || obj.Exported() {
+ why = check.sprintf("%s, but does have %s%s", why, what, obj.Name())
+ } else if obj.Name() == sel {
+ why = check.sprintf("%s%s is not exported", what, obj.Name())
}
}
}
@@ -857,7 +856,6 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, w
// method expression
m, _ := obj.(*Func)
if m == nil {
- // TODO(gri) should check if capitalization of sel matters and provide better error message in that case
check.errorf(e.Sel, MissingFieldOrMethod, "%s.%s undefined (type %s has no method %s)", x.expr, sel, x.typ, sel)
goto Error
}
diff --git a/src/go/types/expr.go b/src/go/types/expr.go
index 8651ddad93..5b5efd279f 100644
--- a/src/go/types/expr.go
+++ b/src/go/types/expr.go
@@ -1164,7 +1164,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e ast.Expr, hint Type)
check.errorf(kv, InvalidLitField, "invalid field name %s in struct literal", kv.Key)
continue
}
- i := fieldIndex(utyp.fields, check.pkg, key.Name)
+ i := fieldIndex(utyp.fields, check.pkg, key.Name, false)
if i < 0 {
check.errorf(kv, MissingLitField, "unknown field %s in struct literal of type %s", key.Name, base)
continue
diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go
index 7723c43565..82425f64a8 100644
--- a/src/go/types/lookup.go
+++ b/src/go/types/lookup.go
@@ -11,7 +11,6 @@ package types
import (
"bytes"
"go/token"
- "strings"
)
// Internal use of LookupFieldOrMethod: If the obj result is a method
@@ -48,7 +47,12 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
if T == nil {
panic("LookupFieldOrMethod on nil type")
}
+ return lookupFieldOrMethod(T, addressable, pkg, name, false)
+}
+// lookupFieldOrMethod is like LookupFieldOrMethod but with the additional foldCase parameter
+// (see Object.sameId for the meaning of foldCase).
+func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string, foldCase bool) (obj Object, index []int, indirect bool) {
// Methods cannot be associated to a named pointer type.
// (spec: "The type denoted by T is called the receiver base type;
// it must not be a pointer or interface type and it must be declared
@@ -58,7 +62,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// not have found it for T (see also go.dev/issue/8590).
if t := asNamed(T); t != nil {
if p, _ := t.Underlying().(*Pointer); p != nil {
- obj, index, indirect = lookupFieldOrMethodImpl(p, false, pkg, name, false)
+ obj, index, indirect = lookupFieldOrMethodImpl(p, false, pkg, name, foldCase)
if _, ok := obj.(*Func); ok {
return nil, nil, false
}
@@ -66,7 +70,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
}
}
- obj, index, indirect = lookupFieldOrMethodImpl(T, addressable, pkg, name, false)
+ obj, index, indirect = lookupFieldOrMethodImpl(T, addressable, pkg, name, foldCase)
// If we didn't find anything and if we have a type parameter with a core type,
// see if there is a matching field (but not a method, those need to be declared
@@ -75,7 +79,7 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
const enableTParamFieldLookup = false // see go.dev/issue/51576
if enableTParamFieldLookup && obj == nil && isTypeParam(T) {
if t := coreType(T); t != nil {
- obj, index, indirect = lookupFieldOrMethodImpl(t, addressable, pkg, name, false)
+ obj, index, indirect = lookupFieldOrMethodImpl(t, addressable, pkg, name, foldCase)
if _, ok := obj.(*Var); !ok {
obj, index, indirect = nil, nil, false // accept fields (variables) only
}
@@ -84,8 +88,8 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
return
}
-// lookupFieldOrMethodImpl is the implementation of LookupFieldOrMethod.
-// Notably, in contrast to LookupFieldOrMethod, it won't find struct fields
+// lookupFieldOrMethodImpl is the implementation of lookupFieldOrMethod.
+// Notably, in contrast to lookupFieldOrMethod, it won't find struct fields
// in base types of defined (*Named) pointer types T. For instance, given
// the declaration:
//
@@ -94,12 +98,9 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
// lookupFieldOrMethodImpl won't find the field f in the defined (*Named) type T
// (methods on T are not permitted in the first place).
//
-// Thus, lookupFieldOrMethodImpl should only be called by LookupFieldOrMethod
+// Thus, lookupFieldOrMethodImpl should only be called by lookupFieldOrMethod
// 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, 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) {
// WARNING: The code in this function is extremely subtle - do not modify casually!
@@ -169,7 +170,7 @@ func lookupFieldOrMethodImpl(T Type, addressable bool, pkg *Package, name string
case *Struct:
// look for a matching field and collect embedded types
for i, f := range t.fields {
- if f.sameId(pkg, name) {
+ if f.sameId(pkg, name, foldCase) {
assert(f.typ != nil)
index = concat(e.index, i)
if obj != nil || e.multiples {
@@ -579,10 +580,11 @@ func concat(list []int, i int) []int {
}
// fieldIndex returns the index for the field with matching package and name, or a value < 0.
-func fieldIndex(fields []*Var, pkg *Package, name string) int {
+// See Object.sameId for the meaning of foldCase.
+func fieldIndex(fields []*Var, pkg *Package, name string, foldCase bool) int {
if name != "_" {
for i, f := range fields {
- if f.sameId(pkg, name) {
+ if f.sameId(pkg, name, foldCase) {
return i
}
}
@@ -591,12 +593,11 @@ 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
-// and their packages are ignored (e.g., pkg1.m, pkg1.M, pkg2.m, and pkg2.M are all equal).
+// See Object.sameId for the meaning of foldCase.
func lookupMethod(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
if name != "_" {
for i, m := range methods {
- if m.sameId(pkg, name) || foldCase && strings.EqualFold(m.name, name) {
+ if m.sameId(pkg, name, foldCase) {
return i, m
}
}
diff --git a/src/go/types/object.go b/src/go/types/object.go
index 51b3886716..3558c187f2 100644
--- a/src/go/types/object.go
+++ b/src/go/types/object.go
@@ -11,6 +11,7 @@ import (
"fmt"
"go/constant"
"go/token"
+ "strings"
"unicode"
"unicode/utf8"
)
@@ -52,7 +53,9 @@ type Object interface {
setParent(*Scope)
// sameId reports whether obj.Id() and Id(pkg, name) are the same.
- sameId(pkg *Package, name string) bool
+ // If foldCase is true, 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).
+ sameId(pkg *Package, name string, foldCase bool) bool
// scopePos returns the start position of the scope of this Object
scopePos() token.Pos
@@ -165,26 +168,24 @@ func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ =
func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos }
-func (obj *object) sameId(pkg *Package, name string) bool {
+func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool {
+ // If we don't care about capitalization, we also ignore packages.
+ if foldCase && strings.EqualFold(obj.name, name) {
+ return true
+ }
// spec:
// "Two identifiers are different if they are spelled differently,
// or if they appear in different packages and are not exported.
// Otherwise, they are the same."
- if name != obj.name {
+ if obj.name != name {
return false
}
// obj.Name == name
if obj.Exported() {
return true
}
- // not exported, so packages must be the same (pkg == nil for
- // fields in Universe scope; this can only happen for types
- // introduced via Eval)
- if pkg == nil || obj.pkg == nil {
- return pkg == obj.pkg
- }
- // pkg != nil && obj.pkg != nil
- return pkg.path == obj.pkg.path
+ // not exported, so packages must be the same
+ return samePkg(obj.pkg, pkg)
}
// less reports whether object a is ordered before object b.
diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go
index cac2b3c75f..677dff01a0 100644
--- a/src/go/types/predicates.go
+++ b/src/go/types/predicates.go
@@ -207,6 +207,16 @@ func hasNil(t Type) bool {
return false
}
+// samePkg reports whether packages a and b are the same.
+func samePkg(a, b *Package) bool {
+ // package is nil for objects in universe scope
+ if a == nil || b == nil {
+ return a == b
+ }
+ // a != nil && b != nil
+ return a.path == b.path
+}
+
// An ifacePair is a node in a stack of interface type pairs compared for identity.
type ifacePair struct {
x, y *Interface
@@ -271,7 +281,7 @@ func (c *comparer) identical(x, y Type, p *ifacePair) bool {
g := y.fields[i]
if f.embedded != g.embedded ||
!c.ignoreTags && x.Tag(i) != y.Tag(i) ||
- !f.sameId(g.pkg, g.name) ||
+ !f.sameId(g.pkg, g.name, false) ||
!c.identical(f.typ, g.typ, p) {
return false
}
diff --git a/src/go/types/scope.go b/src/go/types/scope.go
index bf646f6882..08d94e55a8 100644
--- a/src/go/types/scope.go
+++ b/src/go/types/scope.go
@@ -275,20 +275,20 @@ func resolve(name string, obj Object) Object {
// stub implementations so *lazyObject implements Object and we can
// store them directly into Scope.elems.
-func (*lazyObject) Parent() *Scope { panic("unreachable") }
-func (*lazyObject) Pos() token.Pos { panic("unreachable") }
-func (*lazyObject) Pkg() *Package { panic("unreachable") }
-func (*lazyObject) Name() string { panic("unreachable") }
-func (*lazyObject) Type() Type { panic("unreachable") }
-func (*lazyObject) Exported() bool { panic("unreachable") }
-func (*lazyObject) Id() string { panic("unreachable") }
-func (*lazyObject) String() string { panic("unreachable") }
-func (*lazyObject) order() uint32 { panic("unreachable") }
-func (*lazyObject) color() color { panic("unreachable") }
-func (*lazyObject) setType(Type) { panic("unreachable") }
-func (*lazyObject) setOrder(uint32) { panic("unreachable") }
-func (*lazyObject) setColor(color color) { panic("unreachable") }
-func (*lazyObject) setParent(*Scope) { panic("unreachable") }
-func (*lazyObject) sameId(pkg *Package, name string) bool { panic("unreachable") }
-func (*lazyObject) scopePos() token.Pos { panic("unreachable") }
-func (*lazyObject) setScopePos(pos token.Pos) { panic("unreachable") }
+func (*lazyObject) Parent() *Scope { panic("unreachable") }
+func (*lazyObject) Pos() token.Pos { panic("unreachable") }
+func (*lazyObject) Pkg() *Package { panic("unreachable") }
+func (*lazyObject) Name() string { panic("unreachable") }
+func (*lazyObject) Type() Type { panic("unreachable") }
+func (*lazyObject) Exported() bool { panic("unreachable") }
+func (*lazyObject) Id() string { panic("unreachable") }
+func (*lazyObject) String() string { panic("unreachable") }
+func (*lazyObject) order() uint32 { panic("unreachable") }
+func (*lazyObject) color() color { panic("unreachable") }
+func (*lazyObject) setType(Type) { panic("unreachable") }
+func (*lazyObject) setOrder(uint32) { panic("unreachable") }
+func (*lazyObject) setColor(color color) { panic("unreachable") }
+func (*lazyObject) setParent(*Scope) { panic("unreachable") }
+func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") }
+func (*lazyObject) scopePos() token.Pos { panic("unreachable") }
+func (*lazyObject) setScopePos(token.Pos) { panic("unreachable") }
diff --git a/src/go/types/unify.go b/src/go/types/unify.go
index d4889b93d9..ffb5b4a74a 100644
--- a/src/go/types/unify.go
+++ b/src/go/types/unify.go
@@ -610,7 +610,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
g := y.fields[i]
if f.embedded != g.embedded ||
x.Tag(i) != y.Tag(i) ||
- !f.sameId(g.pkg, g.name) ||
+ !f.sameId(g.pkg, g.name, false) ||
!u.nify(f.typ, g.typ, emode, p) {
return false
}
diff --git a/src/internal/types/testdata/check/issues0.go b/src/internal/types/testdata/check/issues0.go
index 2f4d266b8a..dc6e0b0b22 100644
--- a/src/internal/types/testdata/check/issues0.go
+++ b/src/internal/types/testdata/check/issues0.go
@@ -137,7 +137,7 @@ func issue10260() {
_ = x /* ERROR "impossible type assertion: x.(T1)\n\tT1 does not implement I1 (method foo has pointer receiver)" */ .(T1)
T1{}.foo /* ERROR "cannot call pointer method foo on T1" */ ()
- x.Foo /* ERROR "x.Foo undefined (type I1 has no field or method Foo, but does have foo)" */ ()
+ x.Foo /* ERROR "x.Foo undefined (type I1 has no field or method Foo, but does have method foo)" */ ()
_ = i2 /* ERROR "impossible type assertion: i2.(*T1)\n\t*T1 does not implement I2 (wrong type for method foo)\n\t\thave foo()\n\t\twant foo(int)" */ .(*T1)
diff --git a/src/internal/types/testdata/check/lookup.go b/src/internal/types/testdata/check/lookup.go
new file mode 100644
index 0000000000..0b15d45157
--- /dev/null
+++ b/src/internal/types/testdata/check/lookup.go
@@ -0,0 +1,73 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package lookup
+
+import "math/big" // provides big.Float struct with unexported fields and methods
+
+func _() {
+ var s struct {
+ x, aBc int
+ }
+ _ = s.x
+ _ = s /* ERROR "invalid operation: cannot call non-function s.x (variable of type int)" */ .x()
+ _ = s.X // ERROR "s.X undefined (type struct{x int; aBc int} has no field or method X, but does have field x)"
+ _ = s.X /* ERROR "s.X undefined (type struct{x int; aBc int} has no field or method X, but does have field x)" */ ()
+
+ _ = s.aBc
+ _ = s.abc // ERROR "s.abc undefined (type struct{x int; aBc int} has no field or method abc, but does have field aBc)"
+ _ = s.ABC // ERROR "s.ABC undefined (type struct{x int; aBc int} has no field or method ABC, but does have field aBc)"
+}
+
+func _() {
+ type S struct {
+ x int
+ }
+ var s S
+ _ = s.x
+ _ = s /* ERROR "invalid operation: cannot call non-function s.x (variable of type int)" */ .x()
+ _ = s.X // ERROR "s.X undefined (type S has no field or method X, but does have field x)"
+ _ = s.X /* ERROR "s.X undefined (type S has no field or method X, but does have field x)" */ ()
+}
+
+type S struct {
+ x int
+}
+
+func (S) m() {}
+func (S) aBc() {}
+
+func _() {
+ var s S
+ _ = s.m
+ s.m()
+ _ = s.M // ERROR "s.M undefined (type S has no field or method M, but does have method m)"
+ s.M /* ERROR "s.M undefined (type S has no field or method M, but does have method m)" */ ()
+
+ _ = s.aBc
+ _ = s.abc // ERROR "s.abc undefined (type S has no field or method abc, but does have method aBc)"
+ _ = s.ABC // ERROR "s.ABC undefined (type S has no field or method ABC, but does have method aBc)"
+}
+
+func _() {
+ type P *S
+ var s P
+ _ = s.m // ERROR "s.m undefined (type P has no field or method m)"
+ _ = s.M // ERROR "s.M undefined (type P has no field or method M)"
+ _ = s.x
+ _ = s.X // ERROR "s.X undefined (type P has no field or method X, but does have field x)"
+}
+
+func _() {
+ var x big.Float
+ _ = x.neg // ERROR "x.neg undefined (type big.Float has no field or method neg, but does have method Neg)"
+ _ = x.nEg // ERROR "x.nEg undefined (type big.Float has no field or method nEg, but does have method Neg)"
+ _ = x.Neg
+ _ = x.NEg // ERROR "x.NEg undefined (type big.Float has no field or method NEg, but does have method Neg)"
+
+ _ = x.form // ERROR "x.form undefined (field form is not exported)"
+ _ = x.fOrm // ERROR "x.fOrm undefined (type big.Float has no field or method fOrm)"
+ _ = x.Form // ERROR "x.Form undefined (type big.Float has no field or method Form)"
+ _ = x.FOrm // ERROR "x.FOrm undefined (type big.Float has no field or method FOrm)"
+}
diff --git a/test/fixedbugs/issue22794.go b/test/fixedbugs/issue22794.go
index 636af26e84..933c83dc5b 100644
--- a/test/fixedbugs/issue22794.go
+++ b/test/fixedbugs/issue22794.go
@@ -13,9 +13,9 @@ type it struct {
func main() {
i1 := it{Floats: true}
- if i1.floats { // ERROR "(type it .* field or method floats, but does have Floats)|undefined field or method"
+ if i1.floats { // ERROR "(type it .* field or method floats, but does have field Floats)|undefined field or method"
}
- i2 := &it{floats: false} // ERROR "(but does have Floats)|unknown field|declared and not used"
- _ = &it{InneR: "foo"} // ERROR "(but does have inner)|unknown field"
+ i2 := &it{floats: false} // ERROR "(but does have field Floats)|unknown field|declared and not used"
+ _ = &it{InneR: "foo"} // ERROR "(but does have field inner)|unknown field"
_ = i2
}