package parser
import (
"fmt"
"go/ast"
"go/token"
"strings"
)
const debugResolve = false
func resolveFile(file *ast .File , handle *token .File , declErr func (token .Pos , string )) {
pkgScope := ast .NewScope (nil )
r := &resolver {
handle : handle ,
declErr : declErr ,
topScope : pkgScope ,
pkgScope : pkgScope ,
depth : 1 ,
}
for _ , decl := range file .Decls {
ast .Walk (r , decl )
}
r .closeScope ()
assert (r .topScope == nil , "unbalanced scopes" )
assert (r .labelScope == nil , "unbalanced label scopes" )
i := 0
for _ , ident := range r .unresolved {
assert (ident .Obj == unresolved , "object already resolved" )
ident .Obj = r .pkgScope .Lookup (ident .Name )
if ident .Obj == nil {
r .unresolved [i ] = ident
i ++
} else if debugResolve {
pos := ident .Obj .Decl .(interface { Pos () token .Pos }).Pos ()
r .trace ("resolved %s@%v to package object %v" , ident .Name , ident .Pos (), pos )
}
}
file .Scope = r .pkgScope
file .Unresolved = r .unresolved [0 :i ]
}
const maxScopeDepth int = 1e3
type resolver struct {
handle *token .File
declErr func (token .Pos , string )
pkgScope *ast .Scope
topScope *ast .Scope
unresolved []*ast .Ident
depth int
labelScope *ast .Scope
targetStack [][]*ast .Ident
}
func (r *resolver ) trace (format string , args ...any ) {
fmt .Println (strings .Repeat (". " , r .depth ) + r .sprintf (format , args ...))
}
func (r *resolver ) sprintf (format string , args ...any ) string {
for i , arg := range args {
switch arg := arg .(type ) {
case token .Pos :
args [i ] = r .handle .Position (arg )
}
}
return fmt .Sprintf (format , args ...)
}
func (r *resolver ) openScope (pos token .Pos ) {
r .depth ++
if r .depth > maxScopeDepth {
panic (bailout {pos : pos , msg : "exceeded max scope depth during object resolution" })
}
if debugResolve {
r .trace ("opening scope @%v" , pos )
}
r .topScope = ast .NewScope (r .topScope )
}
func (r *resolver ) closeScope () {
r .depth --
if debugResolve {
r .trace ("closing scope" )
}
r .topScope = r .topScope .Outer
}
func (r *resolver ) openLabelScope () {
r .labelScope = ast .NewScope (r .labelScope )
r .targetStack = append (r .targetStack , nil )
}
func (r *resolver ) closeLabelScope () {
n := len (r .targetStack ) - 1
scope := r .labelScope
for _ , ident := range r .targetStack [n ] {
ident .Obj = scope .Lookup (ident .Name )
if ident .Obj == nil && r .declErr != nil {
r .declErr (ident .Pos (), fmt .Sprintf ("label %s undefined" , ident .Name ))
}
}
r .targetStack = r .targetStack [0 :n ]
r .labelScope = r .labelScope .Outer
}
func (r *resolver ) declare (decl , data any , scope *ast .Scope , kind ast .ObjKind , idents ...*ast .Ident ) {
for _ , ident := range idents {
if ident .Obj != nil {
panic (fmt .Sprintf ("%v: identifier %s already declared or resolved" , ident .Pos (), ident .Name ))
}
obj := ast .NewObj (kind , ident .Name )
obj .Decl = decl
obj .Data = data
if _ , ok := decl .(*ast .Ident ); !ok {
ident .Obj = obj
}
if ident .Name != "_" {
if debugResolve {
r .trace ("declaring %s@%v" , ident .Name , ident .Pos ())
}
if alt := scope .Insert (obj ); alt != nil && r .declErr != nil {
prevDecl := ""
if pos := alt .Pos (); pos .IsValid () {
prevDecl = r .sprintf ("\n\tprevious declaration at %v" , pos )
}
r .declErr (ident .Pos (), fmt .Sprintf ("%s redeclared in this block%s" , ident .Name , prevDecl ))
}
}
}
}
func (r *resolver ) shortVarDecl (decl *ast .AssignStmt ) {
n := 0
for _ , x := range decl .Lhs {
if ident , isIdent := x .(*ast .Ident ); isIdent {
assert (ident .Obj == nil , "identifier already declared or resolved" )
obj := ast .NewObj (ast .Var , ident .Name )
obj .Decl = decl
ident .Obj = obj
if ident .Name != "_" {
if debugResolve {
r .trace ("declaring %s@%v" , ident .Name , ident .Pos ())
}
if alt := r .topScope .Insert (obj ); alt != nil {
ident .Obj = alt
} else {
n ++
}
}
}
}
if n == 0 && r .declErr != nil {
r .declErr (decl .Lhs [0 ].Pos (), "no new variables on left side of :=" )
}
}
var unresolved = new (ast .Object )
func (r *resolver ) resolve (ident *ast .Ident , collectUnresolved bool ) {
if ident .Obj != nil {
panic (r .sprintf ("%v: identifier %s already declared or resolved" , ident .Pos (), ident .Name ))
}
if ident .Name == "_" {
return
}
for s := r .topScope ; s != nil ; s = s .Outer {
if obj := s .Lookup (ident .Name ); obj != nil {
if debugResolve {
r .trace ("resolved %v:%s to %v" , ident .Pos (), ident .Name , obj )
}
assert (obj .Name != "" , "obj with no name" )
if _ , ok := obj .Decl .(*ast .Ident ); !ok {
ident .Obj = obj
}
return
}
}
if collectUnresolved {
ident .Obj = unresolved
r .unresolved = append (r .unresolved , ident )
}
}
func (r *resolver ) walkExprs (list []ast .Expr ) {
for _ , node := range list {
ast .Walk (r , node )
}
}
func (r *resolver ) walkLHS (list []ast .Expr ) {
for _ , expr := range list {
expr := ast .Unparen (expr )
if _ , ok := expr .(*ast .Ident ); !ok && expr != nil {
ast .Walk (r , expr )
}
}
}
func (r *resolver ) walkStmts (list []ast .Stmt ) {
for _ , stmt := range list {
ast .Walk (r , stmt )
}
}
func (r *resolver ) Visit (node ast .Node ) ast .Visitor {
if debugResolve && node != nil {
r .trace ("node %T@%v" , node , node .Pos ())
}
switch n := node .(type ) {
case *ast .Ident :
r .resolve (n , true )
case *ast .FuncLit :
r .openScope (n .Pos ())
defer r .closeScope ()
r .walkFuncType (n .Type )
r .walkBody (n .Body )
case *ast .SelectorExpr :
ast .Walk (r , n .X )
case *ast .StructType :
r .openScope (n .Pos ())
defer r .closeScope ()
r .walkFieldList (n .Fields , ast .Var )
case *ast .FuncType :
r .openScope (n .Pos ())
defer r .closeScope ()
r .walkFuncType (n )
case *ast .CompositeLit :
if n .Type != nil {
ast .Walk (r , n .Type )
}
for _ , e := range n .Elts {
if kv , _ := e .(*ast .KeyValueExpr ); kv != nil {
if ident , _ := kv .Key .(*ast .Ident ); ident != nil {
r .resolve (ident , false )
} else {
ast .Walk (r , kv .Key )
}
ast .Walk (r , kv .Value )
} else {
ast .Walk (r , e )
}
}
case *ast .InterfaceType :
r .openScope (n .Pos ())
defer r .closeScope ()
r .walkFieldList (n .Methods , ast .Fun )
case *ast .LabeledStmt :
r .declare (n , nil , r .labelScope , ast .Lbl , n .Label )
ast .Walk (r , n .Stmt )
case *ast .AssignStmt :
r .walkExprs (n .Rhs )
if n .Tok == token .DEFINE {
r .shortVarDecl (n )
} else {
r .walkExprs (n .Lhs )
}
case *ast .BranchStmt :
if n .Tok != token .FALLTHROUGH && n .Label != nil {
depth := len (r .targetStack ) - 1
r .targetStack [depth ] = append (r .targetStack [depth ], n .Label )
}
case *ast .BlockStmt :
r .openScope (n .Pos ())
defer r .closeScope ()
r .walkStmts (n .List )
case *ast .IfStmt :
r .openScope (n .Pos ())
defer r .closeScope ()
if n .Init != nil {
ast .Walk (r , n .Init )
}
ast .Walk (r , n .Cond )
ast .Walk (r , n .Body )
if n .Else != nil {
ast .Walk (r , n .Else )
}
case *ast .CaseClause :
r .walkExprs (n .List )
r .openScope (n .Pos ())
defer r .closeScope ()
r .walkStmts (n .Body )
case *ast .SwitchStmt :
r .openScope (n .Pos ())
defer r .closeScope ()
if n .Init != nil {
ast .Walk (r , n .Init )
}
if n .Tag != nil {
if n .Init != nil {
r .openScope (n .Tag .Pos ())
defer r .closeScope ()
}
ast .Walk (r , n .Tag )
}
if n .Body != nil {
r .walkStmts (n .Body .List )
}
case *ast .TypeSwitchStmt :
if n .Init != nil {
r .openScope (n .Pos ())
defer r .closeScope ()
ast .Walk (r , n .Init )
}
r .openScope (n .Assign .Pos ())
defer r .closeScope ()
ast .Walk (r , n .Assign )
if n .Body != nil {
r .walkStmts (n .Body .List )
}
case *ast .CommClause :
r .openScope (n .Pos ())
defer r .closeScope ()
if n .Comm != nil {
ast .Walk (r , n .Comm )
}
r .walkStmts (n .Body )
case *ast .SelectStmt :
if n .Body != nil {
r .walkStmts (n .Body .List )
}
case *ast .ForStmt :
r .openScope (n .Pos ())
defer r .closeScope ()
if n .Init != nil {
ast .Walk (r , n .Init )
}
if n .Cond != nil {
ast .Walk (r , n .Cond )
}
if n .Post != nil {
ast .Walk (r , n .Post )
}
ast .Walk (r , n .Body )
case *ast .RangeStmt :
r .openScope (n .Pos ())
defer r .closeScope ()
ast .Walk (r , n .X )
var lhs []ast .Expr
if n .Key != nil {
lhs = append (lhs , n .Key )
}
if n .Value != nil {
lhs = append (lhs , n .Value )
}
if len (lhs ) > 0 {
if n .Tok == token .DEFINE {
as := &ast .AssignStmt {
Lhs : lhs ,
Tok : token .DEFINE ,
TokPos : n .TokPos ,
Rhs : []ast .Expr {&ast .UnaryExpr {Op : token .RANGE , X : n .X }},
}
r .walkLHS (lhs )
r .shortVarDecl (as )
} else {
r .walkExprs (lhs )
}
}
ast .Walk (r , n .Body )
case *ast .GenDecl :
switch n .Tok {
case token .CONST , token .VAR :
for i , spec := range n .Specs {
spec := spec .(*ast .ValueSpec )
kind := ast .Con
if n .Tok == token .VAR {
kind = ast .Var
}
r .walkExprs (spec .Values )
if spec .Type != nil {
ast .Walk (r , spec .Type )
}
r .declare (spec , i , r .topScope , kind , spec .Names ...)
}
case token .TYPE :
for _ , spec := range n .Specs {
spec := spec .(*ast .TypeSpec )
r .declare (spec , nil , r .topScope , ast .Typ , spec .Name )
if spec .TypeParams != nil {
r .openScope (spec .Pos ())
defer r .closeScope ()
r .walkTParams (spec .TypeParams )
}
ast .Walk (r , spec .Type )
}
}
case *ast .FuncDecl :
r .openScope (n .Pos ())
defer r .closeScope ()
r .walkRecv (n .Recv )
if n .Type .TypeParams != nil {
r .walkTParams (n .Type .TypeParams )
}
r .resolveList (n .Type .Params )
r .resolveList (n .Type .Results )
r .declareList (n .Recv , ast .Var )
r .declareList (n .Type .Params , ast .Var )
r .declareList (n .Type .Results , ast .Var )
r .walkBody (n .Body )
if n .Recv == nil && n .Name .Name != "init" {
r .declare (n , nil , r .pkgScope , ast .Fun , n .Name )
}
default :
return r
}
return nil
}
func (r *resolver ) walkFuncType (typ *ast .FuncType ) {
r .resolveList (typ .Params )
r .resolveList (typ .Results )
r .declareList (typ .Params , ast .Var )
r .declareList (typ .Results , ast .Var )
}
func (r *resolver ) resolveList (list *ast .FieldList ) {
if list == nil {
return
}
for _ , f := range list .List {
if f .Type != nil {
ast .Walk (r , f .Type )
}
}
}
func (r *resolver ) declareList (list *ast .FieldList , kind ast .ObjKind ) {
if list == nil {
return
}
for _ , f := range list .List {
r .declare (f , nil , r .topScope , kind , f .Names ...)
}
}
func (r *resolver ) walkRecv (recv *ast .FieldList ) {
if recv == nil || len (recv .List ) == 0 {
return
}
typ := recv .List [0 ].Type
if ptr , ok := typ .(*ast .StarExpr ); ok {
typ = ptr .X
}
var declareExprs []ast .Expr
var resolveExprs []ast .Expr
switch typ := typ .(type ) {
case *ast .IndexExpr :
declareExprs = []ast .Expr {typ .Index }
resolveExprs = append (resolveExprs , typ .X )
case *ast .IndexListExpr :
declareExprs = typ .Indices
resolveExprs = append (resolveExprs , typ .X )
default :
resolveExprs = append (resolveExprs , typ )
}
for _ , expr := range declareExprs {
if id , _ := expr .(*ast .Ident ); id != nil {
r .declare (expr , nil , r .topScope , ast .Typ , id )
} else {
resolveExprs = append (resolveExprs , expr )
}
}
for _ , expr := range resolveExprs {
if expr != nil {
ast .Walk (r , expr )
}
}
for _ , f := range recv .List [1 :] {
if f .Type != nil {
ast .Walk (r , f .Type )
}
}
}
func (r *resolver ) walkFieldList (list *ast .FieldList , kind ast .ObjKind ) {
if list == nil {
return
}
r .resolveList (list )
r .declareList (list , kind )
}
func (r *resolver ) walkTParams (list *ast .FieldList ) {
r .declareList (list , ast .Typ )
r .resolveList (list )
}
func (r *resolver ) walkBody (body *ast .BlockStmt ) {
if body == nil {
return
}
r .openLabelScope ()
defer r .closeLabelScope ()
r .walkStmts (body .List )
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .