mirror of https://github.com/golang/go.git
go/types: implement predeclared contract "comparable"
The comparable contract defines a magic method "==" which enables comparisons with == and != . Comparable types automagically implement this method. TODO: If a type is not comparable but a comparable type is expected, the error message can be confusing (missing == method). Change-Id: Ie0d89b87c36d83549f7d869c18dd9786151adbae
This commit is contained in:
parent
e6f000d2f9
commit
10b59aa539
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)()
|
||||
}
|
||||
/*
|
||||
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
|
||||
}
|
||||
*/
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue