Removing LineNumberNodes when displaying expressions with macros

I’m trying to define a function to filter LineNumberNodes from an expression to pretty-print it without annotations:

filterLNN(x) = deepcopy(x)

function filterLNN(e::Expr)
    Expr(e.head, filterLNN.(filter(x->!isa(x, LineNumberNode), e.args))...)
end
julia> quote
         let acc = 1
           for i in 1:3
             acc += i
           end
           acc
         end
       end |> filterLNN

quote
    let acc = 1
        for i = 1:3
            acc += i
        end
        acc
    end
end

So far, so good, but my problem is that macro calls in the expression aren’t displayed anymore:

julia> quote
         let acc = 1
           @simd for i in 1:3
             acc += i
           end
           acc
         end
       end |> filterLNN

quote
    let acc = 1
        @simd
        acc
    end
end

I can display macro calls again if I leave at least their “accompanying” LineNumberNode in the expression:

function filterLNN(e::Expr)
    if e.head == :macrocall
        Expr(e.head, e.args[1:2]..., filterLNN.(filter(x->!isa(x, LineNumberNode), e.args[3:end]))...)
    else
        Expr(e.head, filterLNN.(filter(x->!isa(x, LineNumberNode), e.args))...)
    end
end
julia> quote
         let acc = 1
           @simd for i in 1:3
             acc += i
           end
           acc
         end
       end |> filterLNN

quote
    let acc = 1
        #= REPL[26]:3 =# @simd for i = 1:3
                acc += i
            end
        acc
    end
end

Is this expected behavior? Or should I look for a bug in the implementation of display for macro calls?

There is MacroTools.rmlines which doesn’t actually seem to work on your quote. My hack was here simply to regex the string version… but surely there is a nicer way.

1 Like

You can also use my tool SyntaxTree.linefilter! from SyntaxTree.jl for that

julia> using SyntaxTree

julia> quote
           x = 1
           y = x+2
       end
quote
    #= REPL[2]:2 =#
    x = 1
    #= REPL[2]:3 =#
    y = x + 2
end

julia> linefilter!(ans)
quote
    x = 1
    y = x + 2
end

The linefilter! method is faster than MacroTools because it is recursively mutating

Thanks! It looks like rmlines removes only one level of LineNumberNodes; striplines is recursive. Both these seem to handle macro calls like in my implementation, but at least it’s an improvement to use an already written implementation instead of having to write mine.

Thanks. I’ll consider doing that too if I don’t find a nicer way.

1 Like

Thanks, but this function does not seem to handle macro calls in any special way, and therefore produces the same output as my first attempt above:

julia> quote
           let acc = 1
               @simd for i in 1:3
                   acc += i
               end
               acc
           end
       end |> SyntaxTree.linefilter

quote
    let acc = 1
        @simd
        acc
    end
end

Okay, I didnt test that, I will fix it later today since that’s a bug for me.

It is fixed in the master branch now, it can handle it correctly

julia> quote
           let acc = 1
               @simd for i in 1:3
                   acc += i
               end
               acc
           end
       end |> SyntaxTree.linefilter!

quote
    let acc = 1
        @simd for i = 1:3
                acc += i
            end
        acc
    end
end

The problem is that :macrocall expressions now require a LineNumberNode or something to fill its place, so the solution is to simply put nothing in its place

This bug fix will be tagged as an updated release today

1 Like

I realize this is solved but for sport I figured I’ll write my own solution of linefilter! also using recursion, only tested in 1.7.1, maybe someone stumbling across this thread years later will find it useful:

Code:

macro unquote(expr::Expr)

        function aux!(args::Vector{Any}) ::Nothing

            to_delete = Vector{Integer}()
            for (i, x) in enumerate(args)
                if x isa LineNumberNode
                    push!(to_delete, i)
                elseif x isa Expr
                    aux!(x.args)
                end
            end

            n_deleted = 0;
            for i in to_delete
                deleteat!(args, i - n_deleted)
                n_deleted += 1
            end
        end

        aux!(expr.args)
        return Expr(expr.head, :($(expr.args...)))
    end

Usage:

julia> quoted = quote module m end; struct s end; i = 1; end
quote
    #= REPL[15]:1 =#
    module m
    #= REPL[15]:1 =#
    #= REPL[15]:1 =#
    end
    #= REPL[15]:1 =#
    struct s
        #= REPL[15]:1 =#
    end
    #= REPL[15]:1 =#
    i = 1
end
julia> unquoted = @unquote quote module m end; struct s end; i = 1; end
quote
    module m
    end
    struct s
    end
    i = 1
end

It is part of jluna and can be used freely