1
0
mirror of https://github.com/golang/go synced 2024-11-06 22:26:11 -07:00

go/types, types2: AssertableTo is undefined for generalized interfaces

Document that AssertableTo is undefined (at least for 1.18) if
the first argument is a generalized interface; i.e., an interface
that may only be used as a constraint in Go code.

Still, implement it as we might expect it to be defined in the
future, to prevent problems down the road due to Hyrum's Law.

While at it, also removed the internal flag forceStrict and its
one use in Checker.assertableTo; forceStrict was never enabled
and if it would have been enabled, the behavior would not have
been correct.

Change-Id: Ie4dc9345c88d04c9640f881132154a002db22643
Reviewed-on: https://go-review.googlesource.com/c/go/+/383917
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2022-02-07 18:37:02 -08:00
parent 0cbe3e00d8
commit c5bce7445e
8 changed files with 102 additions and 66 deletions

View File

@ -421,9 +421,15 @@ func (conf *Config) Check(path string, files []*syntax.File, info *Info) (*Packa
}
// AssertableTo reports whether a value of type V can be asserted to have type T.
// The behavior of AssertableTo is undefined if V is a generalized interface; i.e.,
// an interface that may only be used as a type constraint in Go code.
func AssertableTo(V *Interface, T Type) bool {
m, _ := (*Checker)(nil).assertableTo(V, T)
return m == nil
// Checker.newAssertableTo suppresses errors for invalid types, so we need special
// handling here.
if T.Underlying() == Typ[Invalid] {
return false
}
return (*Checker)(nil).newAssertableTo(V, T) == nil
}
// AssignableTo reports whether a value of type V is assignable to a variable of type T.

View File

@ -2313,27 +2313,27 @@ type Bad Bad // invalid type
conf := Config{Error: func(error) {}}
pkg, _ := conf.Check(f.PkgName.Value, []*syntax.File{f}, nil)
scope := pkg.Scope()
lookup := func(tname string) Type { return pkg.Scope().Lookup(tname).Type() }
var (
EmptyIface = scope.Lookup("EmptyIface").Type().Underlying().(*Interface)
I = scope.Lookup("I").Type().(*Named)
EmptyIface = lookup("EmptyIface").Underlying().(*Interface)
I = lookup("I").(*Named)
II = I.Underlying().(*Interface)
C = scope.Lookup("C").Type().(*Named)
C = lookup("C").(*Named)
CI = C.Underlying().(*Interface)
Integer = scope.Lookup("Integer").Type().Underlying().(*Interface)
EmptyTypeSet = scope.Lookup("EmptyTypeSet").Type().Underlying().(*Interface)
N1 = scope.Lookup("N1").Type()
Integer = lookup("Integer").Underlying().(*Interface)
EmptyTypeSet = lookup("EmptyTypeSet").Underlying().(*Interface)
N1 = lookup("N1")
N1p = NewPointer(N1)
N2 = scope.Lookup("N2").Type()
N2 = lookup("N2")
N2p = NewPointer(N2)
N3 = scope.Lookup("N3").Type()
N4 = scope.Lookup("N4").Type()
Bad = scope.Lookup("Bad").Type()
N3 = lookup("N3")
N4 = lookup("N4")
Bad = lookup("Bad")
)
tests := []struct {
t Type
i *Interface
V Type
T *Interface
want bool
}{
{I, II, true},
@ -2364,8 +2364,20 @@ type Bad Bad // invalid type
}
for _, test := range tests {
if got := Implements(test.t, test.i); got != test.want {
t.Errorf("Implements(%s, %s) = %t, want %t", test.t, test.i, got, test.want)
if got := Implements(test.V, test.T); got != test.want {
t.Errorf("Implements(%s, %s) = %t, want %t", test.V, test.T, got, test.want)
}
// The type assertion x.(T) is valid if T is an interface or if T implements the type of x.
// The assertion is never valid if T is a bad type.
V := test.T
T := test.V
want := false
if _, ok := T.Underlying().(*Interface); (ok || Implements(T, V)) && T != Bad {
want = true
}
if got := AssertableTo(V, T); got != want {
t.Errorf("AssertableTo(%s, %s) = %t, want %t", V, T, got, want)
}
}
}

View File

@ -18,19 +18,6 @@ var nopos syntax.Pos
// debugging/development support
const debug = false // leave on during development
// If forceStrict is set, the type-checker enforces additional
// rules not specified by the Go 1 spec, but which will
// catch guaranteed run-time errors if the respective
// code is executed. In other words, programs passing in
// strict mode are Go 1 compliant, but not all Go 1 programs
// will pass in strict mode. The additional rules are:
//
// - A type assertion x.(T) where T is an interface type
// is invalid if any (statically known) method that exists
// for both x and T have different signatures.
//
const forceStrict = false
// exprInfo stores information about an untyped expression.
type exprInfo struct {
isLhs bool // expression is lhs operand of a shift with delayed type-check

View File

@ -425,18 +425,31 @@ func (check *Checker) funcString(f *Func) string {
// method required by V and whether it is missing or just has the wrong type.
// The receiver may be nil if assertableTo is invoked through an exported API call
// (such as AssertableTo), i.e., when all methods have been type-checked.
// If the global constant forceStrict is set, assertions that are known to fail
// are not permitted.
// TODO(gri) replace calls to this function with calls to newAssertableTo.
func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) {
// no static check is required if T is an interface
// spec: "If T is an interface type, x.(T) asserts that the
// dynamic type of x implements the interface T."
if IsInterface(T) && !forceStrict {
if IsInterface(T) {
return
}
// TODO(gri) fix this for generalized interfaces
return check.missingMethod(T, V, false)
}
// newAssertableTo reports whether a value of type V can be asserted to have type T.
// It also implements behavior for interfaces that currently are only permitted
// in constraint position (we have not yet defined that behavior in the spec).
func (check *Checker) newAssertableTo(V *Interface, T Type) error {
// no static check is required if T is an interface
// spec: "If T is an interface type, x.(T) asserts that the
// dynamic type of x implements the interface T."
if IsInterface(T) {
return nil
}
return check.implements(T, V)
}
// deref dereferences typ if it is a *Pointer and returns its base and true.
// Otherwise it returns (typ, false).
func deref(typ Type) (Type, bool) {

View File

@ -417,9 +417,15 @@ func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, i
}
// AssertableTo reports whether a value of type V can be asserted to have type T.
// The behavior of AssertableTo is undefined if V is a generalized interface; i.e.,
// an interface that may only be used as a type constraint in Go code.
func AssertableTo(V *Interface, T Type) bool {
m, _ := (*Checker)(nil).assertableTo(V, T)
return m == nil
// Checker.newAssertableTo suppresses errors for invalid types, so we need special
// handling here.
if T.Underlying() == Typ[Invalid] {
return false
}
return (*Checker)(nil).newAssertableTo(V, T) == nil
}
// AssignableTo reports whether a value of type V is assignable to a variable of type T.

View File

@ -2306,27 +2306,27 @@ type Bad Bad // invalid type
conf := Config{Error: func(error) {}}
pkg, _ := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil)
scope := pkg.Scope()
lookup := func(tname string) Type { return pkg.Scope().Lookup(tname).Type() }
var (
EmptyIface = scope.Lookup("EmptyIface").Type().Underlying().(*Interface)
I = scope.Lookup("I").Type().(*Named)
EmptyIface = lookup("EmptyIface").Underlying().(*Interface)
I = lookup("I").(*Named)
II = I.Underlying().(*Interface)
C = scope.Lookup("C").Type().(*Named)
C = lookup("C").(*Named)
CI = C.Underlying().(*Interface)
Integer = scope.Lookup("Integer").Type().Underlying().(*Interface)
EmptyTypeSet = scope.Lookup("EmptyTypeSet").Type().Underlying().(*Interface)
N1 = scope.Lookup("N1").Type()
Integer = lookup("Integer").Underlying().(*Interface)
EmptyTypeSet = lookup("EmptyTypeSet").Underlying().(*Interface)
N1 = lookup("N1")
N1p = NewPointer(N1)
N2 = scope.Lookup("N2").Type()
N2 = lookup("N2")
N2p = NewPointer(N2)
N3 = scope.Lookup("N3").Type()
N4 = scope.Lookup("N4").Type()
Bad = scope.Lookup("Bad").Type()
N3 = lookup("N3")
N4 = lookup("N4")
Bad = lookup("Bad")
)
tests := []struct {
t Type
i *Interface
V Type
T *Interface
want bool
}{
{I, II, true},
@ -2357,8 +2357,20 @@ type Bad Bad // invalid type
}
for _, test := range tests {
if got := Implements(test.t, test.i); got != test.want {
t.Errorf("Implements(%s, %s) = %t, want %t", test.t, test.i, got, test.want)
if got := Implements(test.V, test.T); got != test.want {
t.Errorf("Implements(%s, %s) = %t, want %t", test.V, test.T, got, test.want)
}
// The type assertion x.(T) is valid if T is an interface or if T implements the type of x.
// The assertion is never valid if T is a bad type.
V := test.T
T := test.V
want := false
if _, ok := T.Underlying().(*Interface); (ok || Implements(T, V)) && T != Bad {
want = true
}
if got := AssertableTo(V, T); got != want {
t.Errorf("AssertableTo(%s, %s) = %t, want %t", V, T, got, want)
}
}
}

View File

@ -24,19 +24,6 @@ const (
compilerErrorMessages = false // match compiler error messages
)
// If forceStrict is set, the type-checker enforces additional
// rules not specified by the Go 1 spec, but which will
// catch guaranteed run-time errors if the respective
// code is executed. In other words, programs passing in
// strict mode are Go 1 compliant, but not all Go 1 programs
// will pass in strict mode. The additional rules are:
//
// - A type assertion x.(T) where T is an interface type
// is invalid if any (statically known) method that exists
// for both x and T have different signatures.
//
const forceStrict = false
// exprInfo stores information about an untyped expression.
type exprInfo struct {
isLhs bool // expression is lhs operand of a shift with delayed type-check

View File

@ -424,18 +424,31 @@ func (check *Checker) funcString(f *Func) string {
// method required by V and whether it is missing or just has the wrong type.
// The receiver may be nil if assertableTo is invoked through an exported API call
// (such as AssertableTo), i.e., when all methods have been type-checked.
// If the global constant forceStrict is set, assertions that are known to fail
// are not permitted.
// TODO(gri) replace calls to this function with calls to newAssertableTo.
func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) {
// no static check is required if T is an interface
// spec: "If T is an interface type, x.(T) asserts that the
// dynamic type of x implements the interface T."
if IsInterface(T) && !forceStrict {
if IsInterface(T) {
return
}
// TODO(gri) fix this for generalized interfaces
return check.missingMethod(T, V, false)
}
// newAssertableTo reports whether a value of type V can be asserted to have type T.
// It also implements behavior for interfaces that currently are only permitted
// in constraint position (we have not yet defined that behavior in the spec).
func (check *Checker) newAssertableTo(V *Interface, T Type) error {
// no static check is required if T is an interface
// spec: "If T is an interface type, x.(T) asserts that the
// dynamic type of x implements the interface T."
if IsInterface(T) {
return nil
}
return check.implements(T, V)
}
// deref dereferences typ if it is a *Pointer and returns its base and true.
// Otherwise it returns (typ, false).
func deref(typ Type) (Type, bool) {