1

I'm trying to inspect Go source code in order to make a tool. For this I'm using the ast.Inspect function.

I need to know how channels are being used inside a function/method.

I have this as an example code to inspect:

package main

func B(ch chan int) {
    for x := range ch {

    }
}

This is the AST for the function B:

 0  *ast.FuncDecl {
     1  .  Name: *ast.Ident {
     2  .  .  NamePos: -:4:6
     3  .  .  Name: "B"
     4  .  .  Obj: *ast.Object {
     5  .  .  .  Kind: func
     6  .  .  .  Name: "B"
     7  .  .  .  Decl: *(obj @ 0)
     8  .  .  }
     9  .  }
    10  .  Type: *ast.FuncType {
    11  .  .  Func: -:4:1
    12  .  .  Params: *ast.FieldList {
    13  .  .  .  Opening: -:4:7
    14  .  .  .  List: []*ast.Field (len = 1) {
    15  .  .  .  .  0: *ast.Field {
    16  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    17  .  .  .  .  .  .  0: *ast.Ident {
    18  .  .  .  .  .  .  .  NamePos: -:4:8
    19  .  .  .  .  .  .  .  Name: "ch"
    20  .  .  .  .  .  .  .  Obj: *ast.Object {
    21  .  .  .  .  .  .  .  .  Kind: var
    22  .  .  .  .  .  .  .  .  Name: "ch"
    23  .  .  .  .  .  .  .  .  Decl: *(obj @ 15)
    24  .  .  .  .  .  .  .  }
    25  .  .  .  .  .  .  }
    26  .  .  .  .  .  }
    27  .  .  .  .  .  Type: *ast.ChanType {
    28  .  .  .  .  .  .  Begin: -:4:11
    29  .  .  .  .  .  .  Arrow: -
    30  .  .  .  .  .  .  Dir: 3
    31  .  .  .  .  .  .  Value: *ast.Ident {
    32  .  .  .  .  .  .  .  NamePos: -:4:16
    33  .  .  .  .  .  .  .  Name: "int"
    34  .  .  .  .  .  .  }
    35  .  .  .  .  .  }
    36  .  .  .  .  }
    37  .  .  .  }
    38  .  .  .  Closing: -:4:19
    39  .  .  }
    40  .  }
    41  .  Body: *ast.BlockStmt {
    42  .  .  Lbrace: -:4:21
    43  .  .  List: []ast.Stmt (len = 1) {
    44  .  .  .  0: *ast.RangeStmt {
    45  .  .  .  .  For: -:5:2
    46  .  .  .  .  Key: *ast.Ident {
    47  .  .  .  .  .  NamePos: -:5:6
    48  .  .  .  .  .  Name: "x"
    49  .  .  .  .  .  Obj: *ast.Object {
    50  .  .  .  .  .  .  Kind: var
    51  .  .  .  .  .  .  Name: "x"
    52  .  .  .  .  .  .  Decl: *ast.AssignStmt {
    53  .  .  .  .  .  .  .  Lhs: []ast.Expr (len = 1) {
    54  .  .  .  .  .  .  .  .  0: *(obj @ 46)
    55  .  .  .  .  .  .  .  }
    56  .  .  .  .  .  .  .  TokPos: -:5:8
    57  .  .  .  .  .  .  .  Tok: :=
    58  .  .  .  .  .  .  .  Rhs: []ast.Expr (len = 1) {
    59  .  .  .  .  .  .  .  .  0: *ast.UnaryExpr {
    60  .  .  .  .  .  .  .  .  .  OpPos: -:5:11
    61  .  .  .  .  .  .  .  .  .  Op: range
    62  .  .  .  .  .  .  .  .  .  X: *ast.Ident {
    63  .  .  .  .  .  .  .  .  .  .  NamePos: -:5:17
    64  .  .  .  .  .  .  .  .  .  .  Name: "ch"
    65  .  .  .  .  .  .  .  .  .  .  Obj: *(obj @ 20)
    66  .  .  .  .  .  .  .  .  .  }
    67  .  .  .  .  .  .  .  .  }
    68  .  .  .  .  .  .  .  }
    69  .  .  .  .  .  .  }
    70  .  .  .  .  .  }
    71  .  .  .  .  }
    72  .  .  .  .  TokPos: -:5:8
    73  .  .  .  .  Tok: :=
    74  .  .  .  .  X: *(obj @ 62)
    75  .  .  .  .  Body: *ast.BlockStmt {
    76  .  .  .  .  .  Lbrace: -:5:20
    77  .  .  .  .  .  Rbrace: -:7:2
    78  .  .  .  .  }
    79  .  .  .  }
    80  .  .  }
    81  .  .  Rbrace: -:8:1
    82  .

As you can see in line 59 we can see there is an UnaryExpr node with Op set to range. That's the node I want to catch.

I tried using this code to walk the ast and only catch that node.

exampleFunc := `
package main

func B(ch chan int) {
    for x := range ch {

    }
}
`
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "-", exampleFunc, parser.ParseComments)
ast.Inspect(file, func(node ast.Node) bool {
    fn, ok := node.(*ast.UnaryExpr) // try to cast
    if !ok {
        return true
    }
        ast.Print(fset, fn)
    return true
})

But it's doesn't seem to work.

Any idea why, printing the AST of the whole FuncDecl, I can see there is an UnaryExpr node, but nothing appear when trying to get that node?

SegFault
  • 59
  • 4
  • 2
    Not sure if this is documented somewhere but if you take a look at the source of `Walk`, which is what `Inspect` delegates to, you'll find that `Ident` is considered by it as something like a leaf node, i.e. any children of `Ident` aren't walked any further, at least not directly through the `Ident` node. (the comment in the source says `nothing to do`). Which means that you'll have to do a bit more work to get to your desired node. For example you could manually dig yourself through from `RangeStmt` to `AssignStmt` and then continue with a second call to `Inspect` to catch your `UnaryExpr`. – mkopriva Nov 11 '19 at 22:56
  • 2
    ... example: https://play.golang.com/p/23wbLSik9wK – mkopriva Nov 11 '19 at 22:56
  • I thought about that, but do you think that would work on all cases? I mean, range over channels? – SegFault Nov 11 '19 at 23:34
  • I'm not sure, you'll have to test it yourself which shouldn't be too hard, I think, as there aren't too many variations to the range over channel expression. – mkopriva Nov 12 '19 at 06:50

1 Answers1

1

As @mkopriva, *ast.Ident nodes are considered as leaf. Thus we need to manually move from RangeStmt to AssignStmt (if there is any) then use inspect again to reach the UnaryExpr

SegFault
  • 59
  • 4