// assocMethod associates a method declaration with the respective // receiver base type. meth.Recv must exist. // func (check *checker) assocMethod(meth *ast.FuncDecl) { // The receiver type is one of the following (enforced by parser): // - *ast.Ident // - *ast.StarExpr{*ast.Ident} // - *ast.BadExpr (parser error) typ := meth.Recv.List[0].Type if ptr, ok := typ.(*ast.StarExpr); ok { typ = ptr.X } // determine receiver base type object (or nil if error) var obj *ast.Object if ident, ok := typ.(*ast.Ident); ok && ident.Obj != nil { obj = ident.Obj if obj.Kind != ast.Typ { check.errorf(ident.Pos(), "%s is not a type", ident.Name) obj = nil } // TODO(gri) determine if obj was defined in this package /* if check.notLocal(obj) { check.errorf(ident.Pos(), "cannot define methods on non-local type %s", ident.Name) obj = nil } */ } else { // If it's not an identifier or the identifier wasn't declared/resolved, // the parser/resolver already reported an error. Nothing to do here. } // determine base type scope (or nil if error) var scope *ast.Scope if obj != nil { if obj.Data != nil { scope = obj.Data.(*ast.Scope) } else { scope = ast.NewScope(nil) obj.Data = scope } } else { // use a dummy scope so that meth can be declared in // presence of an error and get an associated object // (always use a new scope so that we don't get double // declaration errors) scope = ast.NewScope(nil) } check.declare(scope, ast.Fun, meth.Name, meth) }
// assocMethod associates a method declaration with the respective // receiver base type. meth.Recv must exist. // func (check *checker) assocMethod(meth *ast.FuncDecl) { // The receiver type is one of the following (enforced by parser): // - *ast.Ident // - *ast.StarExpr{*ast.Ident} // - *ast.BadExpr (parser error) typ := meth.Recv.List[0].Type if ptr, ok := typ.(*ast.StarExpr); ok { typ = ptr.X } // determine receiver base type object var obj *ast.Object if ident, ok := typ.(*ast.Ident); ok && ident.Obj != nil { obj = ident.Obj if obj.Kind != ast.Typ { check.errorf(ident.Pos(), "%s is not a type", ident.Name) return // ignore this method } // TODO(gri) determine if obj was defined in this package /* if check.notLocal(obj) { check.errorf(ident.Pos(), "cannot define methods on non-local type %s", ident.Name) return // ignore this method } */ } else { // If it's not an identifier or the identifier wasn't declared/resolved, // the parser/resolver already reported an error. Nothing to do here. return // ignore this method } // declare method in receiver base type scope var scope *ast.Scope if obj.Data != nil { scope = obj.Data.(*ast.Scope) } else { scope = ast.NewScope(nil) obj.Data = scope } check.declare(scope, ast.Fun, meth.Name, meth) }
// object typechecks an object by assigning it a type; obj.Type must be nil. // Callers must check obj.Type before calling object; this eliminates a call // for each identifier that has been typechecked already, a common scenario. // func (check *checker) object(obj *ast.Object, cycleOk bool) { assert(obj.Type == nil) switch obj.Kind { case ast.Bad, ast.Pkg: // nothing to do case ast.Con, ast.Var: // The obj.Data field for constants and variables is initialized // to the respective (hypothetical, for variables) iota value by // the parser. The object's fields can be in one of the following // states: // Type != nil => the constant value is Data // Type == nil => the object is not typechecked yet, and Data can be: // Data is int => Data is the value of iota for this declaration // Data == nil => the object's expression is being evaluated if obj.Data == nil { check.errorf(obj.Pos(), "illegal cycle in initialization of %s", obj.Name) obj.Type = Typ[Invalid] return } spec := obj.Decl.(*ast.ValueSpec) iota := obj.Data.(int) obj.Data = nil // determine initialization expressions values := spec.Values if len(values) == 0 && obj.Kind == ast.Con { values = check.initexprs[spec] } check.valueSpec(spec.Pos(), obj, spec.Names, spec.Type, values, iota) case ast.Typ: typ := &NamedType{Obj: obj} obj.Type = typ // "mark" object so recursion terminates typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk)) // typecheck associated method signatures if obj.Data != nil { scope := obj.Data.(*ast.Scope) switch t := typ.Underlying.(type) { case *Struct: // struct fields must not conflict with methods for _, f := range t.Fields { if m := scope.Lookup(f.Name); m != nil { check.errorf(m.Pos(), "type %s has both field and method named %s", obj.Name, f.Name) // ok to continue } } case *Interface: // methods cannot be associated with an interface type for _, m := range scope.Objects { recv := m.Decl.(*ast.FuncDecl).Recv.List[0].Type check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.Name, obj.Name) // ok to continue } } // typecheck method signatures for _, obj := range scope.Objects { mdecl := obj.Decl.(*ast.FuncDecl) sig := check.typ(mdecl.Type, cycleOk).(*Signature) params, _ := check.collectParams(mdecl.Recv, false) sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter obj.Type = sig check.later(obj, sig, mdecl.Body) } } case ast.Fun: fdecl := obj.Decl.(*ast.FuncDecl) // methods are typechecked when their receivers are typechecked if fdecl.Recv == nil { sig := check.typ(fdecl.Type, cycleOk).(*Signature) if obj.Name == "init" && (len(sig.Params) != 0 || len(sig.Results) != 0) { check.errorf(fdecl.Pos(), "func init must have no arguments and no return values") // ok to continue } obj.Type = sig check.later(obj, sig, fdecl.Body) } default: panic("unreachable") } }
func (c *compiler) VisitValueSpec(valspec *ast.ValueSpec, isconst bool) { var value_type types.Type if valspec.Type != nil { value_type = c.GetType(valspec.Type) } var iota_obj *ast.Object = types.Universe.Lookup("iota") defer func(data interface{}) { iota_obj.Data = data }(iota_obj.Data) for i, name_ := range valspec.Names { // We may resolve constants in the process of resolving others. obj := name_.Obj if _, isvalue := (obj.Data).(Value); isvalue { continue } // Set iota if necessary. if isconst { if iota_, isint := (name_.Obj.Data).(int); isint { iota_value := c.NewConstValue(token.INT, strconv.Itoa(iota_)) iota_obj.Data = iota_value // Con objects with an iota have an embedded ValueSpec // in the Decl field. We'll just pull it out and use it // for evaluating the expression below. valspec = (name_.Obj.Decl).(*ast.ValueSpec) } } // Expression may have side-effects, so compute it regardless of // whether it'll be assigned to a name. var expr ast.Expr if i < len(valspec.Values) && valspec.Values[i] != nil { expr = valspec.Values[i] } // For constants, we just pass the ConstValue around. Otherwise, we // will convert it to an LLVMValue. var value Value name := name_.String() if !isconst { ispackagelevel := len(c.functions) == 0 if !ispackagelevel { // Visit the expression. var init_ Value if expr != nil { init_ = c.VisitExpr(expr) if value_type == nil { value_type = init_.Type() } } // The variable should be allocated on the stack if it's // declared inside a function. var llvm_init llvm.Value stack_value := c.builder.CreateAlloca( c.types.ToLLVM(value_type), name) if init_ == nil { // If no initialiser was specified, set it to the // zero value. llvm_init = llvm.ConstNull(c.types.ToLLVM(value_type)) } else { llvm_init = init_.Convert(value_type).LLVMValue() } c.builder.CreateStore(llvm_init, stack_value) llvm_value := c.NewLLVMValue(stack_value, &types.Pointer{Base: value_type}) value = llvm_value.makePointee() } else { // ispackagelevel // Set the initialiser. If it's a non-const value, then // we'll have to do the assignment in a global constructor // function. export := name_.IsExported() value = c.createGlobal(expr, value_type, name, export) } } else { // isconst value = c.VisitExpr(expr).(ConstValue) if value_type != nil { value = value.Convert(value_type) } } if name != "_" { obj.Data = value } } }
// object typechecks an object by assigning it a type; obj.Type must be nil. // Callers must check obj.Type before calling object; this eliminates a call // for each identifier that has been typechecked already, a common scenario. // func (check *checker) object(obj *ast.Object, cycleOk bool) { assert(obj.Type == nil) switch obj.Kind { case ast.Bad, ast.Pkg: // nothing to do case ast.Con, ast.Var: // The obj.Data field for constants and variables is initialized // to the respective (hypothetical, for variables) iota value by // the parser. The object's fields can be in one of the following // states: // Type != nil => the constant value is Data // Type == nil => the object is not typechecked yet, and Data can be: // Data is int => Data is the value of iota for this declaration // Data == nil => the object's expression is being evaluated if obj.Data == nil { check.errorf(obj.Pos(), "illegal cycle in initialization of %s", obj.Name) obj.Type = Typ[Invalid] return } spec := obj.Decl.(*ast.ValueSpec) iota := obj.Data.(int) obj.Data = nil // determine initialization expressions values := spec.Values if len(values) == 0 && obj.Kind == ast.Con { values = check.initexprs[spec] } check.valueSpec(spec.Pos(), obj, spec.Names, spec.Type, values, iota) case ast.Typ: typ := &NamedType{Obj: obj} obj.Type = typ // "mark" object so recursion terminates typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk)) // typecheck associated method signatures if obj.Data != nil { scope := obj.Data.(*ast.Scope) switch t := typ.Underlying.(type) { case *Struct: // struct fields must not conflict with methods for _, f := range t.Fields { if m := scope.Lookup(f.Name); m != nil { check.errorf(m.Pos(), "type %s has both field and method named %s", obj.Name, f.Name) } } // ok to continue case *Interface: // methods cannot be associated with an interface type for _, m := range scope.Objects { recv := m.Decl.(*ast.FuncDecl).Recv.List[0].Type check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.Name, obj.Name) } // ok to continue } // typecheck method signatures for _, m := range scope.Objects { mdecl := m.Decl.(*ast.FuncDecl) // TODO(gri) At the moment, the receiver is type-checked when checking // the method body. Also, we don't properly track if the receiver is // a pointer (i.e., currently, method sets are too large). FIX THIS. mtyp := check.typ(mdecl.Type, cycleOk).(*Signature) m.Type = mtyp } } case ast.Fun: fdecl := obj.Decl.(*ast.FuncDecl) if fdecl.Recv != nil { // This will ensure that the method base type is // type-checked check.collectFields(token.FUNC, fdecl.Recv, true) } ftyp := check.typ(fdecl.Type, cycleOk).(*Signature) obj.Type = ftyp check.function(ftyp, fdecl.Body) default: panic("unreachable") } }
func (c *compiler) Resolve(obj *ast.Object) Value { if obj.Kind == ast.Pkg { return nil } else if obj.Kind == ast.Typ { return TypeValue{obj.Type.(types.Type)} } value, isvalue := (obj.Data).(Value) if isvalue { return value } switch obj.Kind { case ast.Con: if obj.Decl != nil { valspec := obj.Decl.(*ast.ValueSpec) c.VisitValueSpec(valspec, true) value = (obj.Data).(Value) } else if obj == types.Nil { return NilValue{c} } else { var typ types.Type switch x := obj.Type.(type) { case *types.Basic: typ = x case *types.Name: typ = x.Underlying.(*types.Basic) default: panic(fmt.Sprintf("unreachable (%T)", x)) } value = ConstValue{(obj.Data.(types.Const)), c, typ} obj.Data = value } case ast.Fun: var funcdecl *ast.FuncDecl if obj.Decl != nil { funcdecl = obj.Decl.(*ast.FuncDecl) } else { funcdecl = &ast.FuncDecl{ Name: &ast.Ident{Name: obj.Name, Obj: obj}, } } value = c.VisitFuncProtoDecl(funcdecl) obj.Data = value case ast.Var: switch x := (obj.Decl).(type) { case *ast.ValueSpec: c.VisitValueSpec(x, false) case *ast.Field: // No-op. Fields will be yielded for function // arg/recv/ret. We update the .Data field of the // object when we enter the function definition. if obj.Data == nil { panic("expected obj.Data value") } } // If it's an external variable, we'll need to create a global // value reference here. if obj.Data == nil { module := c.module.Module t := obj.Type.(types.Type) name := c.pkgmap[obj] + "." + obj.Name g := llvm.AddGlobal(module, c.types.ToLLVM(t), name) g.SetLinkage(llvm.AvailableExternallyLinkage) obj.Data = c.NewLLVMValue(g, t) } value = (obj.Data).(Value) } return value }
func (c *compiler) Resolve(obj *ast.Object) Value { if obj.Kind == ast.Pkg { return nil } else if obj.Kind == ast.Typ { return TypeValue{obj.Type.(types.Type)} } value, isvalue := (obj.Data).(Value) if isvalue { return value } switch obj.Kind { case ast.Con: if obj.Decl != nil { valspec := obj.Decl.(*ast.ValueSpec) c.VisitValueSpec(valspec, true) value = (obj.Data).(Value) } else if obj == types.Nil { return NilValue{c} } else { typ := obj.Type.(types.Type) value = ConstValue{(obj.Data.(types.Const)), c, typ} obj.Data = value } case ast.Fun: var funcdecl *ast.FuncDecl if obj.Decl != nil { funcdecl = obj.Decl.(*ast.FuncDecl) } else { funcdecl = &ast.FuncDecl{ Name: &ast.Ident{Name: obj.Name, Obj: obj}, } } value = c.VisitFuncProtoDecl(funcdecl) obj.Data = value case ast.Var: switch x := (obj.Decl).(type) { case *ast.ValueSpec: c.VisitValueSpec(x, false) case *ast.Field: // No-op. Fields will be yielded for function // arg/recv/ret. We update the .Data field of the // object when we enter the function definition. if obj.Data == nil { panic("expected obj.Data value") } } // If it's an external variable, we'll need to create a global // value reference here. It may be possible for multiple objects // to refer to the same variable. if obj.Data == nil { module := c.module.Module t := obj.Type.(types.Type) name := c.pkgmap[obj] + "." + obj.Name g := module.NamedGlobal(name) if g.IsNil() { g = llvm.AddGlobal(module, c.types.ToLLVM(t), name) } obj.Data = c.NewLLVMValue(g, &types.Pointer{Base: t}).makePointee() } value = (obj.Data).(Value) } return value }
func check(fset *token.FileSet, pkg *ast.Package, types map[ast.Expr]Type) error { var check checker check.fset = fset check.pkg = pkg check.types = types // Compute sorted list of file names so that // package file iterations are reproducible (needed for testing). filenames := make([]string, len(pkg.Files)) { i := 0 for filename := range pkg.Files { filenames[i] = filename i++ } sort.Strings(filenames) } // Associate methods with types // TODO(gri) All other objects are resolved by the parser. // Consider doing this in the parser (and provide the info // in the AST. In the long-term (might require Go 1 API // changes) it's probably easier to do all the resolution // in one place in the type checker. See also comment // with checker.declare. for _, filename := range filenames { file := pkg.Files[filename] for _, decl := range file.Decls { if meth, ok := decl.(*ast.FuncDecl); ok && meth.Recv != nil { // The receiver type is one of the following (enforced by parser): // - *ast.Ident // - *ast.StarExpr{*ast.Ident} // - *ast.BadExpr (parser error) typ := meth.Recv.List[0].Type if ptr, ok := typ.(*ast.StarExpr); ok { typ = ptr.X } // determine receiver base type object (or nil if error) var obj *ast.Object if ident, ok := typ.(*ast.Ident); ok && ident.Obj != nil { obj = ident.Obj if obj.Kind != ast.Typ { check.errorf(ident.Pos(), "%s is not a type", ident.Name) obj = nil } // TODO(gri) determine if obj was defined in this package /* if check.notLocal(obj) { check.errorf(ident.Pos(), "cannot define methods on non-local type %s", ident.Name) obj = nil } */ } else { // If it's not an identifier or the identifier wasn't declared/resolved, // the parser/resolver already reported an error. Nothing to do here. } // determine base type scope (or nil if error) var scope *ast.Scope if obj != nil { if obj.Data != nil { scope = obj.Data.(*ast.Scope) } else { scope = ast.NewScope(nil) obj.Data = scope } } else { // use a dummy scope so that meth can be declared in // presence of an error and get an associated object // (always use a new scope so that we don't get double // declaration errors) scope = ast.NewScope(nil) } check.declare(scope, ast.Fun, meth.Name, meth) } } } // Sort objects so that we get reproducible error // positions (this is only needed for testing). // TODO(gri): Consider ast.Scope implementation that // provides both a list and a map for fast lookup. // Would permit the use of scopes instead of ObjMaps // elsewhere. list := make(ObjList, len(pkg.Scope.Objects)) { i := 0 for _, obj := range pkg.Scope.Objects { list[i] = obj i++ } list.Sort() } // Check global objects. for _, obj := range list { check.obj(obj, false) } // TODO(gri) Missing pieces: // - blank (_) objects and init functions are not in scopes but should be type-checked // do not remove multiple errors per line - depending on // order or error reporting this may hide the real error return check.errors.Err() }
// obj type checks an object. func (check *checker) obj(obj *ast.Object, cycleOk bool) { if trace { fmt.Printf("obj(%s)\n", obj.Name) } if obj.Type != nil { // object has already been type checked return } switch obj.Kind { case ast.Bad, ast.Pkg: // nothing to do case ast.Con: if obj.Data == nil { check.errorf(obj.Pos(), "illegal cycle in initialization of %s", obj.Name) return } spec, ok := obj.Decl.(*ast.ValueSpec) assert(ok) // The Data stored with the constant is the value of iota for that // ast.ValueSpec. Use it for the evaluation of the initialization // expressions. iota := obj.Data.(int) obj.Data = nil check.decl(spec.Pos(), obj, spec.Names, spec.Type, check.specValues(spec), iota) case ast.Var: // TODO(gri) missing cycle detection spec, ok := obj.Decl.(*ast.ValueSpec) if !ok { // TODO(gri) the assertion fails for "x, y := 1, 2, 3" it seems fmt.Printf("var = %s\n", obj.Name) } assert(ok) check.decl(spec.Pos(), obj, spec.Names, spec.Type, spec.Values, 0) case ast.Typ: typ := &NamedType{Obj: obj} obj.Type = typ // "mark" object so recursion terminates typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk)) // collect associated methods, if any if obj.Data != nil { scope := obj.Data.(*ast.Scope) // struct fields must not conflict with methods if t, ok := typ.Underlying.(*Struct); ok { for _, f := range t.Fields { if m := scope.Lookup(f.Name); m != nil { check.errorf(m.Pos(), "type %s has both field and method named %s", obj.Name, f.Name) } } } // collect methods methods := make(ObjList, len(scope.Objects)) i := 0 for _, m := range scope.Objects { methods[i] = m i++ } methods.Sort() typ.Methods = methods // methods cannot be associated with an interface type // (do this check after sorting for reproducible error positions - needed for testing) if _, ok := typ.Underlying.(*Interface); ok { for _, m := range methods { recv := m.Decl.(*ast.FuncDecl).Recv.List[0].Type check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.Name, obj.Name) } } } case ast.Fun: fdecl := obj.Decl.(*ast.FuncDecl) ftyp := check.typ(fdecl.Type, cycleOk).(*Signature) obj.Type = ftyp if fdecl.Recv != nil { // TODO(gri) handle method receiver } check.stmt(fdecl.Body) default: panic("unreachable") } }
func (c *compiler) VisitValueSpec(valspec *ast.ValueSpec, isconst bool) { // Check if the value-spec has already been visited (referenced // before definition visited.) if len(valspec.Names) > 0 { if _, ok := valspec.Names[0].Obj.Data.(Value); ok { return } } var iotaObj *ast.Object = types.Universe.Lookup("iota") defer func(data interface{}) { iotaObj.Data = data }(iotaObj.Data) pkgname, ispackagelevel := c.pkgmap[valspec.Names[0].Obj] if ispackagelevel && !isconst { c.createGlobals(valspec.Names, valspec.Values, pkgname) return } var values []Value if len(valspec.Values) == 1 && len(valspec.Names) > 1 { values = c.destructureExpr(valspec.Values[0]) } else if len(valspec.Values) > 0 { values = make([]Value, len(valspec.Names)) for i, name_ := range valspec.Names { if isconst { if iota_, isint := (name_.Obj.Data).(int); isint { iotaValue := c.NewConstValue(token.INT, strconv.Itoa(iota_)) iotaObj.Data = iotaValue } } values[i] = c.VisitExpr(valspec.Values[i]) } } for i, name := range valspec.Names { if name.Name == "_" { continue } // For constants, we just pass the ConstValue around. Otherwise, we // will convert it to an LLVMValue. var value Value if isconst { value = values[i].Convert(name.Obj.Type.(types.Type)) } else { // The variable should be allocated on the stack if it's // declared inside a function. var llvmInit llvm.Value typ := name.Obj.Type.(types.Type) ptr := c.builder.CreateAlloca(c.types.ToLLVM(typ), name.Name) if values == nil || values[i] == nil { // If no initialiser was specified, set it to the // zero value. llvmInit = llvm.ConstNull(c.types.ToLLVM(typ)) } else { llvmInit = values[i].Convert(typ).LLVMValue() } c.builder.CreateStore(llvmInit, ptr) value = c.NewLLVMValue(ptr, &types.Pointer{Base: typ}).makePointee() } name.Obj.Data = value } }