go/types: steps towards customization of methods in bounds checks (snapshot)

This CL introduces an update mechanism to customize method signatures
with the correct type parameters before comparing them for equality.
The mechanism is not correctly used, yet.

Change-Id: Ib88af88e151578a3fb3a648ed70d3f462a936c9b
This commit is contained in:
Robert Griesemer 2019-12-16 16:47:33 -08:00
parent e1203396e7
commit 118728b8c2
6 changed files with 74 additions and 26 deletions

View File

@ -1,5 +1,4 @@
TODO
- allow recursive type parameterization without need to repeat type parameters
- if type parameters are repeated in recursive instantiation, they must be the same order (not yet checked)
- implement contract embedding
- interface embedding doesn't take care of literal type constraints yet
@ -7,15 +6,15 @@ TODO
OPEN ISSUES
- using a contract and enumerating type arguments currently leads to an error (e.g. func f(type T C(T)) (x T) ... )
git add - contracts slip through in places where only types are permitted
- contracts slip through in places where only types are permitted
- parameterized interface methods (of type bounds) need to be customized (subst) for context
DESIGN DECISIONS
- 12/4/2019: do not allow parenthesized generic uninstantiated types (unless instantiated implicitly)
In other words: generic types must always be instantiated before they can be used in any form
More generally: Only permit type instantiation T(x) in type context, when the type is a named type.
Do not permit in in general in type context: e.g., disallow []T(x) because we consider that a
Do not permit it in general in type context: e.g., disallow []T(x) because we consider that a
conversion, in general. Same for ([]T)(x).
- 12/12/2019: represent type bounds always as (possibly unnamed) interfaces
(contracts are for user only)
(contracts are user syntactic sugar)

View File

@ -5,6 +5,7 @@
package types
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
@ -53,6 +54,9 @@ func pathString(path []Object) string {
// For the meaning of def, see Checker.definedType, in typexpr.go.
func (check *Checker) objDecl(obj Object, def *Named) {
if check.conf.Trace && obj.Type() == nil {
if check.indent == 0 {
fmt.Println() // empty line between top-lebvel objects for readability
}
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
check.indent++
defer func() {

View File

@ -265,7 +265,7 @@ func (check *Checker) lookupType(m map[Type]int, typ Type) (int, bool) {
// x is of interface type V).
//
func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) {
m, typ := (*Checker)(nil).missingMethod(V, T, static)
m, typ := (*Checker)(nil).missingMethod(V, T, static, nil)
return m, typ != nil
}
@ -273,11 +273,15 @@ func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType b
// The receiver may be nil if missingMethod is invoked through
// an exported API call (such as MissingMethod), i.e., when all
// methods have been type-checked.
// If a non-nil update function is provided, it is used to update
// the method types of V before comparing them with the methods
// of V (usually be renaming type parameters so they can be
// compared).
// If the type has the correctly named method, but with the wrong
// signature, the existing method is returned as well.
// To improve error messages, also report the wrong signature
// when the method exists on *V instead of V.
func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) {
func (check *Checker) missingMethod(V Type, T *Interface, static bool, update func(Type) Type) (method, wrongType *Func) {
check.completeInterface(token.NoPos, T)
// fast path for common case
@ -289,16 +293,26 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
check.completeInterface(token.NoPos, ityp)
// TODO(gri) allMethods is sorted - can do this more efficiently
for _, m := range T.allMethods {
_, obj := lookupMethod(ityp.allMethods, m.pkg, m.name)
switch {
case obj == nil:
_, f := lookupMethod(ityp.allMethods, m.pkg, m.name)
if f == nil {
if static {
return m, nil
}
case !check.identical(obj.Type(), m.typ):
return m, obj
continue
}
// update (generic) method signatures before comparing them
ftyp := f.Type()
if update != nil {
ftyp = update(ftyp)
}
if !check.identical(ftyp, m.typ) {
return m, f
}
}
return
}
@ -326,7 +340,13 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
check.objDecl(f, nil)
}
if !check.identical(f.typ, m.typ) {
// update (generic) method signatures before comparing them
ftyp := f.typ
if update != nil {
ftyp = update(ftyp)
}
if !check.identical(ftyp, m.typ) {
return m, f
}
}
@ -346,7 +366,7 @@ func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Fun
if _, ok := T.Underlying().(*Interface); ok && !strict {
return
}
return check.missingMethod(T, V, false)
return check.missingMethod(T, V, false, nil)
}
// deref dereferences typ if it is a *Pointer and returns its base and true.

View File

@ -268,7 +268,7 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) bool {
// T is an interface type and x implements T
if Ti, ok := Tu.(*Interface); ok {
if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ {
if m, wrongType := check.missingMethod(V, Ti, true, nil); m != nil /* Implements(V, Ti) */ {
if reason != nil {
if wrongType != nil {
if check.identical(m.typ, wrongType.typ) {

View File

@ -81,8 +81,16 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
iface = check.subst(pos, iface, tparams, targs).(*Interface)
//check.dump(">>> %s: iface after : %s", pos, iface)
// update targ method signatures
update := func(typ Type) Type {
// check.dump(">>> %s: sig before: %s (tparams = %s)", pos, typ, typ.(*Signature).tparams)
typ = check.subst(pos, typ, tparams, targs)
// check.dump(">>> %s: sig after : %s", pos, typ)
return typ
}
// targ must implement iface (methods)
if m, _ := check.missingMethod(targ, iface, true); m != nil {
if m, _ := check.missingMethod(targ, iface, true, update); m != nil {
check.softErrorf(pos, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m)
break
}

View File

@ -4,17 +4,34 @@
package p
contract C(T) {
T int
T Abs() T
}
type B(type T) interface {
type int
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T C)(a, b T) T {
d := a - b
return d.Abs()
}
func AbsDifference(type T NumericAbs(T))(x T)
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)(x T) {
// TODO(gri) this should work
AbsDifference /* ERROR does not satisfy */ (OrderedAbs(T)(x))
}
// TODO(gri) Once the code above works, check the code below.
/*
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T NumericAbs(T))()
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)() {
AbsDifference(OrderedAbs(T))()
}
*/