Skip to content

Commit ee86584

Browse files
authored
Tools for automatic test case reduction (#61)
Adds a few tools for verbose debug lowering and automatic test case reduction.
1 parent fd48002 commit ee86584

File tree

2 files changed

+152
-25
lines changed

2 files changed

+152
-25
lines changed

test/demo.jl

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using JuliaSyntax
44
using JuliaLowering
55

6-
using JuliaLowering: SyntaxGraph, SyntaxTree, ensure_attributes!, ensure_attributes, newnode!, setchildren!, is_leaf, children, child, setattr!, sourceref, makenode, sourcetext, showprov, lookup_binding
6+
using JuliaLowering: SyntaxGraph, SyntaxTree, ensure_attributes!, ensure_attributes, newnode!, setchildren!, is_leaf, @ast, numchildren, children, child, setattr!, sourceref, makenode, sourcetext, showprov, lookup_binding
77

88
using JuliaSyntaxFormatter
99

@@ -32,6 +32,37 @@ function formatsrc(ex; kws...)
3232
Text(JuliaSyntaxFormatter.formatsrc(ex; kws...))
3333
end
3434

35+
function debug_lower(mod, ex; expr_compat_mode=false, verbose=false, do_eval=false)
36+
ctx1, ex_macroexpand = JuliaLowering.expand_forms_1(mod, ex, expr_compat_mode)
37+
38+
verbose && @info "Macro expanded" formatsrc(ex_macroexpand, color_by=:scope_layer)
39+
40+
ctx2, ex_desugar = JuliaLowering.expand_forms_2(ctx1, ex_macroexpand)
41+
verbose && @info "Desugared" formatsrc(ex_desugar, color_by=:scope_layer)
42+
43+
ctx3, ex_scoped = JuliaLowering.resolve_scopes(ctx2, ex_desugar)
44+
verbose && @info "Resolved scopes" formatsrc(ex_scoped, color_by=e->var_kind(ctx2,e))
45+
46+
ctx4, ex_converted = JuliaLowering.convert_closures(ctx3, ex_scoped)
47+
verbose && @info "Closure converted" formatsrc(ex_converted, color_by=:var_id)
48+
49+
ctx5, ex_compiled = JuliaLowering.linearize_ir(ctx4, ex_converted)
50+
verbose && @info "Linear IR" formatsrc(ex_compiled, color_by=:var_id) Text(sprint(JuliaLowering.print_ir, ex_compiled))
51+
52+
ex_expr = JuliaLowering.to_lowered_expr(mod, ex_compiled)
53+
verbose && @info "CodeInfo" ex_expr
54+
55+
if do_eval
56+
eval_result = Base.eval(mod, ex_expr)
57+
verbose && @info "Eval" eval_result
58+
else
59+
eval_result = nothing
60+
end
61+
62+
(ctx1, ex_macroexpand, ctx2, ex_desugar, ctx3, ex_scoped, ctx4, ex_converted, ctx5, ex_compiled, ex_expr, eval_result)
63+
end
64+
65+
3566
# Currently broken - need to push info back onto src
3667
# function annotate_scopes(mod, ex)
3768
# ex = ensure_attributes(ex, var_id=Int)
@@ -853,31 +884,28 @@ end
853884
"""
854885

855886
ex = parsestmt(SyntaxTree, src, filename="foo.jl")
856-
ex = ensure_attributes(ex, var_id=Int)
887+
#ex = ensure_attributes(ex, var_id=Int)
857888
#ex = softscope_test(ex)
858889
@info "Input code" formatsrc(ex)
859890

860-
in_mod = M
861-
# in_mod=Main
862-
ctx1, ex_macroexpand = JuliaLowering.expand_forms_1(in_mod, ex, false)
863-
@info "Macro expanded" formatsrc(ex_macroexpand, color_by=:scope_layer)
864-
#@info "Macro expanded" formatsrc(ex_macroexpand, color_by=e->JuliaLowering.flattened_provenance(e)[1:end-1])
865-
866-
ctx2, ex_desugar = JuliaLowering.expand_forms_2(ctx1, ex_macroexpand)
867-
@info "Desugared" formatsrc(ex_desugar, color_by=:scope_layer)
868-
869-
ctx3, ex_scoped = JuliaLowering.resolve_scopes(ctx2, ex_desugar)
870-
@info "Resolved scopes" formatsrc(ex_scoped, color_by=e->var_kind(ctx2,e))
871-
872-
ctx4, ex_converted = JuliaLowering.convert_closures(ctx3, ex_scoped)
873-
@info "Closure converted" formatsrc(ex_converted, color_by=:var_id)
874-
875-
ctx5, ex_compiled = JuliaLowering.linearize_ir(ctx4, ex_converted)
876-
@info "Linear IR" formatsrc(ex_compiled, color_by=:var_id) Text(sprint(JuliaLowering.print_ir, ex_compiled))
877-
878-
ex_expr = JuliaLowering.to_lowered_expr(in_mod, ex_compiled)
879-
@info "CodeInfo" ex_expr
891+
(ctx1, ex_macroexpand,
892+
ctx2, ex_desugar,
893+
ctx3, ex_scoped,
894+
ctx4, ex_converted,
895+
ctx5, ex_compiled,
896+
ex_expr, eval_result) = debug_lower(M, ex; verbose=true)
897+
898+
# Automatic test reduction
899+
# bad = reduce_any_failing_toplevel(JuliaLowering, joinpath(@__DIR__, "../src/desugaring.jl"))
900+
# if !isnothing(bad)
901+
# @error "Reduced expression as code" formatsrc(bad)
902+
# write("bad.jl", JuliaSyntaxFormatter.formatsrc(bad))
903+
# end
880904

881-
eval_result = Base.eval(in_mod, ex_expr)
882-
@info "Eval" eval_result
905+
# Old lowering
906+
# text = read(joinpath(@__DIR__, "../src/desugaring.jl"), String)
907+
# ex = parseall(SyntaxTree, text, filename="desugaring.jl")
908+
# for e in Meta.parseall(text).args
909+
# Meta.lower(JuliaLowering, e)
910+
# end
883911

test/utils.jl

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ using JuliaLowering:
1818
makenode, makeleaf, setattr!, sethead!,
1919
is_leaf, numchildren, children,
2020
@ast, flattened_provenance, showprov, LoweringError, MacroExpansionError,
21-
syntax_graph, Bindings, ScopeLayer
21+
syntax_graph, Bindings, ScopeLayer, mapchildren
2222

2323
function _ast_test_graph()
2424
graph = SyntaxGraph()
@@ -285,3 +285,102 @@ function docstrings_equal(d1, d2; debug=true)
285285
end
286286
docstrings_equal(d1::Docs.DocStr, d2) = docstrings_equal(Docs.parsedoc(d1), d2)
287287

288+
#-------------------------------------------------------------------------------
289+
# Tools for test case reduction
290+
291+
function block_reduction_1(is_lowering_error::Function, orig_ex::ST, ex::ST,
292+
curr_path = Int[]) where {ST <: SyntaxTree}
293+
if !is_leaf(ex)
294+
if kind(ex) == K"block"
295+
for i in 1:numchildren(ex)
296+
trial_ex = delete_block_child(orig_ex, orig_ex, curr_path, i)
297+
if is_lowering_error(trial_ex)
298+
# @info "Reduced expression" curr_path i
299+
return trial_ex
300+
end
301+
end
302+
end
303+
for (i,e) in enumerate(children(ex))
304+
push!(curr_path, i)
305+
res = block_reduction_1(is_lowering_error, orig_ex, e, curr_path)
306+
if !isnothing(res)
307+
return res
308+
end
309+
pop!(curr_path)
310+
end
311+
end
312+
return nothing
313+
end
314+
315+
# Find children of all `K"block"`s in an expression and try deleting them while
316+
# preserving the invariant `is_lowering_error(reduced) == true`.
317+
function block_reduction(is_lowering_error, ex)
318+
reduced = ex
319+
was_reduced = false
320+
while true
321+
r = block_reduction_1(is_lowering_error, reduced, reduced)
322+
if isnothing(r)
323+
return (reduced, was_reduced)
324+
end
325+
reduced = r
326+
was_reduced = true
327+
end
328+
end
329+
330+
function delete_block_child(ctx, ex, block_path, child_idx, depth=1)
331+
if depth > length(block_path)
332+
cs = copy(children(ex))
333+
deleteat!(cs, child_idx)
334+
@ast ctx ex [ex cs...]
335+
else
336+
j = block_path[depth]
337+
mapchildren(ctx, ex, j:j) do e
338+
delete_block_child(ctx, e, block_path, child_idx, depth+1)
339+
end
340+
end
341+
end
342+
343+
function throws_lowering_exc(mod, ex)
344+
try
345+
debug_lower(mod, ex)
346+
return false
347+
catch exc
348+
if exc isa LoweringError
349+
return true
350+
else
351+
rethrow()
352+
end
353+
end
354+
end
355+
356+
# Parse a file and lower the top level expression one child at a time, finding
357+
# any top level statement that fails lowering and producing a partially reduced
358+
# test case.
359+
function reduce_any_failing_toplevel(mod, filename; do_eval=false)
360+
text = read(filename, String)
361+
ex0 = parseall(SyntaxTree, text; filename)
362+
for ex in children(ex0)
363+
try
364+
ex_compiled = JuliaLowering.lower(mod, ex)
365+
ex_expr = JuliaLowering.to_lowered_expr(mod, ex_compiled)
366+
if do_eval
367+
Base.eval(mod, ex_expr)
368+
end
369+
catch exc
370+
@error "Failure lowering code" ex
371+
if !(exc isa LoweringError)
372+
rethrow()
373+
end
374+
(reduced,was_reduced) = block_reduction(e->throws_lowering_exc(mod,e), ex)
375+
if !was_reduced
376+
@info "No reduction possible"
377+
return ex
378+
else
379+
@info "Reduced code" reduced
380+
return reduced
381+
end
382+
end
383+
end
384+
nothing
385+
end
386+

0 commit comments

Comments
 (0)