astdump.go raw

   1  package main
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/token"
   7  	"strings"
   8  )
   9  
  10  // dumpAST produces a compact text representation of a declaration's AST.
  11  // Format: indented tree with node types and key identifiers.
  12  func dumpAST(fset *token.FileSet, decl ast.Decl) string {
  13  	var b strings.Builder
  14  	dumpNode(&b, fset, decl, 0)
  15  	return b.String()
  16  }
  17  
  18  func dumpNode(b *strings.Builder, fset *token.FileSet, n ast.Node, depth int) {
  19  	if n == nil {
  20  		return
  21  	}
  22  	indent := strings.Repeat("  ", depth)
  23  
  24  	switch x := n.(type) {
  25  	case *ast.FuncDecl:
  26  		recv := ""
  27  		if x.Recv != nil && len(x.Recv.List) > 0 {
  28  			recv = " recv=" + exprStr(x.Recv.List[0].Type)
  29  		}
  30  		fmt.Fprintf(b, "%sFuncDecl %s%s\n", indent, x.Name.Name, recv)
  31  		if x.Type != nil {
  32  			dumpFuncType(b, fset, x.Type, depth+1)
  33  		}
  34  		if x.Body != nil {
  35  			dumpBlock(b, fset, x.Body, depth+1)
  36  		}
  37  
  38  	case *ast.GenDecl:
  39  		fmt.Fprintf(b, "%sGenDecl %s\n", indent, x.Tok)
  40  		for _, spec := range x.Specs {
  41  			dumpSpec(b, fset, spec, depth+1)
  42  		}
  43  
  44  	default:
  45  		fmt.Fprintf(b, "%s%T\n", indent, n)
  46  	}
  47  }
  48  
  49  func dumpFuncType(b *strings.Builder, fset *token.FileSet, ft *ast.FuncType, depth int) {
  50  	indent := strings.Repeat("  ", depth)
  51  	if ft.TypeParams != nil && len(ft.TypeParams.List) > 0 {
  52  		fmt.Fprintf(b, "%sTypeParams\n", indent)
  53  		for _, f := range ft.TypeParams.List {
  54  			dumpField(b, f, depth+1)
  55  		}
  56  	}
  57  	if ft.Params != nil && len(ft.Params.List) > 0 {
  58  		fmt.Fprintf(b, "%sParams\n", indent)
  59  		for _, f := range ft.Params.List {
  60  			dumpField(b, f, depth+1)
  61  		}
  62  	}
  63  	if ft.Results != nil && len(ft.Results.List) > 0 {
  64  		fmt.Fprintf(b, "%sResults\n", indent)
  65  		for _, f := range ft.Results.List {
  66  			dumpField(b, f, depth+1)
  67  		}
  68  	}
  69  }
  70  
  71  func dumpField(b *strings.Builder, f *ast.Field, depth int) {
  72  	indent := strings.Repeat("  ", depth)
  73  	names := ""
  74  	for i, n := range f.Names {
  75  		if i > 0 {
  76  			names += ","
  77  		}
  78  		names += n.Name
  79  	}
  80  	if names != "" {
  81  		fmt.Fprintf(b, "%s%s %s\n", indent, names, exprStr(f.Type))
  82  	} else {
  83  		fmt.Fprintf(b, "%s%s\n", indent, exprStr(f.Type))
  84  	}
  85  }
  86  
  87  func dumpSpec(b *strings.Builder, fset *token.FileSet, spec ast.Spec, depth int) {
  88  	indent := strings.Repeat("  ", depth)
  89  	switch s := spec.(type) {
  90  	case *ast.ImportSpec:
  91  		path := ""
  92  		if s.Path != nil {
  93  			path = s.Path.Value
  94  		}
  95  		name := ""
  96  		if s.Name != nil {
  97  			name = s.Name.Name + " "
  98  		}
  99  		fmt.Fprintf(b, "%sImport %s%s\n", indent, name, path)
 100  	case *ast.TypeSpec:
 101  		fmt.Fprintf(b, "%sType %s %s\n", indent, s.Name.Name, exprStr(s.Type))
 102  		if st, ok := s.Type.(*ast.StructType); ok && st.Fields != nil {
 103  			for _, f := range st.Fields.List {
 104  				dumpField(b, f, depth+1)
 105  			}
 106  		}
 107  		if it, ok := s.Type.(*ast.InterfaceType); ok && it.Methods != nil {
 108  			for _, f := range it.Methods.List {
 109  				dumpField(b, f, depth+1)
 110  			}
 111  		}
 112  	case *ast.ValueSpec:
 113  		names := ""
 114  		for i, n := range s.Names {
 115  			if i > 0 {
 116  				names += ","
 117  			}
 118  			names += n.Name
 119  		}
 120  		typ := ""
 121  		if s.Type != nil {
 122  			typ = " " + exprStr(s.Type)
 123  		}
 124  		fmt.Fprintf(b, "%sValue %s%s\n", indent, names, typ)
 125  	}
 126  }
 127  
 128  func dumpBlock(b *strings.Builder, fset *token.FileSet, block *ast.BlockStmt, depth int) {
 129  	indent := strings.Repeat("  ", depth)
 130  	fmt.Fprintf(b, "%sBlock\n", indent)
 131  	for _, stmt := range block.List {
 132  		dumpStmt(b, fset, stmt, depth+1)
 133  	}
 134  }
 135  
 136  func dumpStmt(b *strings.Builder, fset *token.FileSet, stmt ast.Stmt, depth int) {
 137  	indent := strings.Repeat("  ", depth)
 138  	switch s := stmt.(type) {
 139  	case *ast.ReturnStmt:
 140  		refs := collectRefs(s.Results...)
 141  		if len(refs) > 0 {
 142  			fmt.Fprintf(b, "%sReturn [%s]\n", indent, strings.Join(refs, ","))
 143  		} else {
 144  			fmt.Fprintf(b, "%sReturn\n", indent)
 145  		}
 146  	case *ast.AssignStmt:
 147  		lhs := exprListStr(s.Lhs)
 148  		allExprs := append(s.Lhs, s.Rhs...)
 149  		refs := collectRefs(allExprs...)
 150  		if len(refs) > 0 {
 151  			fmt.Fprintf(b, "%sAssign %s %s [%s]\n", indent, lhs, s.Tok, strings.Join(refs, ","))
 152  		} else {
 153  			fmt.Fprintf(b, "%sAssign %s %s\n", indent, lhs, s.Tok)
 154  		}
 155  	case *ast.ExprStmt:
 156  		refs := collectRefs(s.X)
 157  		if len(refs) > 0 {
 158  			fmt.Fprintf(b, "%sExpr %s [%s]\n", indent, exprStr(s.X), strings.Join(refs, ","))
 159  		} else {
 160  			fmt.Fprintf(b, "%sExpr %s\n", indent, exprStr(s.X))
 161  		}
 162  	case *ast.IfStmt:
 163  		var allExprs []ast.Expr
 164  		if s.Init != nil {
 165  			if as, ok := s.Init.(*ast.AssignStmt); ok {
 166  				allExprs = append(allExprs, as.Lhs...)
 167  				allExprs = append(allExprs, as.Rhs...)
 168  			}
 169  		}
 170  		allExprs = append(allExprs, s.Cond)
 171  		refs := collectRefs(allExprs...)
 172  		if len(refs) > 0 {
 173  			fmt.Fprintf(b, "%sIf [%s]\n", indent, strings.Join(refs, ","))
 174  		} else {
 175  			fmt.Fprintf(b, "%sIf\n", indent)
 176  		}
 177  		if s.Body != nil {
 178  			dumpBlock(b, fset, s.Body, depth+1)
 179  		}
 180  		if s.Else != nil {
 181  			fmt.Fprintf(b, "%sElse\n", indent)
 182  			dumpStmt(b, fset, s.Else, depth+1)
 183  		}
 184  	case *ast.ForStmt:
 185  		var allExprs []ast.Expr
 186  		if s.Init != nil {
 187  			if as, ok := s.Init.(*ast.AssignStmt); ok {
 188  				allExprs = append(allExprs, as.Lhs...)
 189  				allExprs = append(allExprs, as.Rhs...)
 190  			}
 191  		}
 192  		allExprs = append(allExprs, s.Cond)
 193  		if s.Post != nil {
 194  			if as, ok := s.Post.(*ast.AssignStmt); ok {
 195  				allExprs = append(allExprs, as.Lhs...)
 196  				allExprs = append(allExprs, as.Rhs...)
 197  			}
 198  			if inc, ok := s.Post.(*ast.IncDecStmt); ok {
 199  				allExprs = append(allExprs, inc.X)
 200  			}
 201  		}
 202  		refs := collectRefs(allExprs...)
 203  		if len(refs) > 0 {
 204  			fmt.Fprintf(b, "%sFor [%s]\n", indent, strings.Join(refs, ","))
 205  		} else {
 206  			fmt.Fprintf(b, "%sFor\n", indent)
 207  		}
 208  		if s.Body != nil {
 209  			dumpBlock(b, fset, s.Body, depth+1)
 210  		}
 211  	case *ast.RangeStmt:
 212  		allExprs := []ast.Expr{s.X}
 213  		if s.Key != nil {
 214  			allExprs = append(allExprs, s.Key)
 215  		}
 216  		if s.Value != nil {
 217  			allExprs = append(allExprs, s.Value)
 218  		}
 219  		refs := collectRefs(allExprs...)
 220  		rangeStr := exprStr(s.X)
 221  		if len(refs) > 0 {
 222  			fmt.Fprintf(b, "%sRange %s [%s]\n", indent, rangeStr, strings.Join(refs, ","))
 223  		} else {
 224  			fmt.Fprintf(b, "%sRange %s\n", indent, rangeStr)
 225  		}
 226  		if s.Body != nil {
 227  			dumpBlock(b, fset, s.Body, depth+1)
 228  		}
 229  	case *ast.SwitchStmt:
 230  		var allExprs []ast.Expr
 231  		if s.Init != nil {
 232  			if as, ok := s.Init.(*ast.AssignStmt); ok {
 233  				allExprs = append(allExprs, as.Lhs...)
 234  				allExprs = append(allExprs, as.Rhs...)
 235  			}
 236  		}
 237  		allExprs = append(allExprs, s.Tag)
 238  		refs := collectRefs(allExprs...)
 239  		if len(refs) > 0 {
 240  			fmt.Fprintf(b, "%sSwitch [%s]\n", indent, strings.Join(refs, ","))
 241  		} else {
 242  			fmt.Fprintf(b, "%sSwitch\n", indent)
 243  		}
 244  		if s.Body != nil {
 245  			dumpBlock(b, fset, s.Body, depth+1)
 246  		}
 247  	case *ast.TypeSwitchStmt:
 248  		fmt.Fprintf(b, "%sTypeSwitch\n", indent)
 249  		if s.Body != nil {
 250  			dumpBlock(b, fset, s.Body, depth+1)
 251  		}
 252  	case *ast.SelectStmt:
 253  		fmt.Fprintf(b, "%sSelect\n", indent)
 254  		if s.Body != nil {
 255  			dumpBlock(b, fset, s.Body, depth+1)
 256  		}
 257  	case *ast.DeclStmt:
 258  		dumpNode(b, fset, s.Decl, depth)
 259  	case *ast.BlockStmt:
 260  		dumpBlock(b, fset, s, depth)
 261  	case *ast.CaseClause:
 262  		if s.List == nil {
 263  			fmt.Fprintf(b, "%sDefault\n", indent)
 264  		} else {
 265  			refs := collectRefs(s.List...)
 266  			if len(refs) > 0 {
 267  				fmt.Fprintf(b, "%sCase [%s]\n", indent, strings.Join(refs, ","))
 268  			} else {
 269  				fmt.Fprintf(b, "%sCase\n", indent)
 270  			}
 271  		}
 272  		for _, st := range s.Body {
 273  			dumpStmt(b, fset, st, depth+1)
 274  		}
 275  	case *ast.CommClause:
 276  		if s.Comm == nil {
 277  			fmt.Fprintf(b, "%sDefault\n", indent)
 278  		} else {
 279  			fmt.Fprintf(b, "%sComm\n", indent)
 280  		}
 281  		for _, st := range s.Body {
 282  			dumpStmt(b, fset, st, depth+1)
 283  		}
 284  	case *ast.BranchStmt:
 285  		fmt.Fprintf(b, "%s%s\n", indent, s.Tok)
 286  	case *ast.DeferStmt:
 287  		refs := collectRefs(s.Call)
 288  		if len(refs) > 0 {
 289  			fmt.Fprintf(b, "%sDefer %s [%s]\n", indent, exprStr(s.Call.Fun), strings.Join(refs, ","))
 290  		} else {
 291  			fmt.Fprintf(b, "%sDefer %s\n", indent, exprStr(s.Call.Fun))
 292  		}
 293  	case *ast.GoStmt:
 294  		refs := collectRefs(s.Call)
 295  		if len(refs) > 0 {
 296  			fmt.Fprintf(b, "%sGo %s [%s]\n", indent, exprStr(s.Call.Fun), strings.Join(refs, ","))
 297  		} else {
 298  			fmt.Fprintf(b, "%sGo %s\n", indent, exprStr(s.Call.Fun))
 299  		}
 300  	case *ast.SendStmt:
 301  		refs := collectRefs(s.Chan, s.Value)
 302  		if len(refs) > 0 {
 303  			fmt.Fprintf(b, "%sSend %s [%s]\n", indent, exprStr(s.Chan), strings.Join(refs, ","))
 304  		} else {
 305  			fmt.Fprintf(b, "%sSend %s\n", indent, exprStr(s.Chan))
 306  		}
 307  	case *ast.IncDecStmt:
 308  		refs := collectRefs(s.X)
 309  		if len(refs) > 0 {
 310  			fmt.Fprintf(b, "%s%s %s [%s]\n", indent, exprStr(s.X), s.Tok, strings.Join(refs, ","))
 311  		} else {
 312  			fmt.Fprintf(b, "%s%s %s\n", indent, exprStr(s.X), s.Tok)
 313  		}
 314  	case *ast.LabeledStmt:
 315  		fmt.Fprintf(b, "%sLabel %s\n", indent, s.Label.Name)
 316  		dumpStmt(b, fset, s.Stmt, depth+1)
 317  	default:
 318  		fmt.Fprintf(b, "%s%T\n", indent, stmt)
 319  	}
 320  }
 321  
 322  func exprStr(e ast.Expr) string {
 323  	if e == nil {
 324  		return ""
 325  	}
 326  	switch x := e.(type) {
 327  	case *ast.Ident:
 328  		return x.Name
 329  	case *ast.SelectorExpr:
 330  		return exprStr(x.X) + "." + x.Sel.Name
 331  	case *ast.StarExpr:
 332  		return "*" + exprStr(x.X)
 333  	case *ast.ArrayType:
 334  		if x.Len == nil {
 335  			return "[]" + exprStr(x.Elt)
 336  		}
 337  		return "[" + exprStr(x.Len) + "]" + exprStr(x.Elt)
 338  	case *ast.MapType:
 339  		return "map[" + exprStr(x.Key) + "]" + exprStr(x.Value)
 340  	case *ast.ChanType:
 341  		return "chan " + exprStr(x.Value)
 342  	case *ast.FuncType:
 343  		return "func(...)"
 344  	case *ast.InterfaceType:
 345  		return "interface{}"
 346  	case *ast.StructType:
 347  		return "struct{...}"
 348  	case *ast.Ellipsis:
 349  		return "..." + exprStr(x.Elt)
 350  	case *ast.CallExpr:
 351  		return exprStr(x.Fun) + "(...)"
 352  	case *ast.IndexExpr:
 353  		return exprStr(x.X) + "[" + exprStr(x.Index) + "]"
 354  	case *ast.BasicLit:
 355  		return x.Value
 356  	case *ast.UnaryExpr:
 357  		return x.Op.String() + exprStr(x.X)
 358  	case *ast.BinaryExpr:
 359  		return exprStr(x.X) + x.Op.String() + exprStr(x.Y)
 360  	case *ast.ParenExpr:
 361  		return "(" + exprStr(x.X) + ")"
 362  	case *ast.CompositeLit:
 363  		if x.Type != nil {
 364  			return exprStr(x.Type) + "{...}"
 365  		}
 366  		return "{...}"
 367  	case *ast.TypeAssertExpr:
 368  		if x.Type != nil {
 369  			return exprStr(x.X) + ".(" + exprStr(x.Type) + ")"
 370  		}
 371  		return exprStr(x.X) + ".(type)"
 372  	case *ast.SliceExpr:
 373  		return exprStr(x.X) + "[:]"
 374  	case *ast.KeyValueExpr:
 375  		return exprStr(x.Key) + ":" + exprStr(x.Value)
 376  	case *ast.FuncLit:
 377  		return "func(){...}"
 378  	case *ast.IndexListExpr:
 379  		parts := make([]string, len(x.Indices))
 380  		for i, idx := range x.Indices {
 381  			parts[i] = exprStr(idx)
 382  		}
 383  		return exprStr(x.X) + "[" + strings.Join(parts, ",") + "]"
 384  	}
 385  	return fmt.Sprintf("%T", e)
 386  }
 387  
 388  var builtinTypes = map[string]bool{
 389  	"bool": true, "byte": true, "int": true, "int8": true, "int16": true,
 390  	"int32": true, "int64": true, "uint": true, "uint8": true, "uint16": true,
 391  	"uint32": true, "uint64": true, "float32": true, "float64": true,
 392  	"string": true, "rune": true, "error": true, "any": true,
 393  	"true": true, "false": true, "nil": true,
 394  	"len": true, "cap": true, "append": true, "copy": true, "delete": true,
 395  	"close": true, "panic": true, "recover": true, "print": true, "println": true,
 396  	"make": true, "new": true,
 397  }
 398  
 399  func collectRefs(exprs ...ast.Expr) []string {
 400  	seen := map[string]bool{}
 401  	var refs []string
 402  	for _, e := range exprs {
 403  		walkExpr(e, func(id string) {
 404  			if !seen[id] && !builtinTypes[id] {
 405  				seen[id] = true
 406  				refs = append(refs, id)
 407  			}
 408  		})
 409  	}
 410  	return refs
 411  }
 412  
 413  func walkExpr(e ast.Expr, visit func(string)) {
 414  	if e == nil {
 415  		return
 416  	}
 417  	switch x := e.(type) {
 418  	case *ast.Ident:
 419  		visit(x.Name)
 420  	case *ast.SelectorExpr:
 421  		walkExpr(x.X, visit)
 422  		visit(x.Sel.Name)
 423  	case *ast.CallExpr:
 424  		walkExpr(x.Fun, visit)
 425  		for _, a := range x.Args {
 426  			walkExpr(a, visit)
 427  		}
 428  	case *ast.BinaryExpr:
 429  		walkExpr(x.X, visit)
 430  		walkExpr(x.Y, visit)
 431  	case *ast.UnaryExpr:
 432  		walkExpr(x.X, visit)
 433  	case *ast.StarExpr:
 434  		walkExpr(x.X, visit)
 435  	case *ast.ParenExpr:
 436  		walkExpr(x.X, visit)
 437  	case *ast.IndexExpr:
 438  		walkExpr(x.X, visit)
 439  		walkExpr(x.Index, visit)
 440  	case *ast.SliceExpr:
 441  		walkExpr(x.X, visit)
 442  		walkExpr(x.Low, visit)
 443  		walkExpr(x.High, visit)
 444  		walkExpr(x.Max, visit)
 445  	case *ast.CompositeLit:
 446  		walkExpr(x.Type, visit)
 447  		for _, elt := range x.Elts {
 448  			walkExpr(elt, visit)
 449  		}
 450  	case *ast.KeyValueExpr:
 451  		walkExpr(x.Key, visit)
 452  		walkExpr(x.Value, visit)
 453  	case *ast.TypeAssertExpr:
 454  		walkExpr(x.X, visit)
 455  		walkExpr(x.Type, visit)
 456  	case *ast.FuncLit:
 457  		if x.Body != nil {
 458  			for _, stmt := range x.Body.List {
 459  				walkStmtRefs(stmt, visit)
 460  			}
 461  		}
 462  	case *ast.ArrayType:
 463  		walkExpr(x.Elt, visit)
 464  		walkExpr(x.Len, visit)
 465  	case *ast.MapType:
 466  		walkExpr(x.Key, visit)
 467  		walkExpr(x.Value, visit)
 468  	case *ast.IndexListExpr:
 469  		walkExpr(x.X, visit)
 470  		for _, idx := range x.Indices {
 471  			walkExpr(idx, visit)
 472  		}
 473  	}
 474  }
 475  
 476  func walkStmtRefs(stmt ast.Stmt, visit func(string)) {
 477  	if stmt == nil {
 478  		return
 479  	}
 480  	switch s := stmt.(type) {
 481  	case *ast.AssignStmt:
 482  		for _, e := range s.Lhs {
 483  			walkExpr(e, visit)
 484  		}
 485  		for _, e := range s.Rhs {
 486  			walkExpr(e, visit)
 487  		}
 488  	case *ast.ReturnStmt:
 489  		for _, e := range s.Results {
 490  			walkExpr(e, visit)
 491  		}
 492  	case *ast.ExprStmt:
 493  		walkExpr(s.X, visit)
 494  	case *ast.IfStmt:
 495  		if s.Init != nil {
 496  			walkStmtRefs(s.Init, visit)
 497  		}
 498  		walkExpr(s.Cond, visit)
 499  		if s.Body != nil {
 500  			for _, st := range s.Body.List {
 501  				walkStmtRefs(st, visit)
 502  			}
 503  		}
 504  		if s.Else != nil {
 505  			walkStmtRefs(s.Else, visit)
 506  		}
 507  	case *ast.ForStmt:
 508  		if s.Init != nil {
 509  			walkStmtRefs(s.Init, visit)
 510  		}
 511  		walkExpr(s.Cond, visit)
 512  		if s.Post != nil {
 513  			walkStmtRefs(s.Post, visit)
 514  		}
 515  		if s.Body != nil {
 516  			for _, st := range s.Body.List {
 517  				walkStmtRefs(st, visit)
 518  			}
 519  		}
 520  	case *ast.RangeStmt:
 521  		walkExpr(s.Key, visit)
 522  		walkExpr(s.Value, visit)
 523  		walkExpr(s.X, visit)
 524  		if s.Body != nil {
 525  			for _, st := range s.Body.List {
 526  				walkStmtRefs(st, visit)
 527  			}
 528  		}
 529  	case *ast.BlockStmt:
 530  		for _, st := range s.List {
 531  			walkStmtRefs(st, visit)
 532  		}
 533  	case *ast.SwitchStmt:
 534  		if s.Init != nil {
 535  			walkStmtRefs(s.Init, visit)
 536  		}
 537  		walkExpr(s.Tag, visit)
 538  		if s.Body != nil {
 539  			for _, st := range s.Body.List {
 540  				walkStmtRefs(st, visit)
 541  			}
 542  		}
 543  	case *ast.TypeSwitchStmt:
 544  		if s.Init != nil {
 545  			walkStmtRefs(s.Init, visit)
 546  		}
 547  		walkStmtRefs(s.Assign, visit)
 548  		if s.Body != nil {
 549  			for _, st := range s.Body.List {
 550  				walkStmtRefs(st, visit)
 551  			}
 552  		}
 553  	case *ast.CaseClause:
 554  		for _, e := range s.List {
 555  			walkExpr(e, visit)
 556  		}
 557  		for _, st := range s.Body {
 558  			walkStmtRefs(st, visit)
 559  		}
 560  	case *ast.CommClause:
 561  		walkStmtRefs(s.Comm, visit)
 562  		for _, st := range s.Body {
 563  			walkStmtRefs(st, visit)
 564  		}
 565  	case *ast.SelectStmt:
 566  		if s.Body != nil {
 567  			for _, st := range s.Body.List {
 568  				walkStmtRefs(st, visit)
 569  			}
 570  		}
 571  	case *ast.DeferStmt:
 572  		walkExpr(s.Call.Fun, visit)
 573  		for _, a := range s.Call.Args {
 574  			walkExpr(a, visit)
 575  		}
 576  	case *ast.GoStmt:
 577  		walkExpr(s.Call.Fun, visit)
 578  		for _, a := range s.Call.Args {
 579  			walkExpr(a, visit)
 580  		}
 581  	case *ast.SendStmt:
 582  		walkExpr(s.Chan, visit)
 583  		walkExpr(s.Value, visit)
 584  	case *ast.IncDecStmt:
 585  		walkExpr(s.X, visit)
 586  	case *ast.DeclStmt:
 587  		if gd, ok := s.Decl.(*ast.GenDecl); ok {
 588  			for _, sp := range gd.Specs {
 589  				if vs, ok := sp.(*ast.ValueSpec); ok {
 590  					for _, n := range vs.Names {
 591  						visit(n.Name)
 592  					}
 593  					for _, v := range vs.Values {
 594  						walkExpr(v, visit)
 595  					}
 596  				}
 597  			}
 598  		}
 599  	case *ast.LabeledStmt:
 600  		walkStmtRefs(s.Stmt, visit)
 601  	}
 602  }
 603  
 604  func exprListStr(exprs []ast.Expr) string {
 605  	parts := make([]string, len(exprs))
 606  	for i, e := range exprs {
 607  		parts[i] = exprStr(e)
 608  	}
 609  	return strings.Join(parts, ",")
 610  }
 611