package types
import (
+ "fmt"
"go/ast"
"go/constant"
"go/token"
// objDecl type-checks the declaration of obj in its respective (file) context.
// For the meaning of def, see Checker.definedType, in typexpr.go.
func (check *Checker) objDecl(obj Object, def *Named) {
- if trace {
+ if trace && obj.Type() == nil {
+ if check.indent == 0 {
+ fmt.Println() // empty line between top-level objects for readability
+ }
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
check.indent++
defer func() {
switch obj := obj.(type) {
case *Const:
check.decl = d // new package-level const decl
- check.constDecl(obj, d.typ, d.init, d.inherited)
+ check.constDecl(obj, d.vtyp, d.init, d.inherited)
case *Var:
check.decl = d // new package-level var decl
- check.varDecl(obj, d.lhs, d.typ, d.init)
+ check.varDecl(obj, d.lhs, d.vtyp, d.init)
case *TypeName:
// invalid recursive types are detected via path
- check.typeDecl(obj, d.typ, def, d.alias)
+ check.typeDecl(obj, d.tdecl, def)
+ check.collectMethods(obj) // methods can only be added to top-level types
case *Func:
// functions may be recursive - no need to track dependencies
check.funcDecl(obj, d)
// this information explicitly in the object.
var alias bool
if d := check.objMap[obj]; d != nil {
- alias = d.alias // package-level object
+ alias = d.tdecl.Assign.IsValid() // package-level object
} else {
alias = obj.IsAlias() // function local object
}
}
// don't report a 2nd error if we already know the type is invalid
- // (e.g., if a cycle was detected earlier, via Checker.underlying).
+ // (e.g., if a cycle was detected earlier, via under).
if t.underlying == Typ[Invalid] {
t.info = invalid
return invalid
if tn == t.obj {
check.cycleError(path[i:])
t.info = invalid
- t.underlying = Typ[Invalid]
return t.info
}
}
panic("internal error: cycle start not found")
}
return t.info
+
+ case *instance:
+ return check.validType(t.expand(), path)
}
return valid
if !isConstType(t) {
// don't report an error if the type is an invalid C (defined) type
// (issue #22090)
- if t.Underlying() != Typ[Invalid] {
+ if under(t) != Typ[Invalid] {
check.errorf(typ, _InvalidConstType, "invalid constant type %s", t)
}
obj.typ = Typ[Invalid]
// determine type, if any
if typ != nil {
- obj.typ = check.typ(typ)
+ obj.typ = check.varType(typ)
// We cannot spread the type to all lhs variables if there
// are more than one since that would mark them as checked
// (see Checker.objDecl) and the assignment of init exprs,
check.initVars(lhs, []ast.Expr{init}, token.NoPos)
}
-// underlying returns the underlying type of typ; possibly by following
-// forward chains of named types. Such chains only exist while named types
-// are incomplete. If an underlying type is found, resolve the chain by
-// setting the underlying type for each defined type in the chain before
-// returning it.
-//
-// If no underlying type is found, a cycle error is reported and Typ[Invalid]
-// is used as underlying type for each defined type in the chain and returned
-// as result.
-func (check *Checker) underlying(typ Type) Type {
- // If typ is not a defined type, its underlying type is itself.
- n0, _ := typ.(*Named)
- if n0 == nil {
- return typ // nothing to do
+// under returns the expanded underlying type of n0; possibly by following
+// forward chains of named types. If an underlying type is found, resolve
+// the chain by setting the underlying type for each defined type in the
+// chain before returning it. If no underlying type is found or a cycle
+// is detected, the result is Typ[Invalid]. If a cycle is detected and
+// n0.check != nil, the cycle is reported.
+func (n0 *Named) under() Type {
+ u := n0.underlying
+ if u == nil {
+ return Typ[Invalid]
}
// If the underlying type of a defined type is not a defined
// type, then that is the desired underlying type.
- typ = n0.underlying
- n, _ := typ.(*Named)
+ n := asNamed(u)
if n == nil {
- return typ // common case
+ return u // common case
}
// Otherwise, follow the forward chain.
seen := map[*Named]int{n0: 0}
path := []Object{n0.obj}
for {
- typ = n.underlying
- n1, _ := typ.(*Named)
+ u = n.underlying
+ if u == nil {
+ u = Typ[Invalid]
+ break
+ }
+ n1 := asNamed(u)
if n1 == nil {
break // end of chain
}
if i, ok := seen[n]; ok {
// cycle
- check.cycleError(path[i:])
- typ = Typ[Invalid]
+ // TODO(rFindley) revert this to a method on Checker. Having a possibly
+ // nil Checker on Named and TypeParam is too subtle.
+ if n0.check != nil {
+ n0.check.cycleError(path[i:])
+ }
+ u = Typ[Invalid]
break
}
}
// We should never have to update the underlying type of an imported type;
// those underlying types should have been resolved during the import.
// Also, doing so would lead to a race condition (was issue #31749).
- if n.obj.pkg != check.pkg {
+ // Do this check always, not just in debug more (it's cheap).
+ if n0.check != nil && n.obj.pkg != n0.check.pkg {
panic("internal error: imported type with unresolved underlying type")
}
- n.underlying = typ
+ n.underlying = u
}
- return typ
+ return u
}
func (n *Named) setUnderlying(typ Type) {
}
}
-func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, alias bool) {
+func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) {
assert(obj.typ == nil)
check.later(func() {
check.validType(obj.typ, nil)
})
+ alias := tdecl.Assign.IsValid()
+ if alias && tdecl.TParams != nil {
+ // The parser will ensure this but we may still get an invalid AST.
+ // Complain and continue as regular type definition.
+ check.error(atPos(tdecl.Assign), 0, "generic type cannot be alias")
+ alias = false
+ }
+
if alias {
+ // type alias declaration
obj.typ = Typ[Invalid]
- obj.typ = check.typ(typ)
+ obj.typ = check.anyType(tdecl.Type)
} else {
+ // defined type declaration
- named := &Named{obj: obj}
+ named := &Named{check: check, obj: obj}
def.setUnderlying(named)
obj.typ = named // make sure recursive type declarations terminate
+ if tdecl.TParams != nil {
+ check.openScope(tdecl, "type parameters")
+ defer check.closeScope()
+ named.tparams = check.collectTypeParams(tdecl.TParams)
+ }
+
// determine underlying type of named
- named.orig = check.definedType(typ, named)
+ named.orig = check.definedType(tdecl.Type, named)
// The underlying type of named may be itself a named type that is
// incomplete:
// and which has as its underlying type the named type B.
// Determine the (final, unnamed) underlying type by resolving
// any forward chain.
- named.underlying = check.underlying(named)
+ // TODO(gri) Investigate if we can just use named.origin here
+ // and rely on lazy computation of the underlying type.
+ named.underlying = under(named)
+ }
+
+}
+
+func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeName) {
+ // Type parameter lists should not be empty. The parser will
+ // complain but we still may get an incorrect AST: ignore it.
+ if list.NumFields() == 0 {
+ return
+ }
+
+ // Declare type parameters up-front, with empty interface as type bound.
+ // The scope of type parameters starts at the beginning of the type parameter
+ // list (so we can have mutually recursive parameterized interfaces).
+ for _, f := range list.List {
+ tparams = check.declareTypeParams(tparams, f.Names)
+ }
+ setBoundAt := func(at int, bound Type) {
+ assert(IsInterface(bound))
+ tparams[at].typ.(*TypeParam).bound = bound
}
- check.addMethodDecls(obj)
+ index := 0
+ var bound Type
+ for _, f := range list.List {
+ if f.Type == nil {
+ goto next
+ }
+
+ // The predeclared identifier "any" is visible only as a constraint
+ // in a type parameter list. Look for it before general constraint
+ // resolution.
+ if tident, _ := f.Type.(*ast.Ident); tident != nil && tident.Name == "any" && check.lookup("any") == nil {
+ bound = universeAny
+ } else {
+ bound = check.typ(f.Type)
+ }
+
+ // type bound must be an interface
+ // TODO(gri) We should delay the interface check because
+ // we may not have a complete interface yet:
+ // type C(type T C) interface {}
+ // (issue #39724).
+ if _, ok := under(bound).(*Interface); ok {
+ // Otherwise, set the bound for each type parameter.
+ for i := range f.Names {
+ setBoundAt(index+i, bound)
+ }
+ } else if bound != Typ[Invalid] {
+ check.errorf(f.Type, _Todo, "%s is not an interface", bound)
+ }
+
+ next:
+ index += len(f.Names)
+ }
+
+ return
}
-func (check *Checker) addMethodDecls(obj *TypeName) {
+func (check *Checker) declareTypeParams(tparams []*TypeName, names []*ast.Ident) []*TypeName {
+ for _, name := range names {
+ tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
+ check.NewTypeParam(tpar, len(tparams), &emptyInterface) // assigns type to tpar as a side-effect
+ check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position
+ tparams = append(tparams, tpar)
+ }
+
+ if trace && len(names) > 0 {
+ check.trace(names[0].Pos(), "type params = %v", tparams[len(tparams)-len(names):])
+ }
+
+ return tparams
+}
+
+func (check *Checker) collectMethods(obj *TypeName) {
// get associated methods
// (Checker.collectObjects only collects methods with non-blank names;
// Checker.resolveBaseTypeName ensures that obj is not an alias name
return
}
delete(check.methods, obj)
- assert(!check.objMap[obj].alias) // don't use TypeName.IsAlias (requires fully set up object)
+ assert(!check.objMap[obj].tdecl.Assign.IsValid()) // don't use TypeName.IsAlias (requires fully set up object)
// use an objset to check for name conflicts
var mset objset
// spec: "If the base type is a struct type, the non-blank method
// and field names must be distinct."
- base, _ := obj.typ.(*Named) // shouldn't fail but be conservative
+ base := asNamed(obj.typ) // shouldn't fail but be conservative
if base != nil {
if t, _ := base.underlying.(*Struct); t != nil {
for _, fld := range t.fields {
sig := new(Signature)
obj.typ = sig // guard against cycles
+
+ // Avoid cycle error when referring to method while type-checking the signature.
+ // This avoids a nuisance in the best case (non-parameterized receiver type) and
+ // since the method is not a type, we get an error. If we have a parameterized
+ // receiver type, instantiating the receiver type leads to the instantiation of
+ // its methods, and we don't want a cycle error in that case.
+ // TODO(gri) review if this is correct and/or whether we still need this?
+ saved := obj.color_
+ obj.color_ = black
fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.Type)
- if sig.recv == nil {
- if obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
- check.errorf(fdecl, _InvalidInitDecl, "func init must have no arguments and no return values")
- } else if obj.name == "main" && check.pkg.name == "main" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
- check.errorf(fdecl, _InvalidMainDecl, "func main must have no arguments and no return values")
- }
- // ok to continue
- }
+ obj.color_ = saved
// function body must be type-checked after global declarations
// (functions implemented elsewhere have no body)
check.declare(check.scope, d.spec.Name, obj, scopePos)
// mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl)
obj.setColor(grey + color(check.push(obj)))
- check.typeDecl(obj, d.spec.Type, nil, d.spec.Assign.IsValid())
+ check.typeDecl(obj, d.spec, nil)
check.pop().setColor(black)
default:
check.invalidAST(d.node(), "unknown ast.Decl node %T", d.node())