diff --git a/src/go/types/NOTES b/src/go/types/NOTES index 387550846b..65715fe13e 100644 --- a/src/go/types/NOTES +++ b/src/go/types/NOTES @@ -15,7 +15,9 @@ TODO ---------------------------------------------------------------------------------------------------- KNOWN ISSUES -- 'comparable' predeclared contract is not implemented yet +- parameterized contract embedding is not working correctly +- a non-comparable type used as type argument for a comparable contract leads to sub-optimal error + message (referring to missing method ==) - iteration over generic variables doesn't report certain channel errors (see TODOs in code) - cannot handle mutually recursive parameterized interfaces using themselves as type bounds - contract instantiation requires the type arguments to be type parameters from the type of function diff --git a/src/go/types/examples/contracts.go2 b/src/go/types/examples/contracts.go2 index 7387e9d77c..a3cd4769a1 100644 --- a/src/go/types/examples/contracts.go2 +++ b/src/go/types/examples/contracts.go2 @@ -81,3 +81,33 @@ contract _(T) { Graph /* ERROR not a contract */ (T, T) ( /* ERROR not a contract */ []int) } + +// The predeclared contract "comparable" describes all types +// that support == and != comparisons. +func cmp1(type T comparable)(x, y T) bool { + var _ map[T]int // T is comparable so can use it as map key + return x == y || x != y +} + +func _() { + _ = cmp1(int) + _ = cmp1(float32)(1, 2) + _ = cmp1(42, 53) + _ = cmp1(struct{}) + _ = cmp1([ /* ERROR does not satisfy */ ]byte) +} + +func cmp2(type T1 comparable, T2 comparable)(x1, y1 T1, x2, y2 T2) bool { + return x1 == y1 || x2 != y2 +} + +contract compareTwo(A, B) { + comparable(A) + comparable(B) +} + +// This does not work at the moment due to a contract embedding bug. +// TODO(gri) fix this +// func cmp3(type T1, T2 compareTwo)(x1, y1 T1, x2, y2 T2) bool { +// return x1 == y1 || x2 != y2 +// } diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index 030541766b..04353da498 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -341,6 +341,10 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, // we must have a method (not a field of matching function type) f, _ := obj.(*Func) if f == nil { + // if m is the magic method == and V is comparable, we're ok + if m.name == "==" && Comparable(V) { + continue + } return m, nil } diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index fe8d94b38b..6ce06dc384 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -86,7 +86,10 @@ func Comparable(T Type) bool { case *Array: return Comparable(t.elem) case *TypeParam: - return t.Interface().is(Comparable) + iface := t.Interface() + // If the magic method == exists, the type parameter is comparable. + _, m := lookupMethod(iface.allMethods, nil, "==") + return m != nil || iface.is(Comparable) } return false } diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index 4d9fe75062..2123f15822 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -4,16 +4,48 @@ package main -contract anInt(T) { - T int +type C(type T) interface { + m() } -contract twoInt(K, _) { - anInt(K) +type W(type T) interface { + C(T) } -func f(type K, V twoInt)() +func _(type T W(T))(x T) { + // x.m() // TODO(gri) this doesn't work at the moment - embedding bug +} -func _ () { - f(int, int)() -} \ No newline at end of file +/* +contract C(T) { + T m() +} + +contract W(T) { + C(T) +} + +func _(type T W)(x T) { + x.m() +} +*/ + +/* +contract comparable(T) { + T foo() bool +} + +contract compareTwo(A, B) { + comparable(A) + //comparable(B) +} + +func _(type T1, T2 compareTwo)(x1, y1 T1) bool { + // return x1 == y1 + return x1.foo() +} + +func _(type T comparable)(x, y T) bool { + return x == y || x != y +} +*/ \ No newline at end of file diff --git a/src/go/types/universe.go b/src/go/types/universe.go index ff5b89118a..ff59aad90f 100644 --- a/src/go/types/universe.go +++ b/src/go/types/universe.go @@ -188,6 +188,51 @@ func DefPredeclaredTestFuncs() { def(newBuiltin(_Trace)) } +func defPredeclaredContracts() { + // The "comparable" contract can be envisioned as defined like + // + // contract comparable(T) { + // == (T) untyped bool + // != (T) untyped bool + // } + // + // == and != cannot be user-declared but we can declare + // a magic method == and check for its presence when needed. + // (A simpler approach that simply looks for a magic type + // bound interface is problematic: comparable might be embedded, + // which in turn leads to the embedding of the magic type bound + // interface and then we cannot easily look for that interface + // anymore.) + + // 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() + + // The interface is parameterized with a single + // type parameter to match the comparable contract. + pname := NewTypeName(token.NoPos, nil, "T", nil) + pname.typ = &TypeParam{0, pname, 0, &emptyInterface} + + // The type bound interface needs a name so we can attach the + // type parameter and to match the usual set up of contracts. + iname := NewTypeName(token.NoPos, nil, "comparable_bound", nil) + named := NewNamed(iname, iface, nil) + named.tparams = []*TypeName{pname} + sig.recv = NewVar(token.NoPos, nil, "", named) // complete == signature + + // set up the contract + obj := NewContract(token.NoPos, nil, "comparable") + obj.typ = new(contractType) // mark contract as fully set up + obj.color_ = black + obj.TParams = named.tparams + obj.Bounds = []*Named{named} + + def(obj) +} + func init() { Universe = NewScope(nil, token.NoPos, token.NoPos, "universe") Unsafe = NewPackage("unsafe", "unsafe") @@ -197,6 +242,7 @@ func init() { defPredeclaredConsts() defPredeclaredNil() defPredeclaredFuncs() + defPredeclaredContracts() universeIota = Universe.Lookup("iota").(*Const) universeByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic)