mirror of
https://github.com/golang/go
synced 2024-11-25 07:17:56 -07:00
go/types, types2: better error message when using *interface instead of interface
- detect *interface case and report specific error - replaced switch with sequence of if's for more clarity - fixed isInterfacePtr: it applies to all interfaces, incl. type parameters - reviewed/fixed all uses of isInterfacePtr - adjusted error messages to be consistently of the format "type %s is pointer to interface, not interface" Fixes #48312. Change-Id: Ic3c8cfcf93ad57ecdb60f6a727cce9e1aa4afb5d Reviewed-on: https://go-review.googlesource.com/c/go/+/376914 Trust: Robert Griesemer <gri@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
1f411e9b6d
commit
6019a52d4d
@ -531,41 +531,51 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr) {
|
||||
|
||||
obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel)
|
||||
if obj == nil {
|
||||
switch {
|
||||
case index != nil:
|
||||
if index != nil {
|
||||
// TODO(gri) should provide actual type where the conflict happens
|
||||
check.errorf(e.Sel, "ambiguous selector %s.%s", x.expr, sel)
|
||||
case indirect:
|
||||
check.errorf(e.Sel, "cannot call pointer method %s on %s", sel, x.typ)
|
||||
default:
|
||||
var why string
|
||||
if tpar, _ := x.typ.(*TypeParam); tpar != nil {
|
||||
// Type parameter bounds don't specify fields, so don't mention "field".
|
||||
if tname := tpar.iface().obj; tname != nil {
|
||||
why = check.sprintf("interface %s has no method %s", tname.name, sel)
|
||||
} else {
|
||||
why = check.sprintf("type bound for %s has no method %s", x.typ, sel)
|
||||
}
|
||||
} 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.
|
||||
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:]
|
||||
}
|
||||
if obj, _, _ = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil {
|
||||
why += ", but does have " + changeCase
|
||||
}
|
||||
}
|
||||
|
||||
check.errorf(e.Sel, "%s.%s undefined (%s)", x.expr, sel, why)
|
||||
|
||||
goto Error
|
||||
}
|
||||
|
||||
if indirect {
|
||||
check.errorf(e.Sel, "cannot call pointer method %s on %s", sel, x.typ)
|
||||
goto Error
|
||||
}
|
||||
|
||||
if isInterfacePtr(x.typ) {
|
||||
check.errorf(e.Sel, "%s.%s undefined (type %s is pointer to interface, not interface)", x.expr, sel, x.typ)
|
||||
goto Error
|
||||
}
|
||||
|
||||
var why string
|
||||
if tpar, _ := x.typ.(*TypeParam); tpar != nil {
|
||||
// Type parameter bounds don't specify fields, so don't mention "field".
|
||||
// TODO(gri) Type constraints may have accessible fields now. Revisit this.
|
||||
if tname := tpar.iface().obj; tname != nil {
|
||||
why = check.sprintf("interface %s has no method %s", tname.name, sel)
|
||||
} else {
|
||||
why = check.sprintf("type bound for %s has no method %s", x.typ, sel)
|
||||
}
|
||||
} 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:]
|
||||
}
|
||||
if obj, _, _ = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil {
|
||||
why += ", but does have " + changeCase
|
||||
}
|
||||
}
|
||||
|
||||
check.errorf(e.Sel, "%s.%s undefined (%s)", x.expr, sel, why)
|
||||
goto Error
|
||||
}
|
||||
|
||||
|
@ -448,12 +448,12 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
|
||||
// an extra formatting option for types2.Type that doesn't print out
|
||||
// 'func'.
|
||||
r = strings.Replace(r, "^^func", "", -1)
|
||||
} else if IsInterface(T) && !isTypeParam(T) {
|
||||
} else if IsInterface(T) {
|
||||
if isInterfacePtr(V) {
|
||||
r = fmt.Sprintf("(%s is pointer to interface, not interface)", V)
|
||||
r = fmt.Sprintf("(type %s is pointer to interface, not interface)", V)
|
||||
}
|
||||
} else if isInterfacePtr(T) && !isTypeParam(T) {
|
||||
r = fmt.Sprintf("(%s is pointer to interface, not interface)", T)
|
||||
} else if isInterfacePtr(T) {
|
||||
r = fmt.Sprintf("(type %s is pointer to interface, not interface)", T)
|
||||
}
|
||||
if r == "" {
|
||||
r = fmt.Sprintf("(missing %s)", mname)
|
||||
@ -463,7 +463,7 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
|
||||
|
||||
func isInterfacePtr(T Type) bool {
|
||||
p, _ := under(T).(*Pointer)
|
||||
return p != nil && IsInterface(p.base) && !isTypeParam(p.base)
|
||||
return p != nil && IsInterface(p.base)
|
||||
}
|
||||
|
||||
// assertableTo reports whether a value of type V can be asserted to have type T.
|
||||
|
@ -317,7 +317,7 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
|
||||
if check != nil && check.conf.CompilerErrorMessages {
|
||||
if isInterfacePtr(Tu) {
|
||||
if reason != nil {
|
||||
*reason = check.sprintf("%s does not implement %s (%s is pointer to interface, not interface)", x.typ, T, T)
|
||||
*reason = check.sprintf("%s does not implement %s (type %s is pointer to interface, not interface)", x.typ, T, T)
|
||||
}
|
||||
return false, _InvalidIfaceAssign
|
||||
}
|
||||
|
@ -196,9 +196,9 @@ func issue5918() {
|
||||
_ func(error) string = error.Error
|
||||
|
||||
perr = &err
|
||||
_ = perr.Error /* ERROR "no field or method" */ ()
|
||||
_ func() string = perr.Error /* ERROR "no field or method" */
|
||||
_ func(*error) string = (*error).Error /* ERROR "no field or method" */
|
||||
_ = perr.Error /* ERROR "type \*error is pointer to interface, not interface" */ ()
|
||||
_ func() string = perr.Error /* ERROR "type \*error is pointer to interface, not interface" */
|
||||
_ func(*error) string = (*error).Error /* ERROR "type \*error is pointer to interface, not interface" */
|
||||
)
|
||||
|
||||
type T *interface{ m() int }
|
||||
@ -207,8 +207,8 @@ func issue5918() {
|
||||
_ = (*x).m()
|
||||
_ = (*x).m
|
||||
|
||||
_ = x.m /* ERROR "no field or method" */ ()
|
||||
_ = x.m /* ERROR "no field or method" */
|
||||
_ = T.m /* ERROR "no field or method" */
|
||||
_ = x.m /* ERROR "type T is pointer to interface, not interface" */ ()
|
||||
_ = x.m /* ERROR "type T is pointer to interface, not interface" */
|
||||
_ = T.m /* ERROR "type T is pointer to interface, not interface" */
|
||||
)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func _[P interface{ m() }](x P) {
|
||||
x.m()
|
||||
// (&x).m doesn't exist because &x is of type *P
|
||||
// and pointers to type parameters don't have methods
|
||||
(&x).m /* ERROR \*P has no field or method m */ ()
|
||||
(&x).m /* ERROR type \*P is pointer to interface, not interface */ ()
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ type T2 interface{ m() }
|
||||
func _(x *T2) {
|
||||
// x.m doesn't exists because x is of type *T2
|
||||
// and pointers to interfaces don't have methods
|
||||
x.m /* ERROR \*T2 has no field or method m */()
|
||||
x.m /* ERROR type \*T2 is pointer to interface, not interface */()
|
||||
}
|
||||
|
||||
// Test case 1 from issue
|
||||
|
20
src/cmd/compile/internal/types2/testdata/fixedbugs/issue48312.go2
vendored
Normal file
20
src/cmd/compile/internal/types2/testdata/fixedbugs/issue48312.go2
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2022 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 p
|
||||
|
||||
type T interface{ m() }
|
||||
type P *T
|
||||
|
||||
func _(p *T) {
|
||||
p.m /* ERROR type \*T is pointer to interface, not interface */ ()
|
||||
}
|
||||
|
||||
func _(p P) {
|
||||
p.m /* ERROR type P is pointer to interface, not interface */ ()
|
||||
}
|
||||
|
||||
func _[P T](p *P) {
|
||||
p.m /* ERROR type \*P is pointer to interface, not interface */ ()
|
||||
}
|
@ -533,40 +533,52 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) {
|
||||
|
||||
obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel)
|
||||
if obj == nil {
|
||||
switch {
|
||||
case index != nil:
|
||||
if index != nil {
|
||||
// TODO(gri) should provide actual type where the conflict happens
|
||||
check.errorf(e.Sel, _AmbiguousSelector, "ambiguous selector %s.%s", x.expr, sel)
|
||||
case indirect:
|
||||
check.errorf(e.Sel, _InvalidMethodExpr, "cannot call pointer method %s on %s", sel, x.typ)
|
||||
default:
|
||||
var why string
|
||||
if tpar, _ := x.typ.(*TypeParam); tpar != nil {
|
||||
// Type parameter bounds don't specify fields, so don't mention "field".
|
||||
if tname := tpar.iface().obj; tname != nil {
|
||||
why = check.sprintf("interface %s has no method %s", tname.name, sel)
|
||||
} else {
|
||||
why = check.sprintf("type bound for %s has no method %s", x.typ, sel)
|
||||
}
|
||||
} 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.
|
||||
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:]
|
||||
}
|
||||
if obj, _, _ = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil {
|
||||
why += ", but does have " + changeCase
|
||||
}
|
||||
}
|
||||
|
||||
check.errorf(e.Sel, _MissingFieldOrMethod, "%s.%s undefined (%s)", x.expr, sel, why)
|
||||
goto Error
|
||||
}
|
||||
|
||||
if indirect {
|
||||
check.errorf(e.Sel, _InvalidMethodExpr, "cannot call pointer method %s on %s", sel, x.typ)
|
||||
goto Error
|
||||
}
|
||||
|
||||
if isInterfacePtr(x.typ) {
|
||||
check.errorf(e.Sel, _InvalidMethodExpr, "%s.%s undefined (type %s is pointer to interface, not interface)", x.expr, sel, x.typ)
|
||||
goto Error
|
||||
}
|
||||
|
||||
var why string
|
||||
if tpar, _ := x.typ.(*TypeParam); tpar != nil {
|
||||
// Type parameter bounds don't specify fields, so don't mention "field".
|
||||
// TODO(gri) Type constraints may have accessible fields now. Revisit this.
|
||||
if tname := tpar.iface().obj; tname != nil {
|
||||
why = check.sprintf("interface %s has no method %s", tname.name, sel)
|
||||
} else {
|
||||
why = check.sprintf("type bound for %s has no method %s", x.typ, sel)
|
||||
}
|
||||
} 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:]
|
||||
}
|
||||
if obj, _, _ = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil {
|
||||
why += ", but does have " + changeCase
|
||||
}
|
||||
}
|
||||
|
||||
check.errorf(e.Sel, _MissingFieldOrMethod, "%s.%s undefined (%s)", x.expr, sel, why)
|
||||
goto Error
|
||||
}
|
||||
|
||||
|
@ -422,12 +422,12 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
|
||||
// an extra formatting option for types2.Type that doesn't print out
|
||||
// 'func'.
|
||||
r = strings.Replace(r, "^^func", "", -1)
|
||||
} else if IsInterface(T) && !isTypeParam(T) {
|
||||
} else if IsInterface(T) {
|
||||
if isInterfacePtr(V) {
|
||||
r = fmt.Sprintf("(%s is pointer to interface, not interface)", V)
|
||||
r = fmt.Sprintf("(type %s is pointer to interface, not interface)", V)
|
||||
}
|
||||
} else if isInterfacePtr(T) && !isTypeParam(T) {
|
||||
r = fmt.Sprintf("(%s is pointer to interface, not interface)", T)
|
||||
} else if isInterfacePtr(T) {
|
||||
r = fmt.Sprintf("(type %s is pointer to interface, not interface)", T)
|
||||
}
|
||||
if r == "" {
|
||||
r = fmt.Sprintf("(missing %s)", mname)
|
||||
@ -437,7 +437,7 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
|
||||
|
||||
func isInterfacePtr(T Type) bool {
|
||||
p, _ := under(T).(*Pointer)
|
||||
return p != nil && IsInterface(p.base) && !isTypeParam(T)
|
||||
return p != nil && IsInterface(p.base)
|
||||
}
|
||||
|
||||
// assertableTo reports whether a value of type V can be asserted to have type T.
|
||||
|
@ -308,7 +308,7 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
|
||||
if check != nil && compilerErrorMessages {
|
||||
if isInterfacePtr(Tu) {
|
||||
if reason != nil {
|
||||
*reason = check.sprintf("%s does not implement %s (%s is pointer to interface, not interface)", x.typ, T, T)
|
||||
*reason = check.sprintf("%s does not implement %s (type %s is pointer to interface, not interface)", x.typ, T, T)
|
||||
}
|
||||
return false, _InvalidIfaceAssign
|
||||
}
|
||||
|
12
src/go/types/testdata/check/methodsets.src
vendored
12
src/go/types/testdata/check/methodsets.src
vendored
@ -196,9 +196,9 @@ func issue5918() {
|
||||
_ func(error) string = error.Error
|
||||
|
||||
perr = &err
|
||||
_ = perr.Error /* ERROR "no field or method" */ ()
|
||||
_ func() string = perr.Error /* ERROR "no field or method" */
|
||||
_ func(*error) string = (*error).Error /* ERROR "no field or method" */
|
||||
_ = perr.Error /* ERROR "type \*error is pointer to interface, not interface" */ ()
|
||||
_ func() string = perr.Error /* ERROR "type \*error is pointer to interface, not interface" */
|
||||
_ func(*error) string = (*error).Error /* ERROR "type \*error is pointer to interface, not interface" */
|
||||
)
|
||||
|
||||
type T *interface{ m() int }
|
||||
@ -207,8 +207,8 @@ func issue5918() {
|
||||
_ = (*x).m()
|
||||
_ = (*x).m
|
||||
|
||||
_ = x.m /* ERROR "no field or method" */ ()
|
||||
_ = x.m /* ERROR "no field or method" */
|
||||
_ = T.m /* ERROR "no field or method" */
|
||||
_ = x.m /* ERROR "type T is pointer to interface, not interface" */ ()
|
||||
_ = x.m /* ERROR "type T is pointer to interface, not interface" */
|
||||
_ = T.m /* ERROR "type T is pointer to interface, not interface" */
|
||||
)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func _[P interface{ m() }](x P) {
|
||||
x.m()
|
||||
// (&x).m doesn't exist because &x is of type *P
|
||||
// and pointers to type parameters don't have methods
|
||||
(&x).m /* ERROR \*P has no field or method m */ ()
|
||||
(&x).m /* ERROR type \*P is pointer to interface, not interface */ ()
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ type T2 interface{ m() }
|
||||
func _(x *T2) {
|
||||
// x.m doesn't exists because x is of type *T2
|
||||
// and pointers to interfaces don't have methods
|
||||
x.m /* ERROR \*T2 has no field or method m */()
|
||||
x.m /* ERROR type \*T2 is pointer to interface, not interface */()
|
||||
}
|
||||
|
||||
// Test case 1 from issue
|
||||
|
20
src/go/types/testdata/fixedbugs/issue48312.go2
vendored
Normal file
20
src/go/types/testdata/fixedbugs/issue48312.go2
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2022 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 p
|
||||
|
||||
type T interface{ m() }
|
||||
type P *T
|
||||
|
||||
func _(p *T) {
|
||||
p.m /* ERROR type \*T is pointer to interface, not interface */ ()
|
||||
}
|
||||
|
||||
func _(p P) {
|
||||
p.m /* ERROR type P is pointer to interface, not interface */ ()
|
||||
}
|
||||
|
||||
func _[P T](p *P) {
|
||||
p.m /* ERROR type \*P is pointer to interface, not interface */ ()
|
||||
}
|
@ -32,7 +32,7 @@ func g() {
|
||||
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 = new(I) // ERROR "cannot use new\(I\) \(.*type \*I\) as type I in assignment:\n\t\*I does not implement I \(type \*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\)\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
|
||||
|
@ -28,7 +28,7 @@ type Val interface {
|
||||
val() int
|
||||
}
|
||||
|
||||
var _ = (*Val).val // ERROR "method"
|
||||
var _ = (*Val).val // ERROR "method|type \*Val is pointer to interface, not interface"
|
||||
|
||||
var v Val
|
||||
var pv = &v
|
||||
|
Loading…
Reference in New Issue
Block a user