go/types: implemented AcceptContracts flag to enable/disable contracts

AcceptContracts is a global flag (api.go) to enable/disable contracts.
All go/types tests pass with the flag in either setting, i.e., all
go/types tests involving contracts have been rewritten or disabled.
To ensure the existing go2go code runs, AcceptContracts is set to true
for now.

If contracts are disabled, "comparable" is a predeclared interface
(without type parameters) rather than a predeclared contract (with
one type parameter).

Change-Id: I2ddade9ae38d61d9edca6020f3334d2215b84f1d
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go2-dev/+/734984
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2020-05-01 21:50:31 -07:00
parent 4efa3ddb87
commit bc3ba78503
10 changed files with 103 additions and 44 deletions

View File

@ -34,6 +34,9 @@ import (
"go/token"
)
// If AcceptContracts is set, contracts are accepted.
const AcceptContracts = true
// An Error describes a type-checking error; it implements the error interface.
// A "soft" error is an error that still permits a valid interpretation of a
// package (such as "unused variable"); "hard" errors may lead to unpredictable

View File

@ -104,7 +104,7 @@ var tests = [][]string{
{"testdata/typeparams.go2"},
{"testdata/typeinst.go2"},
{"testdata/typeinst2.go2"},
{"testdata/contracts.go2"},
// {"testdata/contracts.go2"}, // disabled for now
{"testdata/issues.go2"},
{"testdata/todos.go2"},
@ -116,7 +116,7 @@ var tests = [][]string{
{"testdata/linalg.go2"},
// Go 2 prototype examples
{"examples/contracts.go2"},
// {"examples/contracts.go2"}, disabled for now
{"examples/functions.go2"},
{"examples/methods.go2"},
{"examples/types.go2"},

View File

@ -205,6 +205,11 @@ func (check *Checker) objDecl(obj Object, def *Named) {
// functions may be recursive - no need to track dependencies
check.funcDecl(obj, d)
case *Contract:
if !AcceptContracts {
check.errorf(obj.pos, "contracts are not accepted")
obj.typ = Typ[Invalid]
break
}
check.contractDecl(obj, d.cdecl)
default:
unreachable()

View File

@ -16,6 +16,7 @@ import "io"
// the numbers for anInt and twoInt (which embedds anInt).
// The fix simply uses the instantiated non-parameterized
// underlying interface of atInt<K> rather than anInt<K>.
/*
contract anInt(T) {
T int
}
@ -29,8 +30,10 @@ func f(type K, V twoInt)()
func _ () {
f(int, int)()
}
*/
// This is the original (simplified) program causing the same issue.
/*
contract onecomparable(T) {
T int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
@ -49,6 +52,7 @@ func _() {
var m map[int]int
Equal(m, nil)
}
*/
// Interfaces are always comparable (though the comparison may panic at runtime).
func eql(type T comparable)(x, y T) bool {
@ -69,21 +73,23 @@ func _() {
// the pointer in the implementation of the method lookup because
// the type bound of T is an interface an pointer to interface types
// have no methods and then the lookup would fail.
contract C(T) {
T m()
type C(type T) interface {
m()
}
// using contract C
// using type bound C
func _(type T C)(x *T) {
x.m()
}
// using an interface as bound
// TODO(gri) this is now the same as above (no contracts anymore)
func _(type T interface{ m() })(x *T) {
x.m()
}
// This is the original (simplified) program causing the same issue.
/*
type GraphP(type Node, Edge GP) struct {
nodes []*Node
}
@ -95,6 +101,7 @@ contract GP(Node, Edge) {
func (g *GraphP(Node, Edge)) Edges(n *Node) []*Edge {
return n.Edges()
}
*/
// In a generic function body all method calls will be pointer method calls.
// If necessary, the function body will insert temporary variables, not seen
@ -114,6 +121,7 @@ func _() {
}
// This is the original (simplified) program causing the same issue.
/*
func NewP(type Node, Edge GP)(nodes []*Node) *GraphP(Node, Edge) {
return &GraphP(Node, Edge){nodes: nodes}
}
@ -124,6 +132,7 @@ type E struct{}
func F() {
_ = NewP(N, E)(nil)
}
*/
// When a type parameter is used as an argument to instantiate a parameterized
// type with a type list constraint, all of the type argument's types in its
@ -136,6 +145,7 @@ func _(type P)() {
}
// This is the original (simplified) program causing the same issue.
/*
contract Unsigned(T) {
T uint
}
@ -148,14 +158,15 @@ func (u T2(U)) Add1() U {
return u.s + 1
}
func NewT2(type U)() T2(U /* ERROR U has no type constraints */ ) {
return T2(U /* ERROR U has no type constraints */ ){}
func NewT2(type U)() T2(U /- ERROR U has no type constraints -/ ) {
return T2(U /- ERROR U has no type constraints -/ ){}
}
func _() {
u := NewT2(string)()
_ = u.Add1()
}
*/
// When we encounter an instantiated type such as Elem(T) we must
// not "expand" the instantiation when the type to be instantiated

View File

@ -6,13 +6,13 @@ package linalg
import "math"
// Numeric is a contract that matches any numeric type.
// Numeric is type bound that matches any numeric type.
// It would likely be in a contracts package in the standard library.
contract Numeric(T) {
T int; T int8; T int16; T int32; T int64
T uint; T uint8; T uint16; T uint32; T uint64; T uintptr
T float32; T float64
T complex64; T complex128
type Numeric interface {
type int, int8, int16, int32, int64
type uint, uint8, uint16, uint32, uint64, uintptr
type float32, float64
type complex64, complex128
}
func DotProduct(type T Numeric)(s1, s2 []T) T {
@ -27,10 +27,10 @@ func DotProduct(type T Numeric)(s1, s2 []T) T {
}
// NumericAbs matches numeric types with an Abs method.
contract NumericAbs(T) {
Numeric(T)
type NumericAbs(type T) interface {
Numeric
T Abs() T
Abs() T
}
// AbsDifference computes the absolute value of the difference of
@ -40,16 +40,16 @@ func AbsDifference(type T NumericAbs)(a, b T) T {
return d.Abs()
}
// OrderedNumeric matches numeric types that support the < operator.
contract OrderedNumeric(T) {
T int; T int8; T int16; T int32; T int64
T uint; T uint8; T uint16; T uint32; T uint64; T uintptr
T float32; T float64
// OrderedNumeric is a type bound that matches numeric types that support the < operator.
type OrderedNumeric interface {
type int, int8, int16, int32, int64
type uint, uint8, uint16, uint32, uint64, uintptr
type float32, float64
}
// Complex matches the two complex types, which do not have a < operator.
contract Complex(T) {
T complex64; T complex128
// Complex is a type bound that matches the two complex types, which do not have a < operator.
type Complex interface {
type complex64, complex128
}
// OrderedAbs is a helper type that defines an Abs method for

View File

@ -1,15 +1,21 @@
package p
type Adder(type T) interface {
Add(T) T
// These are only ok if AcceptContracts = false.
// (The comparable contract cannot be embedded in Bound.)
/*
func _(type T comparable)(x, y T) bool {
return x == y || x != y
}
// We don't need to explicitly instantiate the Adder bound
// if we have exactly one type parameter.
func Sum(type T Adder)(list []T) T {
var sum T
for _, x := range list {
sum = sum.Add(x)
}
return sum
type Bound interface {
comparable
}
func _(type T Bound)(x, y T) bool {
return x == y || x != y
}
func _(type A, B Bound)(a1, a2 A, b1, b2 B) bool {
return a1 == a2 || b1 != b2
}
*/

View File

@ -9,9 +9,12 @@
package p
// Pointer designation for type parameters is not yet supported.
// TODO(gri) Do we need to do something about this now that we have only interfaces?
/*
contract _C(T) {
* /* ERROR not yet supported */ T m()
*T m()
}
*/
// Indexing on generic types containing type parameters in their type list
// is not yet supported.

View File

@ -22,7 +22,7 @@ type (
func _() {
var x1 T1(int)
var x2 T2(int, float32)
x1.f1.f2 = 0
x1.f1 = x2
}
@ -81,8 +81,8 @@ func (it Iterator(K)) Next() K {
// A more complex test case testing type bounds (extracted from linalg.go2 and reduced to essence)
contract NumericAbs(T) {
T Abs() T
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T NumericAbs)(x T)

View File

@ -55,8 +55,8 @@ func _(type T interface{type int, float32, bool})(x, y T) bool { return x /* ERR
func _(type T C1)(x, y T) bool { return x /* ERROR cannot compare */ < y }
func _(type T C2)(x, y T) bool { return x < y }
contract C1(T) {}
contract C2(T) { T int, float32 }
type C1(type T) interface{}
type C2(type T) interface{ type int, float32 }
func new(type T)() *T {
var x T
@ -365,10 +365,10 @@ func _(type T I3)(x I3, p T) {
}
// error messages related to type bounds mention those bounds
contract C(P) {}
type C(type P) interface{}
func _(type P C) (x P) {
x.m /* ERROR contract C has no method m */ ()
x.m /* ERROR x.m undefined */ ()
}
type I interface {}

View File

@ -188,7 +188,7 @@ func DefPredeclaredTestFuncs() {
def(newBuiltin(_Trace))
}
func defPredeclaredContracts() {
func defPredeclaredComparableContract() {
// The "comparable" contract can be envisioned as defined like
//
// contract comparable(T) {
@ -233,6 +233,33 @@ func defPredeclaredContracts() {
def(obj)
}
func defPredeclaredComparableInterface() {
// The "comparable" interface can be envisioned as defined like
//
// type comparable interface {
// == () untyped bool
// != () untyped bool
// }
//
// == and != cannot be user-declared but we can declare
// a magic method == and check for its presence when needed.
// Define interface { == () }. We don't care about the signature
// for == so leave it empty except for the receiver, which is
// set up later to match the usual interface method assumptions.
sig := new(Signature)
eql := NewFunc(token.NoPos, nil, "==", sig)
iface := NewInterfaceType([]*Func{eql}, nil).Complete()
// set up the defined type for the interface
obj := NewTypeName(token.NoPos, nil, "comparable", nil)
named := NewNamed(obj, iface, nil)
obj.color_ = black
sig.recv = NewVar(token.NoPos, nil, "", named) // complete == signature
def(obj)
}
func init() {
Universe = NewScope(nil, token.NoPos, token.NoPos, "universe")
Unsafe = NewPackage("unsafe", "unsafe")
@ -242,7 +269,11 @@ func init() {
defPredeclaredConsts()
defPredeclaredNil()
defPredeclaredFuncs()
defPredeclaredContracts()
if AcceptContracts {
defPredeclaredComparableContract()
} else {
defPredeclaredComparableInterface()
}
universeIota = Universe.Lookup("iota").(*Const)
universeByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic)