go/types: first cut at supporting real/imag/complex with type parameters

Change-Id: Ic0dba1a5e758942a03d114df10cb5536cf432985
This commit is contained in:
Robert Griesemer 2019-10-10 16:42:34 -07:00
parent e4a39e916f
commit 660f7c38d1
5 changed files with 99 additions and 34 deletions

View File

@ -265,7 +265,21 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
}
// the argument types must be of floating-point type
if !isFloat(x.typ) {
f := func(x Type) Type {
if t, _ := x.Underlying().(*Basic); t != nil {
switch t.kind {
case Float32:
return Typ[Complex64]
case Float64:
return Typ[Complex128]
case UntypedFloat:
return Typ[UntypedComplex]
}
}
return nil
}
resTyp := applyTypeFunc(f, x.typ)
if resTyp == nil {
check.invalidArg(x.pos(), "arguments have type %s, expected floating-point", x.typ)
return
}
@ -277,20 +291,6 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
x.mode = value
}
// determine result type
var res BasicKind
switch x.typ.Underlying().(*Basic).kind {
case Float32:
res = Complex64
case Float64:
res = Complex128
case UntypedFloat:
res = UntypedComplex
default:
unreachable()
}
resTyp := Typ[res]
if check.Types != nil && x.mode != constant_ {
check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ, x.typ))
}
@ -383,7 +383,21 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
}
// the argument must be of complex type
if !isComplex(x.typ) {
f := func(x Type) Type {
if t, _ := x.Underlying().(*Basic); t != nil {
switch t.kind {
case Complex64:
return Typ[Float32]
case Complex128:
return Typ[Float64]
case UntypedComplex:
return Typ[UntypedFloat]
}
}
return nil
}
resTyp := applyTypeFunc(f, x.typ)
if resTyp == nil {
check.invalidArg(x.pos(), "argument has type %s, expected complex type", x.typ)
return
}
@ -399,20 +413,6 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
x.mode = value
}
// determine result type
var res BasicKind
switch x.typ.Underlying().(*Basic).kind {
case Complex64:
res = Float32
case Complex128:
res = Float64
case UntypedComplex:
res = UntypedFloat
default:
unreachable()
}
resTyp := Typ[res]
if check.Types != nil && x.mode != constant_ {
check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ))
}
@ -647,6 +647,51 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
return true
}
// applyTypeFunc applies f to x. If x is a type parameter,
// the result is an anonymous type parameter constrained by
// an unnamed contract. The type constraints of that contract
// are computed by applying f to each of the contract type
// constraints of x. If any of these applications of f return
// nil, applyTypeFunc returns nil.
// If x is not a type parameter, the result is f(x).
func applyTypeFunc(f func(Type) Type, x Type) Type {
if tp, _ := x.Underlying().(*TypeParam); tp != nil {
// Test if t satisfies the requirements for the argument
// type and collect possible result types at the same time.
var resTypes []Type
if !tp.Interface().is(func(x Type) bool {
if r := f(x); r != nil {
resTypes = append(resTypes, r)
return true
}
return false
}) {
return nil
}
// TODO(gri) Would it be ok to return just the one type
// if len(resType) == 1? What about top-level
// uses of real() where the result is used to
// define type and initialize a variable?
// construct a suitable new type parameter
tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "<type parameter literal>", nil)
resTyp := NewTypeParam(tpar, 0, nil) // assigns type to tpar as a side-effect
// construct a corresponding interface and contract
iface := &Interface{allMethods: markComplete, types: resTypes}
contr := &Contract{
TParams: []*TypeName{tpar},
IFaces: map[*TypeName]*Interface{tpar: iface},
}
resTyp.contr = contr
return resTyp
}
return f(x)
}
// makeSig makes a signature for the given argument and result types.
// Default types are used for untyped arguments, and res may be nil.
func makeSig(res Type, args ...Type) *Signature {

View File

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
// This file shows examples of contract declarations.
// They are not type-checked at the moment.
package p

View File

@ -21,7 +21,7 @@ func is(typ Type, what BasicInfo) bool {
case *Basic:
return t.info&what != 0
case *TypeParam:
return t.contr.ifaceAt(t.index).is(func(typ Type) bool { return is(typ, what) })
return t.Interface().is(func(typ Type) bool { return is(typ, what) })
}
return false
}

View File

@ -143,4 +143,21 @@ func sum(type T Integer)(data []T) T {
s += x
}
return s
}
}
// Contracts and built-ins whose result type depends on the argument type.
contract FloatComplex(F, C) {
F float32, float64
C complex64, complex128
}
func _(type F, C FloatComplex)(c C) {
_ = real(c)
_ = imag(c)
re := real(c)
im := imag(c)
var _ F = re // TODO(gri) why does this work? (no type conversion needed?)
var _ F = im // TODO(gri) why does this work? (no type conversion needed?)
c = C(complex(re, im))
}

View File

@ -521,6 +521,9 @@ type Parameterized struct {
}
// A Contract represents a contract.
// TODO(gri) Do we need the ability to represent unnamed type parameter literals?
// For instance, when creating (result) type parameters out of whole cloth
// say for the result type of real/imag(x) where x is of a parameterized type.
type Contract struct {
TParams []*TypeName
IFaces map[*TypeName]*Interface
@ -559,6 +562,7 @@ func NewTypeParam(obj *TypeName, index int, contr *Contract) *TypeParam {
// Interface returns the type parameter's interface as
// specified via its contract. If there is no contract,
// the result is the empty interface.
// TODO(gri) should this be Underlying instead?
func (t *TypeParam) Interface() *Interface {
return t.contr.ifaceAt(t.index)
}
@ -578,7 +582,7 @@ func (c *Chan) Underlying() Type { return c }
func (t *Named) Underlying() Type { return t.underlying }
func (p *Parameterized) Underlying() Type { return p.tname.typ.Underlying() }
func (c *Contract) Underlying() Type { return c }
func (t *TypeParam) Underlying() Type { return t }
func (t *TypeParam) Underlying() Type { return t } // TODO(gri) should this return t.Interface() instead?
func (b *Basic) String() string { return TypeString(b, nil) }
func (a *Array) String() string { return TypeString(a, nil) }