1
0
mirror of https://github.com/golang/go synced 2024-11-14 20:30:35 -07:00

go/types, types2: avoid errors due to missing methods for invalid types

Don't report a (follow-on) error if a method is not found in a type
due to a prior error that made the type invalid, or which caused an
embedded field of a struct to have an invalid type (and thus one
cannot with certainty claim that a method is missing).

Fixes #53535.

Change-Id: Ib2879c6b3b9d927c93bbbf1d355397dd19f336f7
Reviewed-on: https://go-review.googlesource.com/c/go/+/626997
Auto-Submit: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Tim King <taking@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2024-11-11 13:40:45 -08:00 committed by Gopher Robot
parent 83a7626687
commit 66b6b174b6
7 changed files with 103 additions and 8 deletions

View File

@ -299,7 +299,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// Eventually, unify should return an error with cause.
var cause string
constraint := tpar.iface()
if m, _ := check.missingMethod(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause); m != nil {
if !check.hasAllMethods(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause) {
// TODO(gri) better error message (see TODO above)
err.addf(pos, "%s (type %s) does not satisfy %s %s", tpar, tx, tpar.Constraint(), cause)
return nil

View File

@ -277,7 +277,7 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool
}
// V must implement T's methods, if any.
if m, _ := check.missingMethod(V, T, true, Identical, cause); m != nil /* !Implements(V, T) */ {
if !check.hasAllMethods(V, T, true, Identical, cause) /* !Implements(V, T) */ {
if cause != nil {
*cause = check.sprintf("%s does not %s %s %s", V, verb, T, *cause)
}

View File

@ -476,6 +476,37 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
return m, state == wrongSig || state == ptrRecv
}
// hasAllMethods is similar to checkMissingMethod but instead reports whether all methods are present.
// If V is not a valid type, or if it is a struct containing embedded fields with invalid types, the
// result is true because it is not possible to say with certainty whether a method is missing or not
// (an embedded field may have the method in question).
// If the result is false and cause is not nil, *cause describes the error.
// Use hasAllMethods to avoid follow-on errors due to incorrect types.
func (check *Checker) hasAllMethods(V, T Type, static bool, equivalent func(x, y Type) bool, cause *string) bool {
if !isValid(V) {
return true // we don't know anything about V, assume it implements T
}
m, _ := check.missingMethod(V, T, static, equivalent, cause)
return m == nil || hasInvalidEmbeddedFields(V, nil)
}
// hasInvalidEmbeddedFields reports whether T is a struct (or a pointer to a struct) that contains
// (directly or indirectly) embedded fields with invalid types.
func hasInvalidEmbeddedFields(T Type, seen map[*Struct]bool) bool {
if S, _ := under(derefStructPtr(T)).(*Struct); S != nil && !seen[S] {
if seen == nil {
seen = make(map[*Struct]bool)
}
seen[S] = true
for _, f := range S.fields {
if f.embedded && (!isValid(f.typ) || hasInvalidEmbeddedFields(f.typ, seen)) {
return true
}
}
}
return false
}
func isInterfacePtr(T Type) bool {
p, _ := under(T).(*Pointer)
return p != nil && IsInterface(p.base)
@ -519,8 +550,7 @@ func (check *Checker) assertableTo(V, T Type, cause *string) bool {
return true
}
// TODO(gri) fix this for generalized interfaces
m, _ := check.missingMethod(T, V, false, Identical, cause)
return m == nil
return check.hasAllMethods(T, V, false, Identical, cause)
}
// newAssertableTo reports whether a value of type V can be asserted to have type T.

View File

@ -302,7 +302,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
// Eventually, unify should return an error with cause.
var cause string
constraint := tpar.iface()
if m, _ := check.missingMethod(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause); m != nil {
if !check.hasAllMethods(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause) {
// TODO(gri) better error message (see TODO above)
err.addf(posn, "%s (type %s) does not satisfy %s %s", tpar, tx, tpar.Constraint(), cause)
return nil

View File

@ -280,7 +280,7 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool
}
// V must implement T's methods, if any.
if m, _ := check.missingMethod(V, T, true, Identical, cause); m != nil /* !Implements(V, T) */ {
if !check.hasAllMethods(V, T, true, Identical, cause) /* !Implements(V, T) */ {
if cause != nil {
*cause = check.sprintf("%s does not %s %s %s", V, verb, T, *cause)
}

View File

@ -479,6 +479,37 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
return m, state == wrongSig || state == ptrRecv
}
// hasAllMethods is similar to checkMissingMethod but instead reports whether all methods are present.
// If V is not a valid type, or if it is a struct containing embedded fields with invalid types, the
// result is true because it is not possible to say with certainty whether a method is missing or not
// (an embedded field may have the method in question).
// If the result is false and cause is not nil, *cause describes the error.
// Use hasAllMethods to avoid follow-on errors due to incorrect types.
func (check *Checker) hasAllMethods(V, T Type, static bool, equivalent func(x, y Type) bool, cause *string) bool {
if !isValid(V) {
return true // we don't know anything about V, assume it implements T
}
m, _ := check.missingMethod(V, T, static, equivalent, cause)
return m == nil || hasInvalidEmbeddedFields(V, nil)
}
// hasInvalidEmbeddedFields reports whether T is a struct (or a pointer to a struct) that contains
// (directly or indirectly) embedded fields with invalid types.
func hasInvalidEmbeddedFields(T Type, seen map[*Struct]bool) bool {
if S, _ := under(derefStructPtr(T)).(*Struct); S != nil && !seen[S] {
if seen == nil {
seen = make(map[*Struct]bool)
}
seen[S] = true
for _, f := range S.fields {
if f.embedded && (!isValid(f.typ) || hasInvalidEmbeddedFields(f.typ, seen)) {
return true
}
}
}
return false
}
func isInterfacePtr(T Type) bool {
p, _ := under(T).(*Pointer)
return p != nil && IsInterface(p.base)
@ -522,8 +553,7 @@ func (check *Checker) assertableTo(V, T Type, cause *string) bool {
return true
}
// TODO(gri) fix this for generalized interfaces
m, _ := check.missingMethod(T, V, false, Identical, cause)
return m == nil
return check.hasAllMethods(T, V, false, Identical, cause)
}
// newAssertableTo reports whether a value of type V can be asserted to have type T.

View File

@ -0,0 +1,35 @@
// 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 p
import "io"
// test using struct with invalid embedded field
var _ io.Writer = W{} // no error expected here because W has invalid embedded field
type W struct {
*bufio /* ERROR "undefined: bufio" */ .Writer
}
// test using an invalid type
var _ interface{ m() } = &M{} // no error expected here because M is invalid
type M undefined // ERROR "undefined: undefined"
// test using struct with invalid embedded field and containing a self-reference (cycle)
var _ interface{ m() } = &S{} // no error expected here because S is invalid
type S struct {
*S
undefined // ERROR "undefined: undefined"
}
// test using a generic struct with invalid embedded field and containing a self-reference (cycle)
var _ interface{ m() } = &G[int]{} // no error expected here because S is invalid
type G[P any] struct {
*G[P]
undefined // ERROR "undefined: undefined"
}