]> Cypherpunks.ru repositories - gostls13.git/commitdiff
go/types, types2: AssertableTo is undefined for generalized interfaces
authorRobert Griesemer <gri@golang.org>
Tue, 8 Feb 2022 02:37:02 +0000 (18:37 -0800)
committerRobert Griesemer <gri@golang.org>
Tue, 8 Feb 2022 23:29:18 +0000 (23:29 +0000)
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>
src/cmd/compile/internal/types2/api.go
src/cmd/compile/internal/types2/api_test.go
src/cmd/compile/internal/types2/check.go
src/cmd/compile/internal/types2/lookup.go
src/go/types/api.go
src/go/types/api_test.go
src/go/types/check.go
src/go/types/lookup.go

index ee4f275bc072ff7bc5f1dd3928d98571068cc56d..6230c58401ad39938fbf902187ae171429a4516b 100644 (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.
index 094374f7f1b5b0a9689ed16dd55a5e608acf787c..46b184f53c9ce436c6679cab6d2683c384c3c2c6 100644 (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)
                }
        }
 }
index bfed16993b5c19bbeb34e70bb4f811cd381239aa..535de0256cb203af8f4e8bf373b2f8e4b4ee6b11 100644 (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
index b8ddd94cd78a46cafaec1449a903eff423669d5e..9987da485463e70e7fc3579702696a851f3ebf66 100644 (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) {
index 2776e05232c87e75ede4da800be64258617515ef..828461477b91013ab230888b598311afdf980354 100644 (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.
index a18ee16c7b57640ea988d3d8716e514f847ac07a..85452dffe63dae537eeffdb6f4dee56815115791 100644 (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)
                }
        }
 }
index a0c3700254c5127b728408878e5c2bcfb2eee58d..6e1da04b9fcc88cb08fca7ab6b98813aea3a585c 100644 (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
index f2f38be266059112410caef1d57f8d0859b8fb3f..9f1cd7667ca0c649ae5d99081178f51b8844f033 100644 (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) {