Skip to content

Commit 625e8c7

Browse files
authored
[JuliaLowering] Fix placeholders in parameters and decls, work around hasmethod (#60140)
Moved from JuliaLang/JuliaLowering.jl#99 ---- - Fix placeholders in positional and keyword arguments. - Placeholder positional arguments sometimes need to be read from due to the way we desugar keyword functions; give them an internal name. (fix https://github.com/c42f/JuliaLowering.jl/issue/55, port #58803) - Positional arguments may contain duplicate placeholders - Keywords may be placeholders (which is strange) but may not be duplicates - `kws...` may use a placeholder (fix https://github.com/c42f/JuliaLowering.jl/issue/49) - Allow placeholders in decls: `local`/`global _`, `local`/`global _::T`. This can be produced by destructuring assignments. I've implemented this to always drop the type (see #57497), but this may not be what we want. - Work around `hasmethod` always returning false for `world=typemax(UInt)` (some discussion [here](#59808)). I'm not sure we should be using `hasmethod` in the first place. - Comment out our `@atomic` implementation. We need to implement this for all forms that the normal `@atomic` takes, otherwise it hijacks the expansion and expects a simple form.
1 parent 4060c45 commit 625e8c7

File tree

11 files changed

+135
-47
lines changed

11 files changed

+135
-47
lines changed

JuliaLowering/src/desugaring.jl

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,8 +1357,10 @@ function expand_assignment(ctx, ex, is_const=false)
13571357
# Identifier in lhs[1] is a variable type declaration, eg
13581358
# x::T = rhs
13591359
@ast ctx ex [K"block"
1360-
[K"decl" lhs[1] lhs[2]]
1361-
is_const ? [K"const" [K"=" lhs[1] rhs]] : [K"=" lhs[1] rhs]
1360+
if kind(x) !== K"Placeholder"
1361+
[K"decl" x T]
1362+
end
1363+
is_const ? [K"const" [K"=" x rhs]] : [K"=" x rhs]
13621364
]
13631365
else
13641366
# Otherwise just a type assertion, eg
@@ -2171,8 +2173,17 @@ function make_lhs_decls(ctx, stmts, declkind, declmeta, ex, type_decls=true)
21712173
if type_decls
21722174
@chk numchildren(ex) == 2
21732175
name = ex[1]
2174-
@chk kind(name) == K"Identifier"
2175-
push!(stmts, makenode(ctx, ex, K"decl", name, ex[2]))
2176+
if kind(name) == K"Identifier"
2177+
push!(stmts, makenode(ctx, ex, K"decl", name, ex[2]))
2178+
else
2179+
# TODO: Currently, this ignores the LHS in `_::T = val`.
2180+
# We should probably do one of the following:
2181+
# - Throw a LoweringError if that's not too breaking
2182+
# - `convert(T, rhs)::T` and discard the result which is what
2183+
# `x::T = rhs` would do if x is never used again.
2184+
@chk kind(name) == K"Placeholder"
2185+
return
2186+
end
21762187
end
21772188
make_lhs_decls(ctx, stmts, declkind, declmeta, ex[1], type_decls)
21782189
elseif k == K"tuple" || k == K"parameters"
@@ -2198,7 +2209,7 @@ function expand_decls(ctx, ex)
21982209
# expand_assignment will create the type decls
21992210
make_lhs_decls(ctx, stmts, declkind, declmeta, binding[1], false)
22002211
push!(stmts, expand_assignment(ctx, binding))
2201-
elseif is_sym_decl(binding) || kind(binding) == K"Value"
2212+
elseif is_sym_decl(binding) || kind(binding) in (K"Value", K"Placeholder")
22022213
make_lhs_decls(ctx, stmts, declkind, declmeta, binding, true)
22032214
elseif kind(binding) == K"function"
22042215
make_lhs_decls(ctx, stmts, declkind, declmeta, binding[1], false)
@@ -2260,7 +2271,7 @@ end
22602271
#-------------------------------------------------------------------------------
22612272
# Expansion of function definitions
22622273

2263-
function expand_function_arg(ctx, body_stmts, arg, is_last_arg, is_kw)
2274+
function expand_function_arg(ctx, body_stmts, arg, is_last_arg, is_kw, arg_id)
22642275
ex = arg
22652276

22662277
if kind(ex) == K"="
@@ -2311,7 +2322,16 @@ function expand_function_arg(ctx, body_stmts, arg, is_last_arg, is_kw)
23112322
K"local"(meta=CompileHints(:is_destructured_arg, true))
23122323
[K"=" ex name]
23132324
])
2314-
elseif k == K"Identifier" || k == K"Placeholder"
2325+
elseif k == K"Placeholder"
2326+
# Lowering should be able to use placeholder args as rvalues internally,
2327+
# e.g. for kw method dispatch. Duplicate positional placeholder names
2328+
# should be allowed.
2329+
name = if is_kw
2330+
@ast ctx ex ex=>K"Identifier"
2331+
else
2332+
new_local_binding(ctx, ex, "#arg$(string(arg_id))#"; kind=:argument)
2333+
end
2334+
elseif k == K"Identifier"
23152335
name = ex
23162336
else
23172337
throw(LoweringError(ex, is_kw ? "Invalid keyword name" : "Invalid function argument"))
@@ -2649,7 +2669,7 @@ function keyword_function_defs(ctx, srcref, callex_srcref, name_str, typevar_nam
26492669
kwtmp = new_local_binding(ctx, keywords, "kwtmp")
26502670
for (i,arg) in enumerate(children(keywords))
26512671
(aname, atype, default, is_slurp) =
2652-
expand_function_arg(ctx, nothing, arg, i == numchildren(keywords), true)
2672+
expand_function_arg(ctx, nothing, arg, i == numchildren(keywords), true, i)
26532673
push!(kw_names, aname)
26542674
name_sym = @ast ctx aname aname=>K"Symbol"
26552675
push!(body_arg_names, aname)
@@ -3034,8 +3054,8 @@ function expand_function_def(ctx, ex, docs, rewrite_call=identity, rewrite_body=
30343054
first_default = 0 # index into arg_names/arg_types
30353055
arg_defaults = SyntaxList(ctx)
30363056
for (i,arg) in enumerate(args)
3037-
(aname, atype, default, is_slurp) = expand_function_arg(ctx, body_stmts, arg,
3038-
i == length(args), false)
3057+
(aname, atype, default, is_slurp) =
3058+
expand_function_arg(ctx, body_stmts, arg, i == length(args), false, i)
30393059
has_slurp |= is_slurp
30403060
push!(arg_names, aname)
30413061

@@ -3256,8 +3276,8 @@ function expand_opaque_closure(ctx, ex)
32563276
body_stmts = SyntaxList(ctx)
32573277
is_va = false
32583278
for (i, arg) in enumerate(children(args))
3259-
(aname, atype, default, is_slurp) = expand_function_arg(ctx, body_stmts, arg,
3260-
i == numchildren(args), false)
3279+
(aname, atype, default, is_slurp) =
3280+
expand_function_arg(ctx, body_stmts, arg, i == numchildren(args), false, i)
32613281
is_va |= is_slurp
32623282
push!(arg_names, aname)
32633283
push!(arg_types, atype)

JuliaLowering/src/macro_expansion.jl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,13 @@ function expand_macro(ctx, ex)
270270
# age changes concurrently.
271271
#
272272
# TODO: Allow this to be passed in
273-
if hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}; world=ctx.macro_world)
273+
# TODO: hasmethod always returns false for our `typemax(UInt)` meaning
274+
# "latest world," which we shouldn't be using.
275+
has_new_macro = ctx.macro_world === typemax(UInt) ?
276+
hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}) :
277+
hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}; world=ctx.macro_world)
278+
279+
if has_new_macro
274280
macro_args = prepare_macro_args(ctx, mctx, raw_args)
275281
expanded = try
276282
Base.invoke_in_world(ctx.macro_world, macfunc, macro_args...)
@@ -385,16 +391,15 @@ function expand_forms_1(ctx::MacroExpansionContext, ex::SyntaxTree)
385391
k = kind(ex)
386392
if k == K"Identifier"
387393
name_str = ex.name_val
388-
if all(==('_'), name_str)
389-
@ast ctx ex ex=>K"Placeholder"
390-
elseif is_ccall_or_cglobal(name_str)
394+
if is_ccall_or_cglobal(name_str)
391395
# Lower special identifiers `cglobal` and `ccall` to `K"core"`
392396
# pseudo-refs very early so that cglobal and ccall can never be
393397
# turned into normal bindings (eg, assigned to)
394398
@ast ctx ex name_str::K"core"
395399
else
396-
layerid = get(ex, :scope_layer, current_layer_id(ctx))
397-
makeleaf(ctx, ex, ex, kind=K"Identifier", scope_layer=layerid)
400+
k = all(==('_'), name_str) ? K"Placeholder" : K"Identifier"
401+
scope_layer = get(ex, :scope_layer, current_layer_id(ctx))
402+
makeleaf(ctx, ex, ex; kind=k, scope_layer)
398403
end
399404
elseif k == K"StrMacroName" || k == K"CmdMacroName" || k == K"macro_name"
400405
# These can appear outside of a macrocall, e.g. in `import`

JuliaLowering/src/runtime.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,9 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
320320

321321
__module__ = source.module
322322

323-
# Macro expansion. Looking at Core.GeneratedFunctionStub, it seems that
324-
# macros emitted by the generator are currently expanded in the latest
325-
# world, so do that for compatibility.
326-
macro_world = typemax(UInt)
323+
# Macro expansion. Note that we expand in `tls_world_age()` (see
324+
# Core.GeneratedFunctionStub)
325+
macro_world = Base.tls_world_age()
327326
ctx1 = MacroExpansionContext(graph, __module__, false, macro_world)
328327

329328
layer = only(ctx1.scope_layers)

JuliaLowering/src/syntax_macros.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ function Base.var"@nospecialize"(__context__::MacroContext, ex, exs...)
3434
_apply_nospecialize(__context__, ex)
3535
end
3636

37-
function Base.var"@atomic"(__context__::MacroContext, ex)
38-
@chk kind(ex) == K"Identifier" || kind(ex) == K"::" (ex, "Expected identifier or declaration")
39-
@ast __context__ __context__.macrocall [K"atomic" ex]
40-
end
37+
# TODO: support all forms that the original supports
38+
# function Base.var"@atomic"(__context__::MacroContext, ex)
39+
# @chk kind(ex) == K"Identifier" || kind(ex) == K"::" (ex, "Expected identifier or declaration")
40+
# @ast __context__ __context__.macrocall [K"atomic" ex]
41+
# end
4142

4243
function Base.var"@label"(__context__::MacroContext, ex)
4344
@chk kind(ex) == K"Identifier"

JuliaLowering/test/closures_ir.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ end
237237
21 SourceLocation::1:10
238238
22 (call core.svec %₁₈ %₂₀ %₂₁)
239239
23 --- method core.nothing %₂₂
240-
slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/g]
240+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/g]
241241
1 TestMod.#f#g##2
242242
2 static_parameter₁
243243
3 (new %%₂)

JuliaLowering/test/decls.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,17 @@ end
9292

9393
# Tuple/destructuring assignments
9494
@test JuliaLowering.include_string(test_mod, "(a0, a1, a2) = [1,2,3]") == [1,2,3]
95-
9695
@test JuliaLowering.include_string(test_mod, "const a,b,c = 1,2,3") === (1, 2, 3)
9796

97+
@testset "Placeholder decls" begin
98+
@test JuliaLowering.include_string(test_mod, "global _ = 1") === 1
99+
@test JuliaLowering.include_string(test_mod, "global _::Int = 1") === 1
100+
@test JuliaLowering.include_string(test_mod, "let; local _; _ = 1; end") === 1
101+
@test JuliaLowering.include_string(test_mod, "let; local _::Int = 1; end") === 1
102+
@test JuliaLowering.include_string(test_mod, "let; local (a0, _, a2) = [1,2,3]; end") == [1,2,3]
103+
@test JuliaLowering.include_string(test_mod, "let; local (a0, _::Int, a2) = [1,2,3]; end") == [1,2,3]
104+
end
105+
98106
test_mod_2 = Module()
99107
@testset "toplevel-preserving syntax" begin
100108
JuliaLowering.include_string(test_mod_2, "if true; global v1::Bool; else const v1 = 1; end")

JuliaLowering/test/functions.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,35 @@ end
471471
@test cl(x = 20) == 21
472472
end
473473

474+
@testset "Write-only placeholder function arguments" begin
475+
# positional arguments may be duplicate placeholders. keyword arguments can
476+
# contain placeholders, but they must be unique
477+
params_req = [""
478+
"_"
479+
"::Int"
480+
"_, _"]
481+
params_opt = [""
482+
"::Int=2"
483+
"_=2"]
484+
params_va = ["", "_..."]
485+
params_kw = [""
486+
"; _"
487+
"; _::Int"
488+
"; _::Int=1"
489+
"; _=1, __=2"
490+
"; _..."
491+
"; _=1, __..."]
492+
local i = 0
493+
for req in params_req, opt in params_opt, va in params_va, kw in params_kw
494+
arg_str = join(filter(!isempty, (req, opt, va, kw)), ", ")
495+
f_str = "function f_placeholders$i($arg_str); end"
496+
i += 1
497+
@testset "$f_str" begin
498+
@test JuliaLowering.include_string(test_mod, f_str) isa Function
499+
end
500+
end
501+
end
502+
474503
@testset "Generated functions" begin
475504
@test JuliaLowering.include_string(test_mod, raw"""
476505
begin

JuliaLowering/test/functions_ir.jl

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ end
2323
7 SourceLocation::1:10
2424
8 (call core.svec %%%₇)
2525
9 --- method core.nothing %
26-
slots: [slot₁/#self#(!read) slot₂/x slot₃/_(!read) slot₄/y]
26+
slots: [slot₁/#self#(!read) slot₂/x slot₃/#arg2#(!read) slot₄/y]
2727
1 TestMod.+
2828
2 (call %₁ slot₂/x slot₄/y)
2929
3 (return %₂)
@@ -47,7 +47,7 @@ end
4747
8 SourceLocation::1:10
4848
9 (call core.svec %%%₈)
4949
10 --- method core.nothing %
50-
slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/x]
50+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/x]
5151
1 slot₃/x
5252
2 (return %₁)
5353
11 latestworld
@@ -160,7 +160,7 @@ end
160160
16 SourceLocation::1:10
161161
17 (call core.svec %₁₁ %₁₅ %₁₆)
162162
18 --- method core.nothing %₁₇
163-
slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/_(!read) slot₄/_(!read)]
163+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/#arg2#(!read) slot₄/#arg3#(!read)]
164164
1 static_parameter₃
165165
2 static_parameter₁
166166
3 static_parameter₂
@@ -192,7 +192,7 @@ end
192192
14 SourceLocation::1:10
193193
15 (call core.svec %₁₁ %₁₃ %₁₄)
194194
16 --- method core.nothing %₁₅
195-
slots: [slot₁/#self#(!read) slot₂/_(!read)]
195+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)]
196196
1 static_parameter₁
197197
2 (return %₁)
198198
17 latestworld
@@ -220,7 +220,7 @@ end
220220
13 SourceLocation::1:10
221221
14 (call core.svec %₁₀ %₁₂ %₁₃)
222222
15 --- method core.nothing %₁₄
223-
slots: [slot₁/#self#(!read) slot₂/_(!read)]
223+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)]
224224
1 static_parameter₁
225225
2 (return %₁)
226226
16 latestworld
@@ -513,8 +513,8 @@ end
513513
8 SourceLocation::1:10
514514
9 (call core.svec %%%₈)
515515
10 --- method core.nothing %
516-
slots: [slot₁/#self#(called) slot₂/_]
517-
1 (call slot₁/#self# slot₂/_ 1 2)
516+
slots: [slot₁/#self#(called) slot₂/#arg1#]
517+
1 (call slot₁/#self# slot₂/#arg1# 1 2)
518518
2 (return %₁)
519519
11 latestworld
520520
12 TestMod.f
@@ -525,8 +525,8 @@ end
525525
17 SourceLocation::1:10
526526
18 (call core.svec %₁₅ %₁₆ %₁₇)
527527
19 --- method core.nothing %₁₈
528-
slots: [slot₁/#self#(called) slot₂/_ slot₃/y]
529-
1 (call slot₁/#self# slot₂/_ slot₃/y 2)
528+
slots: [slot₁/#self#(called) slot₂/#arg1# slot₃/y]
529+
1 (call slot₁/#self# slot₂/#arg1# slot₃/y 2)
530530
2 (return %₁)
531531
20 latestworld
532532
21 TestMod.f
@@ -537,7 +537,7 @@ end
537537
26 SourceLocation::1:10
538538
27 (call core.svec %₂₄ %₂₅ %₂₆)
539539
28 --- method core.nothing %₂₇
540-
slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/y slot₄/z]
540+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/y slot₄/z]
541541
1 (call core.tuple slot₃/y slot₄/z)
542542
2 (return %₁)
543543
29 latestworld
@@ -560,8 +560,8 @@ end
560560
8 SourceLocation::1:10
561561
9 (call core.svec %%%₈)
562562
10 --- method core.nothing %
563-
slots: [slot₁/#self#(called) slot₂/_]
564-
1 (call slot₁/#self# slot₂/_ 1)
563+
slots: [slot₁/#self#(called) slot₂/#arg1#]
564+
1 (call slot₁/#self# slot₂/#arg1# 1)
565565
2 (return %₁)
566566
11 latestworld
567567
12 TestMod.f
@@ -572,7 +572,7 @@ end
572572
17 SourceLocation::1:10
573573
18 (call core.svec %₁₅ %₁₆ %₁₇)
574574
19 --- method core.nothing %₁₈
575-
slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/x]
575+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/x]
576576
1 slot₃/x
577577
2 (return %₁)
578578
20 latestworld
@@ -923,6 +923,25 @@ end
923923
19 TestMod.f
924924
20 (return %₁₉)
925925

926+
########################################
927+
# Duplicate positional placeholders ok
928+
function f(_, _); end
929+
#---------------------
930+
1 (method TestMod.f)
931+
2 latestworld
932+
3 TestMod.f
933+
4 (call core.Typeof %₃)
934+
5 (call core.svec %₄ core.Any core.Any)
935+
6 (call core.svec)
936+
7 SourceLocation::1:10
937+
8 (call core.svec %%%₇)
938+
9 --- method core.nothing %
939+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/#arg2#(!read)]
940+
1 (return core.nothing)
941+
10 latestworld
942+
11 TestMod.f
943+
12 (return %₁₁)
944+
926945
########################################
927946
# Duplicate destructured placeholders ok
928947
function f((_,), (_,))
@@ -1216,6 +1235,14 @@ end
12161235
76 TestMod.f_kw_simple
12171236
77 (return %₇₆)
12181237

1238+
########################################
1239+
# Error: Duplicate keyword placeholder name
1240+
function f_kw_placeholders(; _=1, _=2); end
1241+
#---------------------
1242+
LoweringError:
1243+
function f_kw_placeholders(; _=1, _=2); end
1244+
# ╙ ── function argument name not unique
1245+
12191246
########################################
12201247
# Keyword slurping - simple forwarding of all kws
12211248
function f_kw_slurp_simple(; all_kws...)

JuliaLowering/test/generators_ir.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
8 SourceLocation::1:2
106106
9 (call core.svec %%%₈)
107107
10 --- method core.nothing %
108-
slots: [slot₁/#self#(!read) slot₂/_(!read)]
108+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)]
109109
1 (return 1)
110110
11 latestworld
111111
12 TestMod.#->##3
@@ -170,7 +170,7 @@ LoweringError:
170170
8 SourceLocation::1:4
171171
9 (call core.svec %%%₈)
172172
10 --- method core.nothing %
173-
slots: [slot₁/#self#(!read) slot₂/_(!read)]
173+
slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)]
174174
1 (call JuliaLowering.interpolate_ast SyntaxTree (inert (return x)))
175175
2 (return %₁)
176176
11 latestworld

JuliaLowering/test/hooks.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const JL = JuliaLowering
4545
@test isdefined(test_mod, :M)
4646
@test isdefined(test_mod.M, :x)
4747

48+
@test jeval("@ccall jl_value_ptr(nothing::Any)::Ptr{Cvoid}") isa Ptr{Cvoid}
49+
4850
# Tricky cases with symbols
4951
out = jeval("""module M2
5052
Base.@constprop :aggressive function f(x); x; end

0 commit comments

Comments
 (0)