go/types: implement type conversion methods

Provide easy accessors to a type's underlying type. Instead of

	if t, _ := typ.Underlying().(*XXX); t != nil { ... }

we can now write

	if t := typ.XXX(); t != nil { ... }

where XXX is a type such as Basic, Array, Slice, Pointer, etc.
This removes a type assertion in all cases where typ is not a *Named
type and the code is easier to read.

Also, made Named.Underlying more robust by tracking cycles and nil
underlying types.

The Named.Underlying change is a first step towards making type
checking more lazy which eventually should permit dealing with
cycles that we cannot yet handle properly.

This change briefly breaks some external code (gcimporter, gccgoimporter)
due to the change in semantics of type.Underlying. This will be fixed by
the next change.

Passes go/types tests (but not all.bash).

Change-Id: I493a949e554da6f2a944e7e9b1f1f6f38f597042
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go2-dev/+/717606
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2020-04-09 23:22:20 -07:00
parent 04f9e20db3
commit 63f4460f70
17 changed files with 266 additions and 142 deletions

View File

@ -125,3 +125,17 @@ type anyType struct{}
func (t anyType) Underlying() types.Type { return t }
func (t anyType) String() string { return "any" }
// types.aType is not exported for now so we need to implemented these here.
func (anyType) Basic() *types.Basic { return nil }
func (anyType) Array() *types.Array { return nil }
func (anyType) Slice() *types.Slice { return nil }
func (anyType) Struct() *types.Struct { return nil }
func (anyType) Pointer() *types.Pointer { return nil }
func (anyType) Tuple() *types.Tuple { return nil }
func (anyType) Signature() *types.Signature { return nil }
func (anyType) Interface() *types.Interface { return nil }
func (anyType) Map() *types.Map { return nil }
func (anyType) Chan() *types.Chan { return nil }
func (anyType) Named() *types.Named { return nil }
func (anyType) TypeParam() *types.TypeParam { return nil }

View File

@ -51,7 +51,7 @@ func (check *Checker) assignment(x *operand, T Type, context string) {
// x.typ is typed
// A generic (non-instantiated) function value cannot be assigned to a variable.
if sig, _ := x.typ.Underlying().(*Signature); sig != nil && len(sig.tparams) > 0 {
if sig := x.typ.Signature(); sig != nil && len(sig.tparams) > 0 {
check.errorf(x.pos(), "cannot use generic function %s without instantiation in %s", x, context)
}

View File

@ -81,7 +81,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// of S and the respective parameter passing rules apply."
S := x.typ
var T Type
if s, _ := S.Underlying().(*Slice); s != nil {
if s := S.Slice(); s != nil {
T = s.elem
} else {
check.invalidArg(x.pos(), "%s is not a slice", x)
@ -176,7 +176,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
}
case *TypeParam:
if t.Interface().is(func(t Type) bool {
if t.Bound().is(func(t Type) bool {
switch t.(type) {
case *Basic:
if isString(t) && id == _Len {
@ -209,7 +209,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
case _Close:
// close(c)
c, _ := x.typ.Underlying().(*Chan)
c := x.typ.Chan()
if c == nil {
check.invalidArg(x.pos(), "%s is not a channel", x)
return
@ -285,7 +285,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// the argument types must be of floating-point type
f := func(x Type) Type {
if t, _ := x.Underlying().(*Basic); t != nil {
if t := x.Basic(); t != nil {
switch t.kind {
case Float32:
return Typ[Complex64]
@ -319,7 +319,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
case _Copy:
// copy(x, y []T) int
var dst Type
if t, _ := x.typ.Underlying().(*Slice); t != nil {
if t := x.typ.Slice(); t != nil {
dst = t.elem
}
@ -356,7 +356,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
case _Delete:
// delete(m, k)
m, _ := x.typ.Underlying().(*Map)
m := x.typ.Map()
if m == nil {
check.invalidArg(x.pos(), "%s is not a map", x)
return
@ -403,7 +403,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// the argument must be of complex type
f := func(x Type) Type {
if t, _ := x.Underlying().(*Basic); t != nil {
if t := x.Basic(); t != nil {
switch t.kind {
case Complex64:
return Typ[Float32]
@ -674,11 +674,11 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// applyTypeFunc returns nil.
// If x is not a type parameter, the result is f(x).
func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
if tp, _ := x.Underlying().(*TypeParam); tp != nil {
if tp := x.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 !tp.Bound().is(func(x Type) bool {
if r := f(x); r != nil {
resTypes = append(resTypes, r)
return true
@ -725,7 +725,7 @@ func makeSig(res Type, args ...Type) *Signature {
//
func implicitArrayDeref(typ Type) Type {
if p, ok := typ.(*Pointer); ok {
if a, ok := p.base.Underlying().(*Array); ok {
if a := p.base.Array(); a != nil {
return a
}
}

View File

@ -67,7 +67,7 @@ func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind {
// function/method call
cgocall := x.mode == cgofunc
sig, _ := x.typ.Underlying().(*Signature)
sig := x.typ.Signature()
if sig == nil {
check.invalidOp(x.pos(), "cannot call non-function %s", x)
x.mode = invalid

View File

@ -147,7 +147,7 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
for _, targ := range targs {
tpar := targ.(*TypeParam)
iface := bounds[tpar.index].underlying.(*Interface)
embed := tpar.Interface() // don't use Named form of tpar.bound
embed := tpar.Bound() // don't use Named form of tpar.bound
iface.embeddeds = append(iface.embeddeds, embed)
check.posMap[iface] = append(check.posMap[iface], econtr.Pos()) // satisfy completeInterface requirements
// check.contractExpr assigned a type bound to its incoming type arguments,
@ -176,7 +176,7 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
// Contracts don't have types, but we need to set a type to
// detect recursive declarations and satisfy assertions.
type contractType struct{}
type contractType struct{ aType }
func (contractType) String() string { return "<dummy contract type>" }
func (contractType) Underlying() Type { panic("unreachable") }

View File

@ -17,7 +17,7 @@ func (check *Checker) conversion(x *operand, T Type) {
switch {
case constArg && isConstType(T):
// constant conversion
switch t := T.Underlying().(*Basic); {
switch t := T.Basic(); {
case representableConst(x.val, check, t, &x.val):
ok = true
case isInteger(x.typ) && isString(t):
@ -137,27 +137,26 @@ func (x *operand) convertibleTo(check *Checker, T Type) bool {
}
func isUintptr(typ Type) bool {
t, ok := typ.Underlying().(*Basic)
return ok && t.kind == Uintptr
t := typ.Basic()
return t != nil && t.kind == Uintptr
}
func isUnsafePointer(typ Type) bool {
// TODO(gri): Is this (typ.Underlying() instead of just typ) correct?
// The spec does not say so, but gc claims it is. See also
// issue 6326.
t, ok := typ.Underlying().(*Basic)
return ok && t.kind == UnsafePointer
t := typ.Basic()
return t != nil && t.kind == UnsafePointer
}
func isPointer(typ Type) bool {
_, ok := typ.Underlying().(*Pointer)
return ok
return typ.Pointer() != nil
}
func isBytesOrRunes(typ Type) bool {
if s, ok := typ.(*Slice); ok {
t, ok := s.elem.Underlying().(*Basic)
return ok && (t.kind == Byte || t.kind == Rune)
if s := typ.Slice(); s != nil {
t := s.elem.Basic()
return t != nil && (t.kind == Byte || t.kind == Rune)
}
return false
}

View File

@ -94,8 +94,8 @@ func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) {
return
case token.ARROW:
typ, ok := x.typ.Underlying().(*Chan)
if !ok {
typ := x.typ.Chan()
if typ == nil {
check.invalidOp(x.pos(), "cannot receive from non-channel %s", x)
x.mode = invalid
return
@ -117,7 +117,7 @@ func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) {
}
if x.mode == constant_ {
typ := x.typ.Underlying().(*Basic)
typ := x.typ.Basic()
var prec uint
if isUnsigned(typ) {
prec = uint(check.conf.sizeof(typ) * 8)
@ -444,7 +444,7 @@ func (check *Checker) updateExprType(x ast.Expr, typ Type, final bool) {
// If the new type is not final and still untyped, just
// update the recorded type.
if !final && isUntyped(typ) {
old.typ = typ.Underlying().(*Basic)
old.typ = typ.Basic()
check.untyped[x] = old
return
}
@ -512,8 +512,8 @@ func (check *Checker) convertUntyped(x *operand, target Type) {
// In case of a type parameter, conversion must succeed against
// all types enumerated by the the type parameter bound.
if t, _ := target.Underlying().(*TypeParam); t != nil {
types := t.Interface().allTypes
if t := target.TypeParam(); t != nil {
types := t.Bound().allTypes
if len(types) == 0 {
goto Error
}
@ -742,7 +742,7 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) {
if e != nil {
x.expr = e // for better error message
}
check.representable(x, x.typ.Underlying().(*Basic))
check.representable(x, x.typ.Basic())
}
return
}
@ -878,7 +878,7 @@ func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, o
if x.mode == constant_ && y.mode == constant_ {
xval := x.val
yval := y.val
typ := x.typ.Underlying().(*Basic)
typ := x.typ.Basic()
// force integer division of integer operands
if op == token.QUO && isInteger(typ) {
op = token.QUO_ASSIGN
@ -1255,7 +1255,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
duplicate := false
// if the key is of interface type, the type is also significant when checking for duplicates
xkey := keyVal(x.val)
if _, ok := utyp.key.Underlying().(*Interface); ok {
if utyp.key.Interface() != nil {
for _, vtyp := range visited[xkey] {
if check.identical(vtyp, x.typ) {
duplicate = true
@ -1338,7 +1338,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
x.typ = typ.elem
case *Pointer:
if typ, _ := typ.base.Underlying().(*Array); typ != nil {
if typ := typ.base.Array(); typ != nil {
valid = true
length = typ.len
x.mode = variable
@ -1367,7 +1367,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
// in its type bound support indexing and have the
// same element type.
var elem Type
if typ.Interface().is(func(t Type) bool {
if typ.Bound().is(func(t Type) bool {
var e Type
switch t := t.(type) {
case *Basic:
@ -1377,7 +1377,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
case *Array:
e = t.elem
case *Pointer:
if t, _ := t.base.Underlying().(*Array); t != nil {
if t := t.base.Array(); t != nil {
e = t.elem
}
case *Slice:
@ -1449,7 +1449,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
x.typ = &Slice{elem: typ.elem}
case *Pointer:
if typ, _ := typ.base.Underlying().(*Array); typ != nil {
if typ := typ.base.Array(); typ != nil {
valid = true
length = typ.len
x.typ = &Slice{elem: typ.elem}
@ -1528,7 +1528,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
case *Interface:
xtyp = t
case *TypeParam:
xtyp = t.Interface()
xtyp = t.Bound()
strict = true
default:
check.invalidOp(x.pos(), "%s is not an interface or generic type", x)
@ -1558,7 +1558,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
case typexpr:
x.typ = &Pointer{base: x.typ}
default:
if typ, ok := x.typ.Underlying().(*Pointer); ok {
if typ := x.typ.Pointer(); typ != nil {
x.mode = variable
x.typ = typ.base
} else {

View File

@ -384,9 +384,12 @@ func TestIssue28005(t *testing.T) {
}
}
if obj == nil {
t.Fatal("interface not found")
t.Fatal("object X not found")
}
iface := obj.Type().Interface() // object X must be an interface
if iface == nil {
t.Fatalf("%s is not an interface", obj)
}
iface := obj.Type().Underlying().(*Interface) // I must be an interface
// Each iface method m is embedded; and m's receiver base type name
// must match the method's name per the choice in the source file.
@ -406,7 +409,7 @@ func TestIssue28282(t *testing.T) {
it := NewInterfaceType(nil, []Type{et})
it.Complete()
// verify that after completing the interface, the embedded method remains unchanged
want := et.Underlying().(*Interface).Method(0)
want := et.Interface().Method(0)
got := it.Method(0)
if got != want {
t.Fatalf("%s.Method(0): got %q (%p); want %q (%p)", it, got, got, want, want)

View File

@ -300,7 +300,7 @@ func (check *Checker) missingMethod(V Type, addressable bool, T *Interface, stat
return
}
if ityp, _ := V.Underlying().(*Interface); ityp != nil {
if ityp := V.Interface(); ityp != nil {
check.completeInterface(token.NoPos, ityp)
// TODO(gri) allMethods is sorted - can do this more efficiently
for _, m := range T.allMethods {
@ -411,7 +411,7 @@ func (check *Checker) assertableTo(V *Interface, T Type, strict bool) (method, w
// no static check is required if T is an interface
// spec: "If T is an interface type, x.(T) asserts that the
// dynamic type of x implements the interface T."
if _, ok := T.Underlying().(*Interface); ok && !(strict || forceStrict) {
if T.Interface() != nil && !(strict || forceStrict) {
return
}
return check.missingMethod(T, false, V, false)
@ -438,8 +438,8 @@ func derefUnpack(typ Type) (Type, bool) {
// derefStructPtr dereferences typ if it is a (named or unnamed) pointer to a
// (named or unnamed) struct and returns its base. Otherwise it returns typ.
func derefStructPtr(typ Type) Type {
if p, _ := typ.Underlying().(*Pointer); p != nil {
if _, ok := p.base.Underlying().(*Struct); ok {
if p := typ.Pointer(); p != nil {
if p.base.Struct() != nil {
return p.base
}
}

View File

@ -31,7 +31,7 @@ func is(typ Type, what BasicInfo) bool {
case *Basic:
return t.info&what != 0
case *TypeParam:
return t.Interface().is(func(typ Type) bool { return is(typ, what) })
return t.Bound().is(func(typ Type) bool { return is(typ, what) })
}
return false
}
@ -45,26 +45,25 @@ func isNumeric(typ Type) bool { return is(typ, IsNumeric) }
func isString(typ Type) bool { return is(typ, IsString) }
func isTyped(typ Type) bool {
t, ok := typ.Underlying().(*Basic)
return !ok || t.info&IsUntyped == 0
t := typ.Basic()
return t == nil || t.info&IsUntyped == 0
}
func isUntyped(typ Type) bool {
t, ok := typ.Underlying().(*Basic)
return ok && t.info&IsUntyped != 0
t := typ.Basic()
return t != nil && t.info&IsUntyped != 0
}
func isOrdered(typ Type) bool { return is(typ, IsOrdered) }
func isConstType(typ Type) bool {
t, ok := typ.Underlying().(*Basic)
return ok && t.info&IsConstType != 0
t := typ.Basic()
return t != nil && t.info&IsConstType != 0
}
// IsInterface reports whether typ is an interface type.
func IsInterface(typ Type) bool {
_, ok := typ.Underlying().(*Interface)
return ok
return typ.Interface() != nil
}
// Comparable reports whether values of type T are comparable.
@ -86,7 +85,7 @@ func Comparable(T Type) bool {
case *Array:
return Comparable(t.elem)
case *TypeParam:
iface := t.Interface()
iface := t.Bound()
// If the magic method == exists, the type parameter is comparable.
_, m := lookupMethod(iface.allMethods, nil, "==")
return m != nil || iface.is(Comparable)

View File

@ -239,7 +239,7 @@ func (conf *Config) offsetsof(T *Struct) []int64 {
func (conf *Config) offsetof(typ Type, index []int) int64 {
var o int64
for _, i := range index {
s := typ.Underlying().(*Struct)
s := typ.Struct()
o += conf.offsetsof(s)[i]
typ = s.fields[i].typ
}

View File

@ -241,7 +241,7 @@ func typecheck(t *testing.T, path string, filenames []string) {
// Perform checks of API invariants.
// All Objects have a package, except predeclared ones.
errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
errorError := Universe.Lookup("error").Type().Interface().ExplicitMethod(0) // (error).Error
for id, obj := range info.Uses {
predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
if predeclared == (obj.Pkg() != nil) {

View File

@ -350,8 +350,8 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
return
}
tch, ok := ch.typ.Underlying().(*Chan)
if !ok {
tch := ch.typ.Chan()
if tch == nil {
check.invalidOp(s.Arrow, "cannot send to non-chan type %s", ch.typ)
return
}
@ -616,7 +616,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
case *Interface:
xtyp = t
case *TypeParam:
xtyp = t.Interface()
xtyp = t.Bound()
strict = true
default:
check.errorf(x.pos(), "%s is not an interface or generic type", &x)
@ -883,7 +883,7 @@ func rangeKeyVal(typ Type, wantKey, wantVal bool) (Type, Type, string) {
case *Slice:
return Typ[Int], typ.elem, ""
case *Pointer:
if typ, _ := typ.base.Underlying().(*Array); typ != nil {
if typ := typ.base.Array(); typ != nil {
return Typ[Int], typ.elem, ""
}
case *Map:
@ -898,7 +898,7 @@ func rangeKeyVal(typ Type, wantKey, wantVal bool) (Type, Type, string) {
first := true
var key, val Type
var msg string
typ.Interface().is(func(t Type) bool {
typ.Bound().is(func(t Type) bool {
k, v, m := rangeKeyVal(t, wantKey, wantVal)
if k == nil || m != "" {
key, val, msg = k, v, m

View File

@ -111,7 +111,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
// check bounds
for i, tname := range tparams {
tpar := tname.typ.(*TypeParam)
iface := tpar.Interface()
iface := tpar.Bound()
if iface.Empty() {
continue // no type bound
}
@ -161,8 +161,8 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
// If targ is itself a type parameter, each of its possible types, but at least one, must be in the
// list of iface types (i.e., the targ type list must be a non-empty subset of the iface types).
if targ, _ := targ.Underlying().(*TypeParam); targ != nil {
targBound := targ.Interface()
if targ := targ.TypeParam(); targ != nil {
targBound := targ.Bound()
if len(targBound.allTypes) == 0 {
check.softErrorf(pos, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ)
break
@ -226,24 +226,24 @@ func (subst *subster) typ(typ Type) Type {
case *Array:
elem := subst.typ(t.elem)
if elem != t.elem {
return &Array{t.len, elem}
return &Array{len: t.len, elem: elem}
}
case *Slice:
elem := subst.typ(t.elem)
if elem != t.elem {
return &Slice{elem}
return &Slice{elem: elem}
}
case *Struct:
if fields, copied := subst.varList(t.fields); copied {
return &Struct{fields, t.tags}
return &Struct{fields: fields, tags: t.tags}
}
case *Pointer:
base := subst.typ(t.base)
if base != t.base {
return &Pointer{base}
return &Pointer{base: base}
}
case *Tuple:
@ -256,7 +256,15 @@ func (subst *subster) typ(typ Type) Type {
params := subst.tuple(t.params)
results := subst.tuple(t.results)
if recv != t.recv || params != t.params || results != t.results {
return &Signature{t.rparams, t.tparams, t.scope, recv, params, results, t.variadic}
return &Signature{
rparams: t.rparams,
tparams: t.tparams,
scope: t.scope,
recv: recv,
params: params,
results: results,
variadic: t.variadic,
}
}
case *Interface:
@ -274,13 +282,13 @@ func (subst *subster) typ(typ Type) Type {
key := subst.typ(t.key)
elem := subst.typ(t.elem)
if key != t.key || elem != t.elem {
return &Map{key, elem}
return &Map{key: key, elem: elem}
}
case *Chan:
elem := subst.typ(t.elem)
if elem != t.elem {
return &Chan{t.dir, elem}
return &Chan{dir: t.dir, elem: elem}
}
case *Named:
@ -398,7 +406,7 @@ func (subst *subster) var_(v *Var) *Var {
func (subst *subster) tuple(t *Tuple) *Tuple {
if t != nil {
if vars, copied := subst.varList(t.vars); copied {
return &Tuple{vars}
return &Tuple{vars: vars}
}
}
return t

View File

@ -14,8 +14,38 @@ type Type interface {
// String returns a string representation of a type.
String() string
// Converters
Basic() *Basic
Array() *Array
Slice() *Slice
Struct() *Struct
Pointer() *Pointer
Tuple() *Tuple
Signature() *Signature
Interface() *Interface
Map() *Map
Chan() *Chan
Named() *Named
TypeParam() *TypeParam
}
// aType implements default type behavior
type aType struct{}
func (aType) Basic() *Basic { return nil }
func (aType) Array() *Array { return nil }
func (aType) Slice() *Slice { return nil }
func (aType) Struct() *Struct { return nil }
func (aType) Pointer() *Pointer { return nil }
func (aType) Tuple() *Tuple { return nil }
func (aType) Signature() *Signature { return nil }
func (aType) Interface() *Interface { return nil }
func (aType) Map() *Map { return nil }
func (aType) Chan() *Chan { return nil }
func (aType) Named() *Named { return nil }
func (aType) TypeParam() *TypeParam { return nil }
// BasicKind describes the kind of basic type.
type BasicKind int
@ -79,6 +109,7 @@ type Basic struct {
kind BasicKind
info BasicInfo
name string
aType
}
// Kind returns the kind of basic type b.
@ -94,11 +125,12 @@ func (b *Basic) Name() string { return b.name }
type Array struct {
len int64
elem Type
aType
}
// NewArray returns a new array type for the given element type and length.
// A negative length indicates an unknown length.
func NewArray(elem Type, len int64) *Array { return &Array{len, elem} }
func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} }
// Len returns the length of array a.
// A negative result indicates an unknown length.
@ -110,10 +142,11 @@ func (a *Array) Elem() Type { return a.elem }
// A Slice represents a slice type.
type Slice struct {
elem Type
aType
}
// NewSlice returns a new slice type for the given element type.
func NewSlice(elem Type) *Slice { return &Slice{elem} }
func NewSlice(elem Type) *Slice { return &Slice{elem: elem} }
// Elem returns the element type of slice s.
func (s *Slice) Elem() Type { return s.elem }
@ -122,6 +155,7 @@ func (s *Slice) Elem() Type { return s.elem }
type Struct struct {
fields []*Var
tags []string // field tags; nil if there are no tags
aType
}
// NewStruct returns a new struct with the given fields and corresponding field tags.
@ -158,6 +192,7 @@ func (s *Struct) Tag(i int) string {
// A Pointer represents a pointer type.
type Pointer struct {
base Type // element type
aType
}
// NewPointer returns a new pointer type for the given element (base) type.
@ -171,16 +206,23 @@ func (p *Pointer) Elem() Type { return p.base }
// assignments; they are not first class types of Go.
type Tuple struct {
vars []*Var
aType
}
// NewTuple returns a new tuple for the given variables.
func NewTuple(x ...*Var) *Tuple {
if len(x) > 0 {
return &Tuple{x}
return &Tuple{vars: x}
}
return nil
}
// We cannot rely on the embedded Basic() method because (*Tuple)(nil)
// is a valid *Tuple value but (*Tuple)(nil).Basic() would panic without
// this implementation.
// TODO(gri) It seems that at the moment we only need this converter.
func (*Tuple) Basic() *Basic { return nil }
// Len returns the number variables of tuple t.
func (t *Tuple) Len() int {
if t != nil {
@ -206,6 +248,7 @@ type Signature struct {
params *Tuple // (incoming) parameters from left to right; or nil
results *Tuple // (outgoing) results from left to right; or nil
variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only)
aType
}
// NewSignature returns a new function type for the given receiver, parameters,
@ -222,7 +265,7 @@ func NewSignature(recv *Var, params, results *Tuple, variadic bool) *Signature {
panic("types.NewSignature: variadic parameter must be of unnamed slice type")
}
}
return &Signature{nil, nil, nil, recv, params, results, variadic}
return &Signature{recv: recv, params: params, results: results, variadic: variadic}
}
// Recv returns the receiver of signature s (if a method), or nil if a
@ -256,6 +299,8 @@ type Interface struct {
allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset)
allTypes []Type // list of types declared with or embedded in this interface
aType
}
// is reports whether interface t represents types that all satisfy pred.
@ -374,23 +419,23 @@ func (t *Interface) Empty() bool {
// empty reports whether interface t is empty without requiring the
// interface to be complete. Should only be called by Interface.Empty.
func empty(t *Interface, visited map[*Interface]bool) bool {
func empty(t *Interface, seen map[*Interface]bool) bool {
if len(t.methods) != 0 || len(t.types) != 0 {
return false
}
for _, e := range t.embeddeds {
// e should be an interface but be careful (it may be invalid)
if e, _ := e.Underlying().(*Interface); e != nil {
if e := e.Interface(); e != nil {
// Cyclic interfaces such as "type E interface { E }" are not permitted
// but they are still constructed and we need to detect such cycles.
if visited[e] {
if seen[e] {
continue
}
if visited == nil {
visited = make(map[*Interface]bool)
if seen == nil {
seen = make(map[*Interface]bool)
}
visited[e] = true
if !empty(e, visited) {
seen[e] = true
if !empty(e, seen) {
return false
}
}
@ -444,7 +489,7 @@ func (t *Interface) Complete() *Interface {
types = append(types, t.types...)
for _, typ := range t.embeddeds {
typ := typ.Underlying().(*Interface)
typ := typ.Interface()
typ.Complete()
for _, m := range typ.allMethods {
addMethod(m, false)
@ -472,11 +517,12 @@ func (t *Interface) Complete() *Interface {
// A Map represents a map type.
type Map struct {
key, elem Type
aType
}
// NewMap returns a new map for the given key and element types.
func NewMap(key, elem Type) *Map {
return &Map{key, elem}
return &Map{key: key, elem: elem}
}
// Key returns the key type of map m.
@ -489,6 +535,7 @@ func (m *Map) Elem() Type { return m.elem }
type Chan struct {
dir ChanDir
elem Type
aType
}
// A ChanDir value indicates a channel direction.
@ -503,7 +550,7 @@ const (
// NewChan returns a new channel type for the given direction and element type.
func NewChan(dir ChanDir, elem Type) *Chan {
return &Chan{dir, elem}
return &Chan{dir: dir, elem: elem}
}
// Dir returns the direction of channel c.
@ -521,6 +568,7 @@ type Named struct {
tparams []*TypeName // type parameters, or nil
targs []Type // type arguments (after instantiation), or nil
methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily
aType
}
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
@ -540,6 +588,21 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
// Obj returns the type name for the named type t.
func (t *Named) Obj() *TypeName { return t.obj }
// Converter methods
func (t *Named) Basic() *Basic { return t.Underlying().Basic() }
func (t *Named) Array() *Array { return t.Underlying().Array() }
func (t *Named) Slice() *Slice { return t.Underlying().Slice() }
func (t *Named) Struct() *Struct { return t.Underlying().Struct() }
func (t *Named) Pointer() *Pointer { return t.Underlying().Pointer() }
func (t *Named) Tuple() *Tuple { return t.Underlying().Tuple() }
func (t *Named) Signature() *Signature { return t.Underlying().Signature() }
func (t *Named) Interface() *Interface { return t.Underlying().Interface() }
func (t *Named) Map() *Map { return t.Underlying().Map() }
func (t *Named) Chan() *Chan { return t.Underlying().Chan() }
// func (t *Named) Named() *Named // declared below
func (t *Named) TypeParam() *TypeParam { return t.Underlying().TypeParam() }
// TODO(gri) Come up with a better representation and API to distinguish
// between parameterized instantiated and non-instantiated types.
@ -583,12 +646,13 @@ type TypeParam struct {
obj *TypeName // corresponding type name
index int // parameter index
bound Type // *Named or *Interface; underlying type is always *Interface
aType
}
// NewTypeParam returns a new TypeParam.
func (check *Checker) NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam {
assert(bound != nil)
typ := &TypeParam{check.nextId, obj, index, bound}
typ := &TypeParam{id: check.nextId, obj: obj, index: index, bound: bound}
check.nextId++
if obj.typ == nil {
obj.typ = typ
@ -596,36 +660,73 @@ func (check *Checker) NewTypeParam(obj *TypeName, index int, bound Type) *TypePa
return typ
}
func (t *TypeParam) Interface() *Interface {
iface := t.bound.Underlying().(*Interface)
func (t *TypeParam) Bound() *Interface {
iface := t.bound.Interface()
iface.Complete() // TODO(gri) should we use check.completeInterface instead?
return iface
}
// Implementations for Type methods.
func (b *Basic) Underlying() Type { return b }
func (a *Array) Underlying() Type { return a }
func (s *Slice) Underlying() Type { return s }
func (s *Struct) Underlying() Type { return s }
func (p *Pointer) Underlying() Type { return p }
func (t *Tuple) Underlying() Type { return t }
func (s *Signature) Underlying() Type { return s }
func (t *Interface) Underlying() Type { return t }
func (m *Map) Underlying() Type { return m }
func (c *Chan) Underlying() Type { return c }
func (t *Named) Underlying() Type { return t.underlying }
func (t *TypeParam) Underlying() Type { return t } // TODO(gri) should this return t.Interface() instead?
func (t *Basic) Basic() *Basic { return t }
func (t *Array) Array() *Array { return t }
func (t *Slice) Slice() *Slice { return t }
func (t *Struct) Struct() *Struct { return t }
func (t *Pointer) Pointer() *Pointer { return t }
func (t *Tuple) Tuple() *Tuple { return t }
func (t *Signature) Signature() *Signature { return t }
func (t *Interface) Interface() *Interface { return t }
func (t *Map) Map() *Map { return t }
func (t *Chan) Chan() *Chan { return t }
func (t *Named) Named() *Named { return t }
func (t *TypeParam) TypeParam() *TypeParam { return t }
func (b *Basic) String() string { return TypeString(b, nil) }
func (a *Array) String() string { return TypeString(a, nil) }
func (s *Slice) String() string { return TypeString(s, nil) }
func (s *Struct) String() string { return TypeString(s, nil) }
func (p *Pointer) String() string { return TypeString(p, nil) }
func (t *Basic) Underlying() Type { return t }
func (t *Array) Underlying() Type { return t }
func (t *Slice) Underlying() Type { return t }
func (t *Struct) Underlying() Type { return t }
func (t *Pointer) Underlying() Type { return t }
func (t *Tuple) Underlying() Type { return t }
func (t *Signature) Underlying() Type { return t }
func (t *Interface) Underlying() Type { return t }
func (t *Map) Underlying() Type { return t }
func (t *Chan) Underlying() Type { return t }
func (t *Named) Underlying() Type {
var u Type
var seen map[*Named]bool
// TODO(gri) If we have a chain of named types, update the
// underlying types once we have found the bottom
// (optimization).
for {
u = t.underlying
if u == nil {
return Typ[Invalid]
}
n, ok := t.underlying.(*Named)
if !ok {
return u // not a *Named type
}
if seen[t] {
return Typ[Invalid] // we have a cycle
}
if seen == nil {
seen = make(map[*Named]bool)
}
seen[t] = true
t = n
}
}
func (t *TypeParam) Underlying() Type { return t }
func (t *Basic) String() string { return TypeString(t, nil) }
func (t *Array) String() string { return TypeString(t, nil) }
func (t *Slice) String() string { return TypeString(t, nil) }
func (t *Struct) String() string { return TypeString(t, nil) }
func (t *Pointer) String() string { return TypeString(t, nil) }
func (t *Tuple) String() string { return TypeString(t, nil) }
func (s *Signature) String() string { return TypeString(s, nil) }
func (t *Signature) String() string { return TypeString(t, nil) }
func (t *Interface) String() string { return TypeString(t, nil) }
func (m *Map) String() string { return TypeString(m, nil) }
func (c *Chan) String() string { return TypeString(c, nil) }
func (t *Map) String() string { return TypeString(t, nil) }
func (t *Chan) String() string { return TypeString(t, nil) }
func (t *Named) String() string { return TypeString(t, nil) }
func (t *TypeParam) String() string { return TypeString(t, nil) }

View File

@ -304,7 +304,7 @@ func writeTParamList(buf *bytes.Buffer, list []*TypeName, qf Qualifier, visited
var writeBounds bool
for _, p := range list {
// bound(p) should be an interface but be careful (it may be invalid)
b, _ := bound(p).Underlying().(*Interface)
b := bound(p).Interface()
if b != nil && !b.Empty() {
writeBounds = true
break
@ -371,7 +371,7 @@ func writeTuple(buf *bytes.Buffer, tup *Tuple, variadic bool, qf Qualifier, visi
} else {
// special case:
// append(s, "foo"...) leads to signature func([]byte, string...)
if t, ok := typ.Underlying().(*Basic); !ok || t.kind != String {
if t := typ.Basic(); t == nil || t.kind != String {
panic("internal error: string type expected")
}
writeType(buf, typ, qf, visited)

View File

@ -34,39 +34,39 @@ var (
// Use Universe.Lookup("byte").Type() to obtain the specific
// alias basic type named "byte" (and analogous for "rune").
var Typ = []*Basic{
Invalid: {Invalid, 0, "invalid type"},
Invalid: {Invalid, 0, "invalid type", aType{}},
Bool: {Bool, IsBoolean, "bool"},
Int: {Int, IsInteger, "int"},
Int8: {Int8, IsInteger, "int8"},
Int16: {Int16, IsInteger, "int16"},
Int32: {Int32, IsInteger, "int32"},
Int64: {Int64, IsInteger, "int64"},
Uint: {Uint, IsInteger | IsUnsigned, "uint"},
Uint8: {Uint8, IsInteger | IsUnsigned, "uint8"},
Uint16: {Uint16, IsInteger | IsUnsigned, "uint16"},
Uint32: {Uint32, IsInteger | IsUnsigned, "uint32"},
Uint64: {Uint64, IsInteger | IsUnsigned, "uint64"},
Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr"},
Float32: {Float32, IsFloat, "float32"},
Float64: {Float64, IsFloat, "float64"},
Complex64: {Complex64, IsComplex, "complex64"},
Complex128: {Complex128, IsComplex, "complex128"},
String: {String, IsString, "string"},
UnsafePointer: {UnsafePointer, 0, "Pointer"},
Bool: {Bool, IsBoolean, "bool", aType{}},
Int: {Int, IsInteger, "int", aType{}},
Int8: {Int8, IsInteger, "int8", aType{}},
Int16: {Int16, IsInteger, "int16", aType{}},
Int32: {Int32, IsInteger, "int32", aType{}},
Int64: {Int64, IsInteger, "int64", aType{}},
Uint: {Uint, IsInteger | IsUnsigned, "uint", aType{}},
Uint8: {Uint8, IsInteger | IsUnsigned, "uint8", aType{}},
Uint16: {Uint16, IsInteger | IsUnsigned, "uint16", aType{}},
Uint32: {Uint32, IsInteger | IsUnsigned, "uint32", aType{}},
Uint64: {Uint64, IsInteger | IsUnsigned, "uint64", aType{}},
Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr", aType{}},
Float32: {Float32, IsFloat, "float32", aType{}},
Float64: {Float64, IsFloat, "float64", aType{}},
Complex64: {Complex64, IsComplex, "complex64", aType{}},
Complex128: {Complex128, IsComplex, "complex128", aType{}},
String: {String, IsString, "string", aType{}},
UnsafePointer: {UnsafePointer, 0, "Pointer", aType{}},
UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool"},
UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int"},
UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune"},
UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float"},
UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex"},
UntypedString: {UntypedString, IsString | IsUntyped, "untyped string"},
UntypedNil: {UntypedNil, IsUntyped, "untyped nil"},
UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool", aType{}},
UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int", aType{}},
UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune", aType{}},
UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float", aType{}},
UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex", aType{}},
UntypedString: {UntypedString, IsString | IsUntyped, "untyped string", aType{}},
UntypedNil: {UntypedNil, IsUntyped, "untyped nil", aType{}},
}
var aliases = [...]*Basic{
{Byte, IsInteger | IsUnsigned, "byte"},
{Rune, IsInteger, "rune"},
{Byte, IsInteger | IsUnsigned, "byte", aType{}},
{Rune, IsInteger, "rune", aType{}},
}
func defPredeclaredTypes() {
@ -214,7 +214,7 @@ func defPredeclaredContracts() {
// 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}
pname.typ = &TypeParam{0, pname, 0, &emptyInterface, aType{}}
// The type bound interface needs a name so we can attach the
// type parameter and to match the usual set up of contracts.