Skip to content

Commit 90f777f

Browse files
committed
occurrences: Replace ismacro Ref plumbing with is_macro0
Remove the `ismacro::Ref{Bool}` keyword from `compute_binding_occurrences` / `!` and compute the same signal at the stage-0 level via a new `is_macro0(st0)` helper (parallel to `is_generated0`). The only production consumer was `analyze_lowered_code!`, where `has_implicit_args` now uses `is_macro0(st0) || is_generated0(st0)` directly. While here, merge the `K"local"` / `K"global"` / `K"function_decl"` decl-recording branches into a single arm and switch `may_record_occurrence!` (renamed from `record_occurrence!`) to return a `Bool` so the decl/def sites can use it as a predicate for advancing `start_idx`.
1 parent 6449a76 commit 90f777f

5 files changed

Lines changed: 38 additions & 67 deletions

File tree

src/analysis/occurrence-analysis.jl

Lines changed: 22 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
compute_binding_occurrences(
33
ctx3::JL.VariableAnalysisContext, st3::Tree3, is_generated::Bool;
4-
ismacro::Union{Nothing,Base.RefValue{Bool}} = nothing
4+
include_global_bindings::Bool = false
55
) where Tree3<:JS.SyntaxTree
66
-> binding_occurrences::Dict{JL.BindingInfo,Set{BindingOccurrence{Tree3}}}
77
@@ -19,7 +19,6 @@ information:
1919
# Arguments
2020
- `ctx3`: Variable analysis context from JuliaLowering containing binding information
2121
- `st3`: Lowered syntax tree (after scope resolution) to analyze
22-
- `ismacro`: Optional mutable reference to track if any function binding is a macro
2322
2423
# Returns
2524
`binding_occurrences` is a dictionary mapping each non-internal local/argument binding to
@@ -34,7 +33,6 @@ a set of `BindingOccurrence` objects that record where and how the binding appea
3433
"""
3534
function compute_binding_occurrences(
3635
ctx3::JL.VariableAnalysisContext, st3::Tree3, is_generated::Bool;
37-
ismacro::Union{Nothing,Base.RefValue{Bool}} = nothing,
3836
include_global_bindings::Bool = false
3937
) where Tree3<:JS.SyntaxTree
4038
occurrences = Dict{JL.BindingInfo,Set{BindingOccurrence{Tree3}}}()
@@ -65,7 +63,7 @@ function compute_binding_occurrences(
6563

6664
isempty(occurrences) && return occurrences
6765

68-
compute_binding_occurrences!(occurrences, ctx3, st3; ismacro, include_global_bindings)
66+
compute_binding_occurrences!(occurrences, ctx3, st3; include_global_bindings)
6967

7068
# In `@generated` functions, arguments are typically used only inside returned
7169
# quoted expressions (`:(...)`) which appear as `inert` nodes after lowering.
@@ -164,30 +162,31 @@ self-recursive calls, which have distinct byte ranges.
164162
"""
165163
const SkipRecording = Dict{JL.BindingInfo,UnitRange{Int}}
166164

167-
function record_occurrence!(occurrences::Dict{JL.BindingInfo,Set{BindingOccurrence{Tree3}}},
165+
function may_record_occurrence!(occurrences::Dict{JL.BindingInfo,Set{BindingOccurrence{Tree3}}},
168166
kind::Symbol, st::Tree3, ctx3::JL.VariableAnalysisContext;
169167
skip_recording::Union{Nothing,SkipRecording} = nothing
170168
) where Tree3<:JS.SyntaxTree
171169
if JS.kind(st) === JS.K"BindingId"
172170
binfo = JL.get_binding(ctx3, st)
173-
record_occurrence!(occurrences, kind, st, binfo; skip_recording)
171+
_may_record_occurrence!(occurrences, kind, st, binfo; skip_recording)
172+
return true
174173
end
175-
return occurrences
174+
return false
176175
end
177176

178-
function record_occurrence!(occurrences::Dict{JL.BindingInfo,Set{BindingOccurrence{Tree3}}},
177+
function _may_record_occurrence!(occurrences::Dict{JL.BindingInfo,Set{BindingOccurrence{Tree3}}},
179178
kind::Symbol, st::Tree3, binfo::JL.BindingInfo;
180179
skip_recording::Union{Nothing,SkipRecording} = nothing
181180
) where Tree3<:JS.SyntaxTree
182-
haskey(occurrences, binfo) || return occurrences
181+
haskey(occurrences, binfo) || return
183182
if !isnothing(skip_recording)
184183
skip_range = get(skip_recording, binfo, nothing)
185184
if skip_range !== nothing && JS.byte_range(st) == skip_range
186-
return occurrences
185+
return
187186
end
188187
end
189188
push!(occurrences[binfo], BindingOccurrence(st, kind))
190-
return occurrences
189+
occurrences
191190
end
192191

193192
is_selffunc(b::JL.BindingInfo) = b.name == "#self#"
@@ -197,46 +196,25 @@ function compute_binding_occurrences!(
197196
occurrences::Dict{JL.BindingInfo,Set{BindingOccurrence{Tree3}}},
198197
ctx3::JL.VariableAnalysisContext, st3::Tree3;
199198
include_global_bindings::Bool = false,
200-
ismacro::Union{Nothing,Base.RefValue{Bool}} = nothing,
201199
skip_recording_uses::Union{Nothing,SkipRecording} = nothing
202200
) where Tree3<:JS.SyntaxTree
203201
stack = JS.SyntaxList(st3)
204202
while !isempty(stack)
205203
st = pop!(stack)
206204
k = JS.kind(st)
207205
nc = JS.numchildren(st)
208-
if k === JS.K"local" || (include_global_bindings && k === JS.K"global")
209-
if nc 1
210-
record_occurrence!(occurrences, :decl, st[1], ctx3)
211-
continue # avoid to recurse to skip recording use
212-
end
213-
end
214-
215206
if k === JS.K"BindingId"
216-
record_occurrence!(occurrences, :use, st, ctx3; skip_recording=skip_recording_uses)
207+
may_record_occurrence!(occurrences, :use, st, ctx3; skip_recording=skip_recording_uses)
217208
end
218209

219210
start_idx = 1
220-
if k === JS.K"function_decl"
221-
if nc 1
222-
func = st[1]
223-
if JS.kind(func) === JS.K"BindingId"
224-
binfo = JL.get_binding(ctx3, func)
225-
record_occurrence!(occurrences, :decl, func, binfo)
226-
if !isnothing(ismacro)
227-
ismacro[] |= startswith(binfo.name, "@")
228-
end
229-
start_idx = 2
230-
end
211+
if k in JS.KSet"local function_decl" || (include_global_bindings && k === JS.K"global")
212+
if nc 1 && may_record_occurrence!(occurrences, :decl, st[1], ctx3)
213+
start_idx = 2 # skip recording use
231214
end
232215
elseif k === JS.K"method_defs" || k === JS.K"constdecl"
233-
if nc 1
234-
local global_binding = st[1]
235-
if JS.kind(global_binding) === JS.K"BindingId"
236-
binfo = JL.get_binding(ctx3, global_binding)
237-
record_occurrence!(occurrences, :def, global_binding, binfo)
238-
start_idx = 2
239-
end
216+
if nc 1 && may_record_occurrence!(occurrences, :def, st[1], ctx3)
217+
start_idx = 2
240218
end
241219
elseif k === JS.K"block" && nc 1 && JS.kind(st[1]) === JS.K"function_decl"
242220
# This block wraps a function definition. Each function's own binding
@@ -266,11 +244,11 @@ function compute_binding_occurrences!(
266244
if !isempty(newly_added)
267245
if isnothing(skip_recording_uses)
268246
compute_binding_occurrences!(occurrences, ctx3, st;
269-
ismacro, skip_recording_uses = SkipRecording(newly_added))
247+
skip_recording_uses = SkipRecording(newly_added))
270248
else
271249
for br in newly_added; push!(skip_recording_uses, br); end
272250
compute_binding_occurrences!(occurrences, ctx3, st;
273-
ismacro, skip_recording_uses)
251+
skip_recording_uses)
274252
for (b, _) in newly_added; delete!(skip_recording_uses, b); end
275253
end
276254
continue
@@ -281,21 +259,21 @@ function compute_binding_occurrences!(
281259
if nc 2
282260
arglist = st[1]
283261
for i = 1:JS.numchildren(arglist)
284-
record_occurrence!(occurrences, :def, arglist[i], ctx3)
262+
may_record_occurrence!(occurrences, :def, arglist[i], ctx3)
285263
end
286264
start_idx = 2
287265
if nc 3
288266
sparamlist = st[2]
289267
for i = 1:JS.numchildren(sparamlist)
290-
record_occurrence!(occurrences, :def, sparamlist[i], ctx3)
268+
may_record_occurrence!(occurrences, :def, sparamlist[i], ctx3)
291269
end
292270
start_idx = 3
293271
end
294272
end
295273
elseif k === JS.K"="
296274
start_idx = 2 # the left hand side, i.e. "definition", does not account for usage
297275
if nc 1
298-
record_occurrence!(occurrences, :def, st[1], ctx3)
276+
may_record_occurrence!(occurrences, :def, st[1], ctx3)
299277
if nc 2
300278
rhs = st[2]
301279
# In struct definitions, `local struct_name` is somehow introduced,
@@ -350,7 +328,7 @@ function compute_binding_occurrences!(
350328
end
351329
end
352330

353-
return occurrences, ismacro
331+
return occurrences
354332
end
355333

356334
function is_matching_global_binding(

src/diagnostic.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,14 +1176,13 @@ function analyze_lowered_code!(
11761176
postprocessor::LSPostProcessor = LSPostProcessor()
11771177
)
11781178
(; ctx3, ctx4, st0, st3) = res
1179-
ismacro = Ref(false)
11801179
binding_occurrences = compute_binding_occurrences(ctx3, st3, is_generated0(st0);
1181-
ismacro, include_global_bindings=true)
1180+
include_global_bindings=true)
1181+
11821182
reported = Set{LoweringDiagnosticKey}() # to prevent duplicate reports for unused default or keyword arguments
11831183
(kwarg_type_names, kwarg_locations) = compute_kwarg_type_annotation_names(st0)
11841184

1185-
has_implicit_args = ismacro[] || is_generated0(st0)
1186-
1185+
has_implicit_args = is_macro0(st0) || is_generated0(st0)
11871186
analyze_unused_bindings!(
11881187
diagnostics, fi, st0, ctx3, binding_occurrences, has_implicit_args, reported,
11891188
kwarg_type_names, kwarg_locations;

src/utils/ast.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ is_mainfunc0(st0::SyntaxTree0) = is_macrocall_st0(st0, "@main")
119119

120120
is_generated0(st0::SyntaxTree0) = is_macrocall_st0(st0, "@generated")
121121

122+
is_macro0(st0::SyntaxTree0) = JS.kind(st0) === JS.K"macro"
123+
122124
# Simple (non-qualified) macro names whose new-style implementations in
123125
# `JuliaLowering/src/syntax_macros.jl` and `src/utils/jl-syntax-macros.jl`
124126
# preserve fine-grained source provenance during expansion. Unlike old-style

test/analysis/test_occurrence_analysis.jl

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,16 @@ module lowering_module end
1212
with_binding_occurrences(callback, code::AbstractString; kwargs...) =
1313
with_binding_occurrences(callback, lowering_module, code; kwargs...)
1414
function with_binding_occurrences(callback, mod::Module, code::AbstractString;
15-
ismacro_callback = nothing,
1615
remove_macrocalls::Bool = false,
1716
is_generated::Bool = false)
1817
st0 = jlparse(code; rule=:statement)
1918
if remove_macrocalls
2019
st0 = JETLS.remove_macrocalls(st0)
2120
end
2221
(; ctx3, st3) = JETLS.jl_lower_for_scope_resolution(mod, st0)
23-
ismacro = isnothing(ismacro_callback) ? nothing : Ref(false)
24-
binding_occurrences = JETLS.compute_binding_occurrences(ctx3, st3, is_generated; ismacro)
25-
if !isnothing(ismacro_callback)
26-
ismacro_callback(ismacro)
27-
end
22+
binding_occurrences = JETLS.compute_binding_occurrences(ctx3, st3, is_generated)
2823
callback(binding_occurrences)
2924
end
30-
nomacro_callback(ismacro) = @test !ismacro[]
31-
ismacro_callback(ismacro) = @test ismacro[]
3225

3326
@testset "compute_binding_occurrences" begin
3427
with_binding_occurrences("""
@@ -37,7 +30,7 @@ ismacro_callback(ismacro) = @test ismacro[]
3730
println(x)
3831
return y
3932
end
40-
"""; ismacro_callback = nomacro_callback) do binding_occurrences
33+
""") do binding_occurrences
4134
binfos = collect(keys(binding_occurrences))
4235
@test length(binfos) == 4
4336
let i = @something findfirst(binfo->binfo.name=="x", binfos)
@@ -92,7 +85,7 @@ ismacro_callback(ismacro) = @test ismacro[]
9285
macro m(x, y)
9386
return Expr(:block, __source__, esc(x))
9487
end
95-
"""; ismacro_callback = ismacro_callback) do binding_occurrences
88+
""") do binding_occurrences
9689
binfos = collect(keys(binding_occurrences))
9790
@test length(binfos) == 4
9891
let i = @something findfirst(binfo->binfo.name=="x", binfos)
@@ -148,7 +141,7 @@ ismacro_callback(ismacro) = @test ismacro[]
148141
function func1(::TTT1) where TTT1<:Integer
149142
return zero(TTT1)
150143
end
151-
"""; ismacro_callback = nomacro_callback) do binding_occurrences
144+
""") do binding_occurrences
152145
binfos = collect(keys(binding_occurrences))
153146
# there are two different bindings representing the `TTT1`, one for defineing the
154147
# signature type and the other for static parameter binding within the method body
@@ -175,7 +168,7 @@ ismacro_callback(ismacro) = @test ismacro[]
175168
end
176169
"""
177170
for code in (code1, code2)
178-
with_binding_occurrences(code; ismacro_callback = nomacro_callback) do binding_occurrences
171+
with_binding_occurrences(code) do binding_occurrences
179172
binfos = collect(keys(binding_occurrences))
180173
# there are two different bindings representing each for `TTT1` and `TTT2`,
181174
# one for defineing the signature type and the other for static parameter binding within the method body
@@ -214,7 +207,7 @@ ismacro_callback(ismacro) = @test ismacro[]
214207
end
215208
println(xxx)
216209
end
217-
"""; ismacro_callback = nomacro_callback) do binding_occurrences
210+
""") do binding_occurrences
218211
binfos = collect(keys(binding_occurrences))
219212
idxs = findall(binfo->binfo.name=="xxx", binfos)
220213
@test length(idxs) == 2
@@ -240,7 +233,7 @@ ismacro_callback(ismacro) = @test ismacro[]
240233
function func(xxx::TTT)::Float64 where TTT<:Integer
241234
return sin(xxx)
242235
end
243-
"""; ismacro_callback = nomacro_callback) do binding_occurrences
236+
""") do binding_occurrences
244237
binfos = collect(keys(binding_occurrences))
245238
idx = only(findall(binfo->binfo.name=="xxx", binfos))
246239
occurrences = binding_occurrences[binfos[idx]]
@@ -253,23 +246,23 @@ ismacro_callback(ismacro) = @test ismacro[]
253246
end
254247

255248
@testset "keyword arguments" begin
256-
with_binding_occurrences("func(a; kw) = kw"; ismacro_callback = nomacro_callback) do binding_occurrences
249+
with_binding_occurrences("func(a; kw) = kw") do binding_occurrences
257250
@test !any(binding_occurrences) do (binding, occurrences)
258251
binding.name == "a" && binding.kind === :argument && any(o->o.kind===:use, occurrences)
259252
end
260253
@test any(binding_occurrences) do (binding, occurrences)
261254
binding.name == "kw" && binding.kind === :argument && any(o->o.kind===:use, occurrences)
262255
end
263256
end
264-
with_binding_occurrences("func(a; kw) = a"; ismacro_callback = nomacro_callback) do binding_occurrences
257+
with_binding_occurrences("func(a; kw) = a") do binding_occurrences
265258
@test any(binding_occurrences) do (binding, occurrences)
266259
binding.name == "a" && binding.kind === :argument && any(o->o.kind===:use, occurrences)
267260
end
268261
@test !any(binding_occurrences) do (binding, occurrences)
269262
binding.name == "kw" && binding.kind === :argument && any(o->o.kind===:use, occurrences)
270263
end
271264
end
272-
with_binding_occurrences("func(a; kw) = nothing"; ismacro_callback = nomacro_callback) do binding_occurrences
265+
with_binding_occurrences("func(a; kw) = nothing") do binding_occurrences
273266
@test !any(binding_occurrences) do (binding, occurrences)
274267
binding.name == "a" && binding.kind === :argument && any(o->o.kind===:use, occurrences)
275268
end

test/utils/test_jl_syntax_macros.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,8 @@ end
254254
world = Base.get_world_counter()
255255
res = JETLS.jl_lower_for_scope_resolution(lowering_module, st0, world;
256256
recover_from_macro_errors=false, convert_closures=true)
257-
ismacro = Ref(false)
258257
binding_occurrences = JETLS.compute_binding_occurrences(
259-
res.ctx3, res.st3, false; ismacro, include_global_bindings=true)
258+
res.ctx3, res.st3, false; include_global_bindings=true)
260259

261260
xxx_binfo = nothing
262261
for (binfo, _) in binding_occurrences

0 commit comments

Comments
 (0)