diff --git a/src/desugaring.jl b/src/desugaring.jl index 2e9aba2..057746f 100644 --- a/src/desugaring.jl +++ b/src/desugaring.jl @@ -1357,8 +1357,10 @@ function expand_assignment(ctx, ex, is_const=false) # Identifier in lhs[1] is a variable type declaration, eg # x::T = rhs @ast ctx ex [K"block" - [K"decl" lhs[1] lhs[2]] - is_const ? [K"const" [K"=" lhs[1] rhs]] : [K"=" lhs[1] rhs] + if kind(x) !== K"Placeholder" + [K"decl" x T] + end + is_const ? [K"const" [K"=" x rhs]] : [K"=" x rhs] ] else # Otherwise just a type assertion, eg @@ -2169,8 +2171,17 @@ function make_lhs_decls(ctx, stmts, declkind, declmeta, ex, type_decls=true) if type_decls @chk numchildren(ex) == 2 name = ex[1] - @chk kind(name) == K"Identifier" - push!(stmts, makenode(ctx, ex, K"decl", name, ex[2])) + if kind(name) == K"Identifier" + push!(stmts, makenode(ctx, ex, K"decl", name, ex[2])) + else + # TODO: Currently, this ignores the LHS in `_::T = val`. + # We should probably do one of the following: + # - Throw a LoweringError if that's not too breaking + # - `convert(T, rhs)::T` and discard the result which is what + # `x::T = rhs` would do if x is never used again. + @chk kind(name) == K"Placeholder" + return + end end make_lhs_decls(ctx, stmts, declkind, declmeta, ex[1], type_decls) elseif k == K"tuple" || k == K"parameters" @@ -2196,7 +2207,7 @@ function expand_decls(ctx, ex) # expand_assignment will create the type decls make_lhs_decls(ctx, stmts, declkind, declmeta, binding[1], false) push!(stmts, expand_assignment(ctx, binding)) - elseif is_sym_decl(binding) || kind(binding) == K"Value" + elseif is_sym_decl(binding) || kind(binding) in (K"Value", K"Placeholder") make_lhs_decls(ctx, stmts, declkind, declmeta, binding, true) elseif kind(binding) == K"function" make_lhs_decls(ctx, stmts, declkind, declmeta, binding[1], false) @@ -2258,7 +2269,7 @@ end #------------------------------------------------------------------------------- # Expansion of function definitions -function expand_function_arg(ctx, body_stmts, arg, is_last_arg, is_kw) +function expand_function_arg(ctx, body_stmts, arg, is_last_arg, is_kw, arg_id) ex = arg if kind(ex) == K"=" @@ -2309,7 +2320,16 @@ function expand_function_arg(ctx, body_stmts, arg, is_last_arg, is_kw) K"local"(meta=CompileHints(:is_destructured_arg, true)) [K"=" ex name] ]) - elseif k == K"Identifier" || k == K"Placeholder" + elseif k == K"Placeholder" + # Lowering should be able to use placeholder args as rvalues internally, + # e.g. for kw method dispatch. Duplicate positional placeholder names + # should be allowed. + name = if is_kw + @ast ctx ex ex=>K"Identifier" + else + new_local_binding(ctx, ex, "#arg$(string(arg_id))#"; kind=:argument) + end + elseif k == K"Identifier" name = ex else throw(LoweringError(ex, is_kw ? "Invalid keyword name" : "Invalid function argument")) @@ -2647,7 +2667,7 @@ function keyword_function_defs(ctx, srcref, callex_srcref, name_str, typevar_nam kwtmp = new_local_binding(ctx, keywords, "kwtmp") for (i,arg) in enumerate(children(keywords)) (aname, atype, default, is_slurp) = - expand_function_arg(ctx, nothing, arg, i == numchildren(keywords), true) + expand_function_arg(ctx, nothing, arg, i == numchildren(keywords), true, i) push!(kw_names, aname) name_sym = @ast ctx aname aname=>K"Symbol" push!(body_arg_names, aname) @@ -3024,8 +3044,8 @@ function expand_function_def(ctx, ex, docs, rewrite_call=identity, rewrite_body= first_default = 0 # index into arg_names/arg_types arg_defaults = SyntaxList(ctx) for (i,arg) in enumerate(args) - (aname, atype, default, is_slurp) = expand_function_arg(ctx, body_stmts, arg, - i == length(args), false) + (aname, atype, default, is_slurp) = + expand_function_arg(ctx, body_stmts, arg, i == length(args), false, i) has_slurp |= is_slurp push!(arg_names, aname) @@ -3246,8 +3266,8 @@ function expand_opaque_closure(ctx, ex) body_stmts = SyntaxList(ctx) is_va = false for (i, arg) in enumerate(children(args)) - (aname, atype, default, is_slurp) = expand_function_arg(ctx, body_stmts, arg, - i == numchildren(args), false) + (aname, atype, default, is_slurp) = + expand_function_arg(ctx, body_stmts, arg, i == numchildren(args), false, i) is_va |= is_slurp push!(arg_names, aname) push!(arg_types, atype) diff --git a/src/macro_expansion.jl b/src/macro_expansion.jl index 673d71a..6aa3bdd 100644 --- a/src/macro_expansion.jl +++ b/src/macro_expansion.jl @@ -275,7 +275,13 @@ function expand_macro(ctx, ex) # age changes concurrently. # # TODO: Allow this to be passed in - if hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}; world=ctx.macro_world) + # TODO: hasmethod always returns false for our `typemax(UInt)` meaning + # "latest world," which we shouldn't be using. + has_new_macro = ctx.macro_world === typemax(UInt) ? + hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}) : + hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}; world=ctx.macro_world) + + if has_new_macro macro_args = prepare_macro_args(ctx, mctx, raw_args) expanded = try Base.invoke_in_world(ctx.macro_world, macfunc, macro_args...) @@ -376,16 +382,15 @@ function expand_forms_1(ctx::MacroExpansionContext, ex::SyntaxTree) k = kind(ex) if k == K"Identifier" name_str = ex.name_val - if all(==('_'), name_str) - @ast ctx ex ex=>K"Placeholder" - elseif is_ccall_or_cglobal(name_str) + if is_ccall_or_cglobal(name_str) # Lower special identifiers `cglobal` and `ccall` to `K"core"` # pseudo-refs very early so that cglobal and ccall can never be # turned into normal bindings (eg, assigned to) @ast ctx ex name_str::K"core" else - layerid = get(ex, :scope_layer, current_layer_id(ctx)) - makeleaf(ctx, ex, ex, kind=K"Identifier", scope_layer=layerid) + k = all(==('_'), name_str) ? K"Placeholder" : K"Identifier" + scope_layer = get(ex, :scope_layer, current_layer_id(ctx)) + makeleaf(ctx, ex, ex; kind=k, scope_layer) end elseif k == K"StrMacroName" || k == K"CmdMacroName" || k == K"macro_name" # These can appear outside of a macrocall, e.g. in `import` diff --git a/src/runtime.jl b/src/runtime.jl index e7bd83c..025c3f9 100644 --- a/src/runtime.jl +++ b/src/runtime.jl @@ -337,10 +337,9 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a __module__ = source.module - # Macro expansion. Looking at Core.GeneratedFunctionStub, it seems that - # macros emitted by the generator are currently expanded in the latest - # world, so do that for compatibility. - macro_world = typemax(UInt) + # Macro expansion. Note that we expand in `tls_world_age()` (see + # Core.GeneratedFunctionStub) + macro_world = Base.tls_world_age() ctx1 = MacroExpansionContext(graph, __module__, false, macro_world) layer = only(ctx1.scope_layers) diff --git a/src/syntax_macros.jl b/src/syntax_macros.jl index 43c186c..c33233b 100644 --- a/src/syntax_macros.jl +++ b/src/syntax_macros.jl @@ -34,10 +34,11 @@ function Base.var"@nospecialize"(__context__::MacroContext, ex, exs...) _apply_nospecialize(__context__, ex) end -function Base.var"@atomic"(__context__::MacroContext, ex) - @chk kind(ex) == K"Identifier" || kind(ex) == K"::" (ex, "Expected identifier or declaration") - @ast __context__ __context__.macrocall [K"atomic" ex] -end +# TODO: support all forms that the original supports +# function Base.var"@atomic"(__context__::MacroContext, ex) +# @chk kind(ex) == K"Identifier" || kind(ex) == K"::" (ex, "Expected identifier or declaration") +# @ast __context__ __context__.macrocall [K"atomic" ex] +# end function Base.var"@label"(__context__::MacroContext, ex) @chk kind(ex) == K"Identifier" diff --git a/test/closures_ir.jl b/test/closures_ir.jl index 0916e31..d1173d5 100644 --- a/test/closures_ir.jl +++ b/test/closures_ir.jl @@ -237,7 +237,7 @@ end 21 SourceLocation::1:10 22 (call core.svec %₁₈ %₂₀ %₂₁) 23 --- method core.nothing %₂₂ - slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/g] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/g] 1 TestMod.#f#g##2 2 static_parameter₁ 3 (new %₁ %₂) diff --git a/test/decls.jl b/test/decls.jl index ab25aaa..a0e3aaf 100644 --- a/test/decls.jl +++ b/test/decls.jl @@ -92,9 +92,17 @@ end # Tuple/destructuring assignments @test JuliaLowering.include_string(test_mod, "(a0, a1, a2) = [1,2,3]") == [1,2,3] - @test JuliaLowering.include_string(test_mod, "const a,b,c = 1,2,3") === (1, 2, 3) +@testset "Placeholder decls" begin + @test JuliaLowering.include_string(test_mod, "global _ = 1") === 1 + @test JuliaLowering.include_string(test_mod, "global _::Int = 1") === 1 + @test JuliaLowering.include_string(test_mod, "let; local _; _ = 1; end") === 1 + @test JuliaLowering.include_string(test_mod, "let; local _::Int = 1; end") === 1 + @test JuliaLowering.include_string(test_mod, "let; local (a0, _, a2) = [1,2,3]; end") == [1,2,3] + @test JuliaLowering.include_string(test_mod, "let; local (a0, _::Int, a2) = [1,2,3]; end") == [1,2,3] +end + test_mod_2 = Module() @testset "toplevel-preserving syntax" begin JuliaLowering.include_string(test_mod_2, "if true; global v1::Bool; else const v1 = 1; end") diff --git a/test/functions.jl b/test/functions.jl index 8193d2c..d25f5f0 100644 --- a/test/functions.jl +++ b/test/functions.jl @@ -471,6 +471,35 @@ end @test cl(x = 20) == 21 end +@testset "Write-only placeholder function arguments" begin + # positional arguments may be duplicate placeholders. keyword arguments can + # contain placeholders, but they must be unique + params_req = ["" + "_" + "::Int" + "_, _"] + params_opt = ["" + "::Int=2" + "_=2"] + params_va = ["", "_..."] + params_kw = ["" + "; _" + "; _::Int" + "; _::Int=1" + "; _=1, __=2" + "; _..." + "; _=1, __..."] + local i = 0 + for req in params_req, opt in params_opt, va in params_va, kw in params_kw + arg_str = join(filter(!isempty, (req, opt, va, kw)), ", ") + f_str = "function f_placeholders$i($arg_str); end" + i += 1 + @testset "$f_str" begin + @test JuliaLowering.include_string(test_mod, f_str) isa Function + end + end +end + @testset "Generated functions" begin @test JuliaLowering.include_string(test_mod, raw""" begin diff --git a/test/functions_ir.jl b/test/functions_ir.jl index a537757..2c25f91 100644 --- a/test/functions_ir.jl +++ b/test/functions_ir.jl @@ -23,7 +23,7 @@ end 7 SourceLocation::1:10 8 (call core.svec %₅ %₆ %₇) 9 --- method core.nothing %₈ - slots: [slot₁/#self#(!read) slot₂/x slot₃/_(!read) slot₄/y] + slots: [slot₁/#self#(!read) slot₂/x slot₃/#arg2#(!read) slot₄/y] 1 TestMod.+ 2 (call %₁ slot₂/x slot₄/y) 3 (return %₂) @@ -47,7 +47,7 @@ end 8 SourceLocation::1:10 9 (call core.svec %₆ %₇ %₈) 10 --- method core.nothing %₉ - slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/x] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/x] 1 slot₃/x 2 (return %₁) 11 latestworld @@ -160,7 +160,7 @@ end 16 SourceLocation::1:10 17 (call core.svec %₁₁ %₁₅ %₁₆) 18 --- method core.nothing %₁₇ - slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/_(!read) slot₄/_(!read)] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/#arg2#(!read) slot₄/#arg3#(!read)] 1 static_parameter₃ 2 static_parameter₁ 3 static_parameter₂ @@ -192,7 +192,7 @@ end 14 SourceLocation::1:10 15 (call core.svec %₁₁ %₁₃ %₁₄) 16 --- method core.nothing %₁₅ - slots: [slot₁/#self#(!read) slot₂/_(!read)] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)] 1 static_parameter₁ 2 (return %₁) 17 latestworld @@ -220,7 +220,7 @@ end 13 SourceLocation::1:10 14 (call core.svec %₁₀ %₁₂ %₁₃) 15 --- method core.nothing %₁₄ - slots: [slot₁/#self#(!read) slot₂/_(!read)] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)] 1 static_parameter₁ 2 (return %₁) 16 latestworld @@ -513,8 +513,8 @@ end 8 SourceLocation::1:10 9 (call core.svec %₆ %₇ %₈) 10 --- method core.nothing %₉ - slots: [slot₁/#self#(called) slot₂/_] - 1 (call slot₁/#self# slot₂/_ 1 2) + slots: [slot₁/#self#(called) slot₂/#arg1#] + 1 (call slot₁/#self# slot₂/#arg1# 1 2) 2 (return %₁) 11 latestworld 12 TestMod.f @@ -525,8 +525,8 @@ end 17 SourceLocation::1:10 18 (call core.svec %₁₅ %₁₆ %₁₇) 19 --- method core.nothing %₁₈ - slots: [slot₁/#self#(called) slot₂/_ slot₃/y] - 1 (call slot₁/#self# slot₂/_ slot₃/y 2) + slots: [slot₁/#self#(called) slot₂/#arg1# slot₃/y] + 1 (call slot₁/#self# slot₂/#arg1# slot₃/y 2) 2 (return %₁) 20 latestworld 21 TestMod.f @@ -537,7 +537,7 @@ end 26 SourceLocation::1:10 27 (call core.svec %₂₄ %₂₅ %₂₆) 28 --- method core.nothing %₂₇ - slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/y slot₄/z] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/y slot₄/z] 1 (call core.tuple slot₃/y slot₄/z) 2 (return %₁) 29 latestworld @@ -560,8 +560,8 @@ end 8 SourceLocation::1:10 9 (call core.svec %₆ %₇ %₈) 10 --- method core.nothing %₉ - slots: [slot₁/#self#(called) slot₂/_] - 1 (call slot₁/#self# slot₂/_ 1) + slots: [slot₁/#self#(called) slot₂/#arg1#] + 1 (call slot₁/#self# slot₂/#arg1# 1) 2 (return %₁) 11 latestworld 12 TestMod.f @@ -572,7 +572,7 @@ end 17 SourceLocation::1:10 18 (call core.svec %₁₅ %₁₆ %₁₇) 19 --- method core.nothing %₁₈ - slots: [slot₁/#self#(!read) slot₂/_(!read) slot₃/x] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/x] 1 slot₃/x 2 (return %₁) 20 latestworld @@ -923,6 +923,25 @@ end 19 TestMod.f 20 (return %₁₉) +######################################## +# Duplicate positional placeholders ok +function f(_, _); end +#--------------------- +1 (method TestMod.f) +2 latestworld +3 TestMod.f +4 (call core.Typeof %₃) +5 (call core.svec %₄ core.Any core.Any) +6 (call core.svec) +7 SourceLocation::1:10 +8 (call core.svec %₅ %₆ %₇) +9 --- method core.nothing %₈ + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read) slot₃/#arg2#(!read)] + 1 (return core.nothing) +10 latestworld +11 TestMod.f +12 (return %₁₁) + ######################################## # Duplicate destructured placeholders ok function f((_,), (_,)) @@ -1216,6 +1235,84 @@ end 76 TestMod.f_kw_simple 77 (return %₇₆) +######################################## +# Keyword args with placeholders that need to be read +function f_kw_placeholders(_, ::Int; kw=1) + kw +end +#--------------------- +1 (method TestMod.f_kw_placeholders) +2 latestworld +3 (method TestMod.#f_kw_placeholders#0) +4 latestworld +5 TestMod.#f_kw_placeholders#0 +6 (call core.Typeof %₅) +7 TestMod.f_kw_placeholders +8 (call core.Typeof %₇) +9 TestMod.Int +10 (call core.svec %₆ core.Any %₈ core.Any %₉) +11 (call core.svec) +12 SourceLocation::1:10 +13 (call core.svec %₁₀ %₁₁ %₁₂) +14 --- method core.nothing %₁₃ + slots: [slot₁/#self#(!read) slot₂/kw slot₃/#self#(!read) slot₄/#arg1#(!read) slot₅/#arg2#(!read)] + 1 (meta :nkw 1) + 2 slot₂/kw + 3 (return %₂) +15 latestworld +16 (call core.typeof core.kwcall) +17 TestMod.f_kw_placeholders +18 (call core.Typeof %₁₇) +19 TestMod.Int +20 (call core.svec %₁₆ core.NamedTuple %₁₈ core.Any %₁₉) +21 (call core.svec) +22 SourceLocation::1:10 +23 (call core.svec %₂₀ %₂₁ %₂₂) +24 --- method core.nothing %₂₃ + slots: [slot₁/#self#(!read) slot₂/kws slot₃/#self# slot₄/#arg1# slot₅/#arg2# slot₆/kwtmp slot₇/kw(!read)] + 1 (newvar slot₇/kw) + 2 (call core.isdefined slot₂/kws :kw) + 3 (gotoifnot %₂ label₇) + 4 (call core.getfield slot₂/kws :kw) + 5 (= slot₆/kwtmp %₄) + 6 (goto label₈) + 7 (= slot₆/kwtmp 1) + 8 slot₆/kwtmp + 9 (call top.keys slot₂/kws) + 10 (call core.tuple :kw) + 11 (call top.diff_names %₉ %₁₀) + 12 (call top.isempty %₁₁) + 13 (gotoifnot %₁₂ label₁₅) + 14 (goto label₁₆) + 15 (call top.kwerr slot₂/kws slot₃/#self# slot₄/#arg1# slot₅/#arg2#) + 16 TestMod.#f_kw_placeholders#0 + 17 (call %₁₆ %₈ slot₃/#self# slot₄/#arg1# slot₅/#arg2#) + 18 (return %₁₇) +25 latestworld +26 TestMod.f_kw_placeholders +27 (call core.Typeof %₂₆) +28 TestMod.Int +29 (call core.svec %₂₇ core.Any %₂₈) +30 (call core.svec) +31 SourceLocation::1:10 +32 (call core.svec %₂₉ %₃₀ %₃₁) +33 --- method core.nothing %₃₂ + slots: [slot₁/#self# slot₂/#arg1# slot₃/#arg2#] + 1 TestMod.#f_kw_placeholders#0 + 2 (call %₁ 1 slot₁/#self# slot₂/#arg1# slot₃/#arg2#) + 3 (return %₂) +34 latestworld +35 TestMod.f_kw_placeholders +36 (return %₃₅) + +######################################## +# Error: Duplicate keyword placeholder name +function f_kw_placeholders(; _=1, _=2); end +#--------------------- +LoweringError: +function f_kw_placeholders(; _=1, _=2); end +# ╙ ── function argument name not unique + ######################################## # Keyword slurping - simple forwarding of all kws function f_kw_slurp_simple(; all_kws...) diff --git a/test/generators_ir.jl b/test/generators_ir.jl index 28f0241..2ff7858 100644 --- a/test/generators_ir.jl +++ b/test/generators_ir.jl @@ -105,7 +105,7 @@ 8 SourceLocation::1:2 9 (call core.svec %₆ %₇ %₈) 10 --- method core.nothing %₉ - slots: [slot₁/#self#(!read) slot₂/_(!read)] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)] 1 (return 1) 11 latestworld 12 TestMod.#->##3 @@ -170,7 +170,7 @@ LoweringError: 8 SourceLocation::1:4 9 (call core.svec %₆ %₇ %₈) 10 --- method core.nothing %₉ - slots: [slot₁/#self#(!read) slot₂/_(!read)] + slots: [slot₁/#self#(!read) slot₂/#arg1#(!read)] 1 (call JuliaLowering.interpolate_ast SyntaxTree (inert (return x))) 2 (return %₁) 11 latestworld diff --git a/test/hooks.jl b/test/hooks.jl index 823e9b6..6dd2b62 100644 --- a/test/hooks.jl +++ b/test/hooks.jl @@ -45,6 +45,8 @@ const JL = JuliaLowering @test isdefined(test_mod, :M) @test isdefined(test_mod.M, :x) + @test jeval("@ccall jl_value_ptr(nothing::Any)::Ptr{Cvoid}") isa Ptr{Cvoid} + # Tricky cases with symbols out = jeval("""module M2 Base.@constprop :aggressive function f(x); x; end diff --git a/test/misc_ir.jl b/test/misc_ir.jl index a0a231d..8159f91 100644 --- a/test/misc_ir.jl +++ b/test/misc_ir.jl @@ -260,10 +260,7 @@ let end #--------------------- LoweringError: -let - @atomic x -# └───────┘ ── unimplemented or unsupported atomic declaration -end +#= none:2 =# - unimplemented or unsupported atomic declaration ######################################## # GC.@preserve support