diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index 65d760f3b7..a81bdc70b4 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -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 */, "", 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 { diff --git a/src/go/types/examples/contracts.go2 b/src/go/types/examples/contracts.go2 index 009a504f6c..cd199f0c4c 100644 --- a/src/go/types/examples/contracts.go2 +++ b/src/go/types/examples/contracts.go2 @@ -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 diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 5f5e14f75d..f01ea780d5 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -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 } diff --git a/src/go/types/testdata/contracts.go2 b/src/go/types/testdata/contracts.go2 index ef128e1115..e83db783fe 100644 --- a/src/go/types/testdata/contracts.go2 +++ b/src/go/types/testdata/contracts.go2 @@ -143,4 +143,21 @@ func sum(type T Integer)(data []T) T { s += x } return s -} \ No newline at end of file +} + +// 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)) +} diff --git a/src/go/types/type.go b/src/go/types/type.go index 0b50fd6f31..6883c37b8d 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -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) }