Skip to content

Commit 6c499d0

Browse files
authored
Extract and filter check macros instead of relying on expansion. (#2)
This prevents check macros changing the AST seen by other macros.
1 parent fb23681 commit 6c499d0

File tree

3 files changed

+75
-66
lines changed

3 files changed

+75
-66
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "FileCheck"
22
uuid = "4e644321-382b-4b05-b0b6-5d23c3d944fb"
33
authors = ["Tim Besard <tim.besard@gmail.com>"]
4-
version = "1.1"
4+
version = "1.1.1"
55

66
[deps]
77
LLVM_jll = "86de99a1-58d6-5da7-8064-bd56ce2e322c"

src/FileCheck.jl

Lines changed: 59 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ module FileCheck
33
import LLVM_jll
44

55
export @filecheck
6-
export @check, @check_label, @check_next, @check_same,
7-
@check_not, @check_dag, @check_empty, @check_count
86

97
global filecheck_path::String
108
function __init__()
@@ -139,71 +137,67 @@ function parse_kwargs(args)
139137
return kwargs
140138
end
141139

142-
# collect checks used in the @filecheck block by piggybacking on macro expansion
143-
const checks = Tuple{Any,Bool,String,Any}[]
144-
145-
macro check(args...)
146-
kwargs = parse_kwargs(args[1:end-1])
147-
cond = get(kwargs, :cond, nothing)
148-
literal = get(kwargs, :literal, false)
149-
push!(checks, (cond, literal, "CHECK", args[end]))
150-
nothing
151-
end
152-
153-
macro check_label(args...)
154-
kwargs = parse_kwargs(args[1:end-1])
155-
cond = get(kwargs, :cond, nothing)
156-
literal = get(kwargs, :literal, false)
157-
push!(checks, (cond, literal, "CHECK-LABEL", args[end]))
158-
nothing
159-
end
160-
161-
macro check_next(args...)
162-
kwargs = parse_kwargs(args[1:end-1])
163-
cond = get(kwargs, :cond, nothing)
164-
literal = get(kwargs, :literal, false)
165-
push!(checks, (cond, literal, "CHECK-NEXT", args[end]))
166-
nothing
140+
# Generate @check* macros that can only be used inside @filecheck blocks
141+
const CHECK_MACROS = Dict{Symbol,String}(
142+
Symbol("@check") => "CHECK",
143+
Symbol("@check_label") => "CHECK-LABEL",
144+
Symbol("@check_next") => "CHECK-NEXT",
145+
Symbol("@check_same") => "CHECK-SAME",
146+
Symbol("@check_not") => "CHECK-NOT",
147+
Symbol("@check_dag") => "CHECK-DAG",
148+
Symbol("@check_empty") => "CHECK-EMPTY",
149+
Symbol("@check_count") => "CHECK-COUNT",
150+
)
151+
for (macro_sym, _) in CHECK_MACROS
152+
name = Symbol(string(macro_sym)[2:end]) # strip leading @
153+
@eval begin
154+
export $(Symbol("@", name))
155+
macro $name(args...)
156+
error($(string("@", name, " can only be used inside a @filecheck block")))
157+
end
158+
end
167159
end
168160

169-
macro check_same(args...)
170-
kwargs = parse_kwargs(args[1:end-1])
171-
cond = get(kwargs, :cond, nothing)
172-
literal = get(kwargs, :literal, false)
173-
push!(checks, (cond, literal, "CHECK-SAME", args[end]))
174-
nothing
175-
end
161+
# Walk the AST recursively, collecting @check* macrocall nodes and removing them from blocks.
162+
function extract_checks!(ex, collected::Vector{Tuple{Any,Bool,String,Any}})
163+
ex isa Expr || return ex
176164

177-
macro check_not(args...)
178-
kwargs = parse_kwargs(args[1:end-1])
179-
cond = get(kwargs, :cond, nothing)
180-
literal = get(kwargs, :literal, false)
181-
push!(checks, (cond, literal, "CHECK-NOT", args[end]))
182-
nothing
183-
end
184-
185-
macro check_dag(args...)
186-
kwargs = parse_kwargs(args[1:end-1])
187-
cond = get(kwargs, :cond, nothing)
188-
literal = get(kwargs, :literal, false)
189-
push!(checks, (cond, literal, "CHECK-DAG", args[end]))
190-
nothing
191-
end
165+
# Recurse into sub-expressions first (depth-first to collect in source order)
166+
for i in eachindex(ex.args)
167+
ex.args[i] = extract_checks!(ex.args[i], collected)
168+
end
192169

193-
macro check_empty(args...)
194-
kwargs = parse_kwargs(args[1:end-1])
195-
cond = get(kwargs, :cond, nothing)
196-
literal = get(kwargs, :literal, false)
197-
push!(checks, (cond, literal, "CHECK-EMPTY", args[end]))
198-
nothing
170+
# Strip @check* calls from blocks
171+
if Meta.isexpr(ex, :block)
172+
filter!(ex.args) do arg
173+
if Meta.isexpr(arg, :macrocall) && haskey(CHECK_MACROS, arg.args[1])
174+
collect_check!(arg, collected)
175+
return false
176+
end
177+
true
178+
end
179+
end
180+
return ex
199181
end
200182

201-
macro check_count(args...)
202-
kwargs = parse_kwargs(args[1:end-2])
203-
cond = get(kwargs, :cond, nothing)
204-
literal = get(kwargs, :literal, false)
205-
push!(checks, (cond, literal, "CHECK-COUNT-$(args[end-1])", args[end]))
206-
nothing
183+
# Extract a single check directive from a raw :macrocall AST node.
184+
# Raw AST args: [macro_sym, LineNumberNode, real_args...]
185+
function collect_check!(ex, collected)
186+
macro_sym = ex.args[1]::Symbol
187+
check_type = CHECK_MACROS[macro_sym]
188+
# Skip macro name and LineNumberNodes
189+
real_args = [a for a in ex.args[2:end] if !(a isa LineNumberNode)]
190+
if macro_sym === Symbol("@check_count")
191+
kwargs = parse_kwargs(real_args[1:end-2])
192+
cond = get(kwargs, :cond, nothing)
193+
literal = get(kwargs, :literal, false)
194+
push!(collected, (cond, literal, "CHECK-COUNT-$(real_args[end-1])", real_args[end]))
195+
else
196+
kwargs = parse_kwargs(real_args[1:end-1])
197+
cond = get(kwargs, :cond, nothing)
198+
literal = get(kwargs, :literal, false)
199+
push!(collected, (cond, literal, check_type, real_args[end]))
200+
end
207201
end
208202

209203
"""
@@ -284,12 +278,12 @@ macro filecheck(args...)
284278
ex = args[end]
285279
macro_kwargs = args[1:end-1]
286280

287-
ex = Base.macroexpand(__module__, ex)
288-
if isempty(checks)
281+
collected = Tuple{Any,Bool,String,Any}[]
282+
ex = extract_checks!(ex, collected)
283+
if isempty(collected)
289284
error("No checks provided within the @filecheck macro block")
290285
end
291-
collected = copy(checks)
292-
empty!(checks)
286+
ex = Base.macroexpand(__module__, ex)
293287

294288
# Build runtime code to conditionally collect check lines
295289
stmts = Expr[:(local _checks = String[])]

test/runtests.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,4 +381,19 @@ end
381381
end
382382
end
383383

384+
@testset "@check outside @filecheck errors" begin
385+
@test_throws ErrorException @macroexpand @check "hello"
386+
@test_throws ErrorException @macroexpand @check_next "hello"
387+
@test_throws ErrorException @macroexpand @check_count 3 "hello"
388+
end
389+
390+
@testset "@check does not emit values" begin
391+
# @check used to expand to nothing, interferring with returned values and other macros
392+
@test @filecheck begin
393+
42
394+
@check "42"
395+
@check_not "error"
396+
end
397+
end
398+
384399
end # @testset "FileCheck"

0 commit comments

Comments
 (0)