"cmd/compile/internal/syntax"
"fmt"
"go/constant"
+ . "internal/types/errors"
)
func (err *error_) recordAltDecl(obj Object) {
if obj.Name() != "_" {
if alt := scope.Insert(obj); alt != nil {
var err error_
+ err.code = DuplicateDecl
err.errorf(obj, "%s redeclared in this block", obj.Name())
err.recordAltDecl(alt)
check.report(&err)
return s
}
-// objDecl type-checks the declaration of obj in its respective (file) context.
+// objDecl type-checks the declaration of obj in its respective (file) environment.
// For the meaning of def, see Checker.definedType, in typexpr.go.
-func (check *Checker) objDecl(obj Object, def *Named) {
+func (check *Checker) objDecl(obj Object, def *TypeName) {
if check.conf.Trace && obj.Type() == nil {
if check.indent == 0 {
fmt.Println() // empty line between top-level objects for readability
fallthrough
case grey:
- // We have a cycle.
+ // We have a (possibly invalid) cycle.
// In the existing code, this is marked by a non-nil type
// for the object except for constants and variables whose
// type may be non-nil (known), or nil if it depends on the
// order code.
switch obj := obj.(type) {
case *Const:
- if check.cycle(obj) || obj.typ == nil {
+ if !check.validCycle(obj) || obj.typ == nil {
obj.typ = Typ[Invalid]
}
case *Var:
- if check.cycle(obj) || obj.typ == nil {
+ if !check.validCycle(obj) || obj.typ == nil {
obj.typ = Typ[Invalid]
}
case *TypeName:
- if check.cycle(obj) {
+ if !check.validCycle(obj) {
// break cycle
// (without this, calling underlying()
// below may lead to an endless loop
}
case *Func:
- if check.cycle(obj) {
+ if !check.validCycle(obj) {
// Don't set obj.typ to Typ[Invalid] here
// because plenty of code type-asserts that
// functions have a *Signature type. Grey
unreachable()
}
- // save/restore current context and setup object context
- defer func(ctxt context) {
- check.context = ctxt
- }(check.context)
- check.context = context{
+ // save/restore current environment and set up object environment
+ defer func(env environment) {
+ check.environment = env
+ }(check.environment)
+ check.environment = environment{
scope: d.file,
}
}
}
-// cycle checks if the cycle starting with obj is valid and
+// validCycle reports whether the cycle starting with obj is valid and
// reports an error if it is not.
-func (check *Checker) cycle(obj Object) (isCycle bool) {
+func (check *Checker) validCycle(obj Object) (valid bool) {
// The object map contains the package scope objects and the non-interface methods.
if debug {
info := check.objMap[obj]
assert(obj.color() >= grey)
start := obj.color() - grey // index of obj in objPath
cycle := check.objPath[start:]
- nval := 0 // number of (constant or variable) values in the cycle
- ndef := 0 // number of type definitions in the cycle
+ tparCycle := false // if set, the cycle is through a type parameter list
+ nval := 0 // number of (constant or variable) values in the cycle; valid if !generic
+ ndef := 0 // number of type definitions in the cycle; valid if !generic
+loop:
for _, obj := range cycle {
switch obj := obj.(type) {
case *Const, *Var:
nval++
case *TypeName:
+ // If we reach a generic type that is part of a cycle
+ // and we are in a type parameter list, we have a cycle
+ // through a type parameter list, which is invalid.
+ if check.inTParamList && isGeneric(obj.typ) {
+ tparCycle = true
+ break loop
+ }
+
// Determine if the type name is an alias or not. For
// package-level objects, use the object map which
// provides syntactic information (which doesn't rely
// the syntactic information. We should consider storing
// this information explicitly in the object.
var alias bool
- if d := check.objMap[obj]; d != nil {
- alias = d.tdecl.Alias // package-level object
+ if check.enableAlias {
+ alias = obj.IsAlias()
} else {
- alias = obj.IsAlias() // function local object
+ if d := check.objMap[obj]; d != nil {
+ alias = d.tdecl.Alias // package-level object
+ } else {
+ alias = obj.IsAlias() // function local object
+ }
}
if !alias {
ndef++
if check.conf.Trace {
check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), len(cycle))
- check.trace(obj.Pos(), "## cycle contains: %d values, %d type definitions", nval, ndef)
+ if tparCycle {
+ check.trace(obj.Pos(), "## cycle contains: generic type in a type parameter list")
+ } else {
+ check.trace(obj.Pos(), "## cycle contains: %d values, %d type definitions", nval, ndef)
+ }
defer func() {
- if isCycle {
+ if valid {
+ check.trace(obj.Pos(), "=> cycle is valid")
+ } else {
check.trace(obj.Pos(), "=> error: cycle is invalid")
}
}()
}
- // A cycle involving only constants and variables is invalid but we
- // ignore them here because they are reported via the initialization
- // cycle check.
- if nval == len(cycle) {
- return false
- }
-
- // A cycle involving only types (and possibly functions) must have at least
- // one type definition to be permitted: If there is no type definition, we
- // have a sequence of alias type names which will expand ad infinitum.
- if nval == 0 && ndef > 0 {
- return false // cycle is permitted
- }
-
- check.cycleError(cycle)
-
- return true
-}
-
-type typeInfo uint
-
-// validType verifies that the given type does not "expand" infinitely
-// producing a cycle in the type graph. Cycles are detected by marking
-// defined types.
-// (Cycles involving alias types, as in "type A = [10]A" are detected
-// earlier, via the objDecl cycle detection mechanism.)
-func (check *Checker) validType(typ Type, path []Object) typeInfo {
- const (
- unknown typeInfo = iota
- marked
- valid
- invalid
- )
-
- switch t := typ.(type) {
- case *Array:
- return check.validType(t.elem, path)
-
- case *Struct:
- for _, f := range t.fields {
- if check.validType(f.typ, path) == invalid {
- return invalid
- }
- }
-
- case *Interface:
- for _, etyp := range t.embeddeds {
- if check.validType(etyp, path) == invalid {
- return invalid
- }
- }
-
- case *Named:
- t.resolve(check.conf.Environment)
-
- // don't touch the type if it is from a different package or the Universe scope
- // (doing so would lead to a race condition - was issue #35049)
- if t.obj.pkg != check.pkg {
- return valid
- }
-
- // don't report a 2nd error if we already know the type is invalid
- // (e.g., if a cycle was detected earlier, via under).
- if t.underlying == Typ[Invalid] {
- t.info = invalid
- return invalid
+ if !tparCycle {
+ // A cycle involving only constants and variables is invalid but we
+ // ignore them here because they are reported via the initialization
+ // cycle check.
+ if nval == len(cycle) {
+ return true
}
- switch t.info {
- case unknown:
- t.info = marked
- t.info = check.validType(t.fromRHS, append(path, t.obj)) // only types of current package added to path
- case marked:
- // cycle detected
- for i, tn := range path {
- if t.obj.pkg != check.pkg {
- panic("type cycle via package-external type")
- }
- if tn == t.obj {
- check.cycleError(path[i:])
- t.info = invalid
- return t.info
- }
- }
- panic("cycle start not found")
+ // A cycle involving only types (and possibly functions) must have at least
+ // one type definition to be permitted: If there is no type definition, we
+ // have a sequence of alias type names which will expand ad infinitum.
+ if nval == 0 && ndef > 0 {
+ return true
}
- return t.info
}
- return valid
+ check.cycleError(cycle)
+ return false
}
// cycleError reports a declaration cycle starting with
// the object in cycle that is "first" in the source.
func (check *Checker) cycleError(cycle []Object) {
+ // name returns the (possibly qualified) object name.
+ // This is needed because with generic types, cycles
+ // may refer to imported types. See go.dev/issue/50788.
+ // TODO(gri) This functionality is used elsewhere. Factor it out.
+ name := func(obj Object) string {
+ return packagePrefix(obj.Pkg(), check.qualifier) + obj.Name()
+ }
+
// TODO(gri) Should we start with the last (rather than the first) object in the cycle
// since that is the earliest point in the source where we start seeing the
// cycle? That would be more consistent with other error messages.
i := firstInSrc(cycle)
obj := cycle[i]
+ objName := name(obj)
+ // If obj is a type alias, mark it as valid (not broken) in order to avoid follow-on errors.
+ tname, _ := obj.(*TypeName)
+ if tname != nil && tname.IsAlias() {
+ // If we use Alias nodes, it is initialized with Typ[Invalid].
+ // TODO(gri) Adjust this code if we initialize with nil.
+ if !check.enableAlias {
+ check.validAlias(tname, Typ[Invalid])
+ }
+ }
+
+ // report a more concise error for self references
+ if len(cycle) == 1 {
+ if tname != nil {
+ check.errorf(obj, InvalidDeclCycle, "invalid recursive type: %s refers to itself", objName)
+ } else {
+ check.errorf(obj, InvalidDeclCycle, "invalid cycle in declaration: %s refers to itself", objName)
+ }
+ return
+ }
+
var err error_
- if check.conf.CompilerErrorMessages {
- err.errorf(obj, "invalid recursive type %s", obj.Name())
+ err.code = InvalidDeclCycle
+ if tname != nil {
+ err.errorf(obj, "invalid recursive type %s", objName)
} else {
- err.errorf(obj, "illegal cycle in declaration of %s", obj.Name())
+ err.errorf(obj, "invalid cycle in declaration of %s", objName)
}
for range cycle {
- err.errorf(obj, "%s refers to", obj.Name())
+ err.errorf(obj, "%s refers to", objName)
i++
if i >= len(cycle) {
i = 0
}
obj = cycle[i]
+ objName = name(obj)
}
- err.errorf(obj, "%s", obj.Name())
+ err.errorf(obj, "%s", objName)
check.report(&err)
}
func firstInSrc(path []Object) int {
fst, pos := 0, path[0].Pos()
for i, t := range path[1:] {
- if t.Pos().Cmp(pos) < 0 {
+ if cmpPos(t.Pos(), pos) < 0 {
fst, pos = i+1, t.Pos()
}
}
t := check.typ(typ)
if !isConstType(t) {
// don't report an error if the type is an invalid C (defined) type
- // (issue #22090)
- if under(t) != Typ[Invalid] {
- check.errorf(typ, "invalid constant type %s", t)
+ // (go.dev/issue/22090)
+ if isValid(under(t)) {
+ check.errorf(typ, InvalidConstType, "invalid constant type %s", t)
}
obj.typ = Typ[Invalid]
return
// expression and not the current constant declaration. Use
// the constant identifier position for any errors during
// init expression evaluation since that is all we have
- // (see issues #42991, #42992).
+ // (see issues go.dev/issue/42991, go.dev/issue/42992).
check.errpos = obj.pos
}
- check.expr(&x, init)
+ check.expr(nil, &x, init)
}
check.initConst(obj, &x)
}
func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init syntax.Expr) {
assert(obj.typ == nil)
- // If we have undefined variable types due to errors,
- // mark variables as used to avoid follow-on errors.
- // Matches compiler behavior.
- defer func() {
- if obj.typ == Typ[Invalid] {
- obj.used = true
- }
- for _, lhs := range lhs {
- if lhs.typ == Typ[Invalid] {
- lhs.used = true
- }
- }
- }()
-
// determine type, if any
if typ != nil {
obj.typ = check.varType(typ)
if lhs == nil || len(lhs) == 1 {
assert(lhs == nil || lhs[0] == obj)
var x operand
- check.expr(&x, init)
+ check.expr(obj.typ, &x, init)
check.initVar(obj, &x, "variable declaration")
return
}
// We have multiple variables on the lhs and one init expr.
// Make sure all variables have been given the same type if
// one was specified, otherwise they assume the type of the
- // init expression values (was issue #15755).
+ // init expression values (was go.dev/issue/15755).
if typ != nil {
for _, lhs := range lhs {
lhs.typ = obj.typ
}
}
- check.initVars(lhs, []syntax.Expr{init}, nopos)
+ check.initVars(lhs, []syntax.Expr{init}, nil)
}
// isImportedConstraint reports whether typ is an imported type constraint.
func (check *Checker) isImportedConstraint(typ Type) bool {
- named, _ := typ.(*Named)
+ named := asNamed(typ)
if named == nil || named.obj.pkg == check.pkg || named.obj.pkg == nil {
return false
}
u, _ := named.under().(*Interface)
- return u != nil && u.IsConstraint()
+ return u != nil && !u.IsMethodSet()
}
-func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named) {
+func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeName) {
assert(obj.typ == nil)
var rhs Type
check.later(func() {
- check.validType(obj.typ, nil)
- // If typ is local, an error was already reported where typ is specified/defined.
- if check.isImportedConstraint(rhs) && !check.allowVersion(check.pkg, 1, 18) {
- check.errorf(tdecl.Type.Pos(), "using type constraint %s requires go1.18 or later", rhs)
+ if t := asNamed(obj.typ); t != nil { // type may be invalid
+ check.validType(t)
}
- })
+ // If typ is local, an error was already reported where typ is specified/defined.
+ _ = check.isImportedConstraint(rhs) && check.verifyVersionf(tdecl.Type, go1_18, "using type constraint %s", rhs)
+ }).describef(obj, "validType(%s)", obj.Name())
- alias := tdecl.Alias
- if alias && tdecl.TParamList != nil {
+ aliasDecl := tdecl.Alias
+ if aliasDecl && tdecl.TParamList != nil {
// The parser will ensure this but we may still get an invalid AST.
// Complain and continue as regular type definition.
- check.error(tdecl, "generic type cannot be alias")
- alias = false
+ check.error(tdecl, BadDecl, "generic type cannot be alias")
+ aliasDecl = false
}
// alias declaration
- if alias {
- if !check.allowVersion(check.pkg, 1, 9) {
- if check.conf.CompilerErrorMessages {
- check.error(tdecl, "type aliases only supported as of -lang=go1.9")
- } else {
- check.error(tdecl, "type aliases requires go1.9 or later")
- }
+ if aliasDecl {
+ check.verifyVersionf(tdecl, go1_9, "type aliases")
+ if check.enableAlias {
+ // TODO(gri) Should be able to use nil instead of Typ[Invalid] to mark
+ // the alias as incomplete. Currently this causes problems
+ // with certain cycles. Investigate.
+ alias := check.newAlias(obj, Typ[Invalid])
+ setDefType(def, alias)
+ rhs = check.definedType(tdecl.Type, obj)
+ assert(rhs != nil)
+ alias.fromRHS = rhs
+ Unalias(alias) // resolve alias.actual
+ } else {
+ check.brokenAlias(obj)
+ rhs = check.typ(tdecl.Type)
+ check.validAlias(obj, rhs)
}
-
- obj.typ = Typ[Invalid]
- rhs = check.varType(tdecl.Type)
- obj.typ = rhs
return
}
// type definition or generic type declaration
- named := check.newNamed(obj, nil, nil, nil, nil)
- def.setUnderlying(named)
+ named := check.newNamed(obj, nil, nil)
+ setDefType(def, named)
if tdecl.TParamList != nil {
check.openScope(tdecl, "type parameters")
}
// determine underlying type of named
- rhs = check.definedType(tdecl.Type, named)
+ rhs = check.definedType(tdecl.Type, obj)
assert(rhs != nil)
named.fromRHS = rhs
- // The underlying type of named may be itself a named type that is
- // incomplete:
- //
- // type (
- // A B
- // B *C
- // C A
- // )
- //
- // The type of C is the (named) type of A which is incomplete,
- // and which has as its underlying type the named type B.
- // Determine the (final, unnamed) underlying type by resolving
- // any forward chain.
- // TODO(gri) Investigate if we can just use named.fromRHS here
- // and rely on lazy computation of the underlying type.
- named.underlying = under(named)
-
- // If the RHS is a type parameter, it must be from this type declaration.
- if tpar, _ := named.underlying.(*TypeParam); tpar != nil && tparamIndex(named.TypeParams().list(), tpar) < 0 {
- check.errorf(tdecl.Type, "cannot use function type parameter %s as RHS in type declaration", tpar)
+
+ // If the underlying type was not set while type-checking the right-hand
+ // side, it is invalid and an error should have been reported elsewhere.
+ if named.underlying == nil {
+ named.underlying = Typ[Invalid]
+ }
+
+ // Disallow a lone type parameter as the RHS of a type declaration (go.dev/issue/45639).
+ // We don't need this restriction anymore if we make the underlying type of a type
+ // parameter its constraint interface: if the RHS is a lone type parameter, we will
+ // use its underlying type (like we do for any RHS in a type declaration), and its
+ // underlying type is an interface and the type declaration is well defined.
+ if isTypeParam(rhs) {
+ check.error(tdecl.Type, MisplacedTypeParam, "cannot use a type parameter as RHS in type declaration")
named.underlying = Typ[Invalid]
}
}
}
// Set the type parameters before collecting the type constraints because
- // the parameterized type may be used by the constraints (issue #47887).
+ // the parameterized type may be used by the constraints (go.dev/issue/47887).
// Example: type T[P T[P]] interface{}
*dst = bindTParams(tparams)
+ // Signal to cycle detection that we are in a type parameter list.
+ // We can only be inside one type parameter list at any given time:
+ // function closures may appear inside a type parameter list but they
+ // cannot be generic, and their bodies are processed in delayed and
+ // sequential fashion. Note that with each new declaration, we save
+ // the existing environment and restore it when done; thus inTParamList
+ // is true exactly only when we are in a specific type parameter list.
+ assert(!check.inTParamList)
+ check.inTParamList = true
+ defer func() {
+ check.inTParamList = false
+ }()
+
+ // Keep track of bounds for later validation.
var bound Type
for i, f := range list {
// Optimization: Re-use the previous type bound if it hasn't changed.
// This also preserves the grouped output of type parameter lists
// when printing type strings.
if i == 0 || f.Type != list[i-1].Type {
- // The predeclared identifier "any" is visible only as a type bound in a type parameter list.
- // If we allow "any" for general use, this if-statement can be removed (issue #33232).
- if name, _ := unparen(f.Type).(*syntax.Name); name != nil && name.Value == "any" && check.lookup("any") == universeAny {
- bound = universeAny.Type()
- } else {
- bound = check.typ(f.Type)
+ bound = check.bound(f.Type)
+ if isTypeParam(bound) {
+ // We may be able to allow this since it is now well-defined what
+ // the underlying type and thus type set of a type parameter is.
+ // But we may need some additional form of cycle detection within
+ // type parameter lists.
+ check.error(f.Type, MisplacedTypeParam, "cannot use a type parameter as constraint")
+ bound = Typ[Invalid]
}
}
tparams[i].bound = bound
}
+}
- check.later(func() {
- for i, tpar := range tparams {
- u := under(tpar.bound)
- if _, ok := u.(*Interface); !ok && u != Typ[Invalid] {
- check.errorf(list[i].Type, "%s is not an interface", tpar.bound)
- }
+func (check *Checker) bound(x syntax.Expr) Type {
+ // A type set literal of the form ~T and A|B may only appear as constraint;
+ // embed it in an implicit interface so that only interface type-checking
+ // needs to take care of such type expressions.
+ if op, _ := x.(*syntax.Operation); op != nil && (op.Op == syntax.Tilde || op.Op == syntax.Or) {
+ t := check.typ(&syntax.InterfaceType{MethodList: []*syntax.Field{{Type: x}}})
+ // mark t as implicit interface if all went well
+ if t, _ := t.(*Interface); t != nil {
+ t.implicit = true
}
- })
+ return t
+ }
+ return check.typ(x)
}
func (check *Checker) declareTypeParam(name *syntax.Name) *TypeParam {
// and field names must be distinct."
base := asNamed(obj.typ) // shouldn't fail but be conservative
if base != nil {
- u := safeUnderlying(base) // base should be expanded, but use safeUnderlying to be conservative
- if t, _ := u.(*Struct); t != nil {
- for _, fld := range t.fields {
- if fld.name != "_" {
- assert(mset.insert(fld) == nil)
- }
- }
- }
+ assert(base.TypeArgs().Len() == 0) // collectMethods should not be called on an instantiated type
+
+ // See go.dev/issue/52529: we must delay the expansion of underlying here, as
+ // base may not be fully set-up.
+ check.later(func() {
+ check.checkFieldUniqueness(base)
+ }).describef(obj, "verifying field uniqueness for %v", base)
// Checker.Files may be called multiple times; additional package files
// may add methods to already type-checked types. Add pre-existing methods
// so that we can detect redeclarations.
- for _, m := range base.methods {
+ for i := 0; i < base.NumMethods(); i++ {
+ m := base.Method(i)
assert(m.name != "_")
assert(mset.insert(m) == nil)
}
// to it must be unique."
assert(m.name != "_")
if alt := mset.insert(m); alt != nil {
- var err error_
- switch alt.(type) {
- case *Var:
- err.errorf(m.pos, "field and method with the same name %s", m.name)
- case *Func:
- if check.conf.CompilerErrorMessages {
- err.errorf(m.pos, "%s.%s redeclared in this block", obj.Name(), m.name)
- } else {
- err.errorf(m.pos, "method %s already declared for %s", m.name, obj)
- }
- default:
- unreachable()
+ if alt.Pos().IsKnown() {
+ check.errorf(m.pos, DuplicateMethod, "method %s.%s already declared at %s", obj.Name(), m.name, alt.Pos())
+ } else {
+ check.errorf(m.pos, DuplicateMethod, "method %s.%s already declared", obj.Name(), m.name)
}
- err.recordAltDecl(alt)
- check.report(&err)
continue
}
if base != nil {
- base.resolve(nil) // TODO(mdempsky): Probably unnecessary.
- base.methods = append(base.methods, m)
+ base.AddMethod(m)
+ }
+ }
+}
+
+func (check *Checker) checkFieldUniqueness(base *Named) {
+ if t, _ := base.under().(*Struct); t != nil {
+ var mset objset
+ for i := 0; i < base.NumMethods(); i++ {
+ m := base.Method(i)
+ assert(m.name != "_")
+ assert(mset.insert(m) == nil)
+ }
+
+ // Check that any non-blank field names of base are distinct from its
+ // method names.
+ for _, fld := range t.fields {
+ if fld.name != "_" {
+ if alt := mset.insert(fld); alt != nil {
+ // Struct fields should already be unique, so we should only
+ // encounter an alternate via collision with a method name.
+ _ = alt.(*Func)
+
+ // For historical consistency, we report the primary error on the
+ // method, and the alt decl on the field.
+ var err error_
+ err.code = DuplicateFieldAndMethod
+ err.errorf(alt, "field and method with the same name %s", fld.name)
+ err.recordAltDecl(fld)
+ check.report(&err)
+ }
+ }
}
}
}
obj.color_ = saved
if len(fdecl.TParamList) > 0 && fdecl.Body == nil {
- check.softErrorf(fdecl, "parameterized function is missing function body")
+ check.softErrorf(fdecl, BadDecl, "generic function is missing function body")
}
// function body must be type-checked after global declarations
if !check.conf.IgnoreFuncBodies && fdecl.Body != nil {
check.later(func() {
check.funcBody(decl, obj.name, sig, fdecl.Body, nil)
- })
+ }).describef(obj, "func %s", obj.name)
}
}
top := len(check.delayed)
// iota is the index of the current constDecl within the group
- if first < 0 || list[index-1].(*syntax.ConstDecl).Group != s.Group {
+ if first < 0 || s.Group == nil || list[index-1].(*syntax.ConstDecl).Group != s.Group {
first = index
last = nil
}
// declare all constants
lhs := make([]*Const, len(s.NameList))
- values := unpackExpr(last.Values)
+ values := syntax.UnpackListExpr(last.Values)
for i, name := range s.NameList {
obj := NewConst(name.Pos(), pkg, name.Value, nil, iota)
lhs[i] = obj
}
// initialize all variables
- values := unpackExpr(s.Values)
+ values := syntax.UnpackListExpr(s.Values)
for i, obj := range lhs0 {
var lhs []*Var
var init syntax.Expr
check.pop().setColor(black)
default:
- check.errorf(s, invalidAST+"unknown syntax.Decl node %T", s)
+ check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s)
}
}
}