diff --git a/src/desugaring.jl b/src/desugaring.jl index 10039b5..6e8456e 100644 --- a/src/desugaring.jl +++ b/src/desugaring.jl @@ -25,18 +25,36 @@ end # Return true when `x` and `y` are "the same identifier", but also works with # bindings (and hence ssa vars). See also `is_identifier_like()` -function is_same_identifier_like(x, y) - return (kind(x) == K"Identifier" && kind(y) == K"Identifier" && NameKey(x) == NameKey(y)) || - (kind(x) == K"BindingId" && kind(y) == K"BindingId" && x.var_id == y.var_id) +function is_same_identifier_like(ex::SyntaxTree, y::SyntaxTree) + return (kind(ex) == K"Identifier" && kind(y) == K"Identifier" && NameKey(ex) == NameKey(y)) || + (kind(ex) == K"BindingId" && kind(y) == K"BindingId" && ex.var_id == y.var_id) end -function is_same_identifier_like(x, name::AbstractString) - return kind(x) == K"Identifier" && x.name_val == name +function is_same_identifier_like(ex::SyntaxTree, name::AbstractString) + return kind(ex) == K"Identifier" && ex.name_val == name end -function contains_identifier(ex, idents...) - return any(is_same_identifier_like(ex, id) for id in idents) || - (!is_leaf(ex) && any(contains_identifier(e, idents...) for e in children(ex))) +function contains_identifier(ex::SyntaxTree, idents::AbstractVector{<:SyntaxTree}) + contains_unquoted(ex) do e + any(is_same_identifier_like(e, id) for id in idents) + end +end + +function contains_identifier(ex::SyntaxTree, idents...) + contains_unquoted(ex) do e + any(is_same_identifier_like(e, id) for id in idents) + end +end + +# Return true if `f(e)` is true for any unquoted child of `ex`, recursively. +function contains_unquoted(f::Function, ex::SyntaxTree) + if f(ex) + return true + elseif !is_leaf(ex) && !(kind(ex) in KSet"quote inert meta") + return any(contains_unquoted(f, e) for e in children(ex)) + else + return false + end end # Identify some expressions that are safe to repeat @@ -526,7 +544,7 @@ function expand_unionall_def(ctx, srcref, lhs, rhs) name = lhs[1] @ast ctx srcref [K"block" [K"const_if_global" name] - unionall_type = expand_forms_2(ctx, [K"where" rhs lhs[2:end]...]) + unionall_type := expand_forms_2(ctx, [K"where" rhs lhs[2:end]...]) expand_forms_2([K"=" name unionall_type]) ] end @@ -1238,7 +1256,9 @@ function match_function_arg(full_ex) is_nospecialize = true ex = ex[2] elseif k == K"=" - @chk full_ex isnothing(default) && !is_slurp + if !isnothing(default) + throw(full_ex, "multiple defaults provided with `=` in function argument") + end default = ex[2] ex = ex[1] else @@ -1252,6 +1272,11 @@ function match_function_arg(full_ex) is_nospecialize=is_nospecialize) end +# Expand `where` clause(s) of a function into (typevar_names, typevar_stmts) where +# - `typevar_names` are the names of the type's type parameters +# - `typevar_stmts` are a list of statements to define a `TypeVar` for each parameter +# name in `typevar_names`, to be emitted prior to uses of `typevar_names`. +# There is exactly one statement from each typevar. function _split_wheres!(ctx, typevar_names, typevar_stmts, ex) if kind(ex) == K"where" && numchildren(ex) == 2 vars_kind = kind(ex[2]) @@ -1259,13 +1284,7 @@ function _split_wheres!(ctx, typevar_names, typevar_stmts, ex) append!(typevar_names, children(ex[2])) else params = vars_kind == K"braces" ? ex[2][1:end] : ex[2:2] - for param in params - bounds = analyze_typevar(ctx, param) - n = bounds[1] - push!(typevar_names, n) - push!(typevar_stmts, @ast ctx param [K"local" n]) - push!(typevar_stmts, @ast ctx param [K"=" n bounds_to_TypeVar(ctx, param, bounds)]) - end + expand_typevars!(ctx, typevar_names, typevar_stmts, params) end _split_wheres!(ctx, typevar_names, typevar_stmts, ex[1]) else @@ -1273,6 +1292,120 @@ function _split_wheres!(ctx, typevar_names, typevar_stmts, ex) end end +function _method_def_expr(ctx, srcref, callex, func_self, method_table, + docs, typevar_names, arg_names, arg_types, ret_var, body) + # metadata contains svec(types, sparms, location) + @ast ctx srcref [K"block" + method_metadata := [K"call"(callex) + "svec" ::K"core" + [K"call" + "svec" ::K"core" + arg_types... + ] + [K"call" + "svec" ::K"core" + typevar_names... + ] + QuoteNode(source_location(LineNumberNode, callex))::K"Value" + ] + [K"method" + method_table + method_metadata + [K"lambda"(body, is_toplevel_thunk=false) + [K"block" arg_names...] + [K"block" typevar_names...] + body + ret_var # might be `nothing` and hence removed + ] + ] + if !isnothing(docs) + [K"call"(docs) + bind_docs!::K"Value" + func_self + docs[1] + method_metadata + ] + end + ] +end + +function trim_used_typevars(ctx, arg_types, typevar_names, typevar_stmts) + n_typevars = length(typevar_names) + @assert n_typevars == length(typevar_stmts) + # Filter typevar names down to those which are directly used in the arg list + typevar_used = [contains_identifier(tn, arg_types) for tn in typevar_names] + # _Or_ used transitively via other typevars. The following code + # computes this by incrementally coloring the graph of dependencies + # between type vars. + found_used = true + while found_used + found_used = false + for (i,tn) in enumerate(typevar_names) + if typevar_used[i] + continue + end + for j = i+1:n_typevars + if typevar_used[j] && contains_identifier(typevar_stmts[j], tn) + found_used = true + typevar_used[i] = true + break + end + end + end + end + trimmed_typevar_names = SyntaxList(ctx) + for (used,tn) in zip(typevar_used, typevar_names) + if used + push!(trimmed_typevar_names, tn) + end + end + return trimmed_typevar_names +end + +# Generate a method for every number of allowed optional arguments +# For example for `f(x, y=1, z=2)` we generate two additional methods +# f(x) = f(x, 1, 2) +# f(x, y) = f(x, y, 2) +function _optional_positional_defs!(ctx, method_stmts, srcref, callex, func_self, + method_table, typevar_names, typevar_stmts, + arg_names, arg_types, first_default, arg_defaults, ret_var) + # Replace placeholder arguments with variables - we need to pass them to + # the inner method for dispatch even when unused in the inner method body + def_arg_names = map(arg_names) do arg + kind(arg) == K"Placeholder" ? + new_mutable_var(ctx, arg, arg.name_val; kind=:argument) : + arg + end + for def_idx = 1:length(arg_defaults) + first_omitted = first_default + def_idx - 1 + trimmed_arg_names = def_arg_names[1:first_omitted-1] + # Call the full method directly if no arguments are reused in + # subsequent defaults. Otherwise conservatively call the function with + # only one additional default argument supplied and let the chain of + # function calls eventually lead to the full method. + any_args_in_trailing_defaults = + any(arg_defaults[def_idx+1:end]) do defaultval + contains_identifier(defaultval, def_arg_names[first_omitted:end]) + end + last_used_default = any_args_in_trailing_defaults ? + def_idx : lastindex(arg_defaults) + body = @ast ctx callex [K"block" + [K"call" + trimmed_arg_names... + arg_defaults[def_idx:last_used_default]... + ] + ] + trimmed_arg_types = arg_types[1:first_omitted-1] + trimmed_typevar_names = trim_used_typevars(ctx, trimmed_arg_types, + typevar_names, typevar_stmts) + # TODO: Ensure we preserve @nospecialize metadata in args + push!(method_stmts, + _method_def_expr(ctx, srcref, callex, func_self, method_table, nothing, + trimmed_typevar_names, trimmed_arg_names, trimmed_arg_types, + ret_var, body)) + end +end + function expand_function_def(ctx, ex, docs, rewrite_call=identity, rewrite_body=identity) @chk numchildren(ex) in (1,2) name = ex[1] @@ -1336,7 +1469,7 @@ function expand_function_def(ctx, ex, docs, rewrite_call=identity, rewrite_body= if isnothing(info.default) if !isempty(arg_defaults) && !info.is_slurp # TODO: Referring to multiple pieces of syntax in one error message is necessary. - # TODO: Poision ASTs with error nodes and continue rather than immediately throwing. + # TODO: Poison ASTs with error nodes and continue rather than immediately throwing. # # We should make something like the following kind of thing work! # arg_defaults[1] = @ast_error ctx arg_defaults[1] """ @@ -1354,6 +1487,9 @@ function expand_function_def(ctx, ex, docs, rewrite_call=identity, rewrite_body= end push!(arg_defaults, info.default) end + # TODO: Ideally, ensure side effects of evaluating arg_types only + # happen once - we should create an ssavar if there's any following + # defaults. (flisp lowering doesn't ensure this either) push!(arg_types, atype) end @@ -1387,6 +1523,7 @@ function expand_function_def(ctx, ex, docs, rewrite_call=identity, rewrite_body= func_self ] end + # Add self argument pushfirst!(arg_names, farg_name) pushfirst!(arg_types, farg_type) @@ -1402,42 +1539,33 @@ function expand_function_def(ctx, ex, docs, rewrite_call=identity, rewrite_body= ret_var = nothing end - method_table = nothing_(ctx, name) # TODO: method overlays + method_table_val = nothing # TODO: method overlays + method_table = isnothing(method_table_val) ? + @ast(ctx, callex, "nothing"::K"core") : + ssavar(ctx, ex, "method_table") + method_stmts = SyntaxList(ctx) + + if !isempty(arg_defaults) + # For self argument added above + first_default += 1 + _optional_positional_defs!(ctx, method_stmts, ex, callex, func_self, + method_table, typevar_names, typevar_stmts, + arg_names, arg_types, first_default, arg_defaults, ret_var) + end + + # The method with all non-default arguments + push!(method_stmts, + _method_def_expr(ctx, ex, callex, func_self, method_table, docs, + typevar_names, arg_names, arg_types, ret_var, body)) + @ast ctx ex [K"scope_block"(scope_type=:hard) [K"block" typevar_stmts... - [K"=" func_self func_self_val] - # metadata contains svec(types, sparms, location) - method_metadata := [K"call"(callex) - "svec" ::K"core" - [K"call" - "svec" ::K"core" - arg_types... - ] - [K"call" - "svec" ::K"core" - typevar_names... - ] - QuoteNode(source_location(LineNumberNode, callex))::K"Value" - ] - [K"method" - method_table - method_metadata - [K"lambda"(body, is_toplevel_thunk=false) - [K"block" arg_names...] - [K"block" typevar_names...] - body - ret_var # might be `nothing` and hence removed - ] - ] - if !isnothing(docs) - [K"call"(docs) - bind_docs!::K"Value" - func_self - docs[1] - method_metadata - ] + if !isnothing(method_table_val) + [K"=" method_table method_table_val] end + [K"=" func_self func_self_val] + method_stmts... [K"unnecessary" func_self] ] ] @@ -1569,19 +1697,27 @@ function analyze_type_sig(ctx, ex) end # Expand type_params into (typevar_names, typevar_stmts) where -# - `typevar_names` are the names of the types's type parameters +# - `typevar_names` are the names of the type's type parameters # - `typevar_stmts` are a list of statements to define a `TypeVar` for each parameter -# name in `typevar_names`, to be emitted prior to uses of `typevar_names` -function expand_typevars(ctx, type_params) - typevar_names = SyntaxList(ctx) - typevar_stmts = SyntaxList(ctx) +# name in `typevar_names`, to be emitted prior to uses of `typevar_names`. +# There is exactly one statement from each typevar. +function expand_typevars!(ctx, typevar_names, typevar_stmts, type_params) for param in type_params bounds = analyze_typevar(ctx, param) n = bounds[1] push!(typevar_names, n) - push!(typevar_stmts, @ast ctx param [K"local" n]) - push!(typevar_stmts, @ast ctx param [K"=" n bounds_to_TypeVar(ctx, param, bounds)]) + push!(typevar_stmts, @ast ctx param [K"block" + [K"local" n] + [K"=" n bounds_to_TypeVar(ctx, param, bounds)] + ]) end + return nothing +end + +function expand_typevars(ctx, type_params) + typevar_names = SyntaxList(ctx) + typevar_stmts = SyntaxList(ctx) + expand_typevars!(ctx, typevar_names, typevar_stmts, type_params) return (typevar_names, typevar_stmts) end diff --git a/test/demo.jl b/test/demo.jl index e92c5e6..4eb3625 100644 --- a/test/demo.jl +++ b/test/demo.jl @@ -570,6 +570,63 @@ struct S9{T} end """ +# Default positional args with missing arg names +src = """ +function f(::Int, y=1, z=2) + (y, z) +end +""" + +# Default positional args with placeholders +src = """ +function f(_::Int, x=1) + x +end +""" + +# Positional args and type parameters with transitive dependencies +# Bug in flisp lowering - see https://github.com/JuliaLang/julia/issues/49275 +src = """ +function f(x, y::S=[1], z) where {T, S<:AbstractVector{T}} + (x, y, z, T) +end +""" + +# Default positional args before trailing slurp are allowed +src = """ +function f(x=1, ys...) + ys +end +""" + +# Default positional args after a slurp is an error +src = """ +function f(x=1, ys..., z=2) + ys +end +""" + +# Positional arg with slurp and default +src = """ +function f(x=1, ys...="hi") + ys +end +""" + +# Positional arg with slurp and splat +src = """ +function f(x=1, ys...=(1,2)...) + ys +end +""" + +# TODO: fix this - it's interpreted in a bizarre way as a kw call. +# src = """ +# function f(x=y=1) +# x +# end +# """ + ex = parsestmt(SyntaxTree, src, filename="foo.jl") ex = ensure_attributes(ex, var_id=Int) #ex = softscope_test(ex) diff --git a/test/functions.jl b/test/functions.jl index 7f39252..2acc836 100644 --- a/test/functions.jl +++ b/test/functions.jl @@ -131,4 +131,60 @@ begin end """) +# Default positional arguments +@test JuliaLowering.include_string(test_mod, """ +begin + function f_def_simple(x=1, y=2, z=x) + (x,y,z) + end + + (f_def_simple(), f_def_simple(10), f_def_simple(10,20), f_def_simple(10,20,30)) +end +""") == ((1,2,1), (10,2,10), (10,20,10), (10,20,30)) + +@test JuliaLowering.include_string(test_mod, """ +begin + function f_def_placeholders(::T=1, _::S=1.0) where {T,S} + (T,S) + end + + (f_def_placeholders(), f_def_placeholders(1.0), f_def_placeholders(1.0, 1)) +end +""") == ((Int,Float64), (Float64,Float64), (Float64,Int)) + +@test JuliaLowering.include_string(test_mod, """ +begin + function f_def_typevars(x, y::S=[1], z::U=2) where {T, S<:AbstractVector{T}, U} + (x, y, z, T, S, U) + end + + (f_def_typevars(1), f_def_typevars(1,[1.0]), f_def_typevars(1,[1.0],-1.0)) +end +""") == ((1, [1], 2, Int, Vector{Int}, Int), + (1, [1.0], 2, Float64, Vector{Float64}, Int), + (1, [1.0], -1.0, Float64, Vector{Float64}, Float64)) + +@test JuliaLowering.include_string(test_mod, """ +begin + function f_def_slurp(x=1, ys...) + (x, ys) + end + + (f_def_slurp(), f_def_slurp(2), f_def_slurp(2,3)) +end +""") == ((1, ()), + (2, ()), + (2, (3,))) + +@test JuliaLowering.include_string(test_mod, """ +begin + function f_def_slurp_splat(ys...=(1,2)...) + ys + end + + (f_def_slurp_splat(), f_def_slurp_splat(10,20)) +end +""") == ((1,2), + (10,20)) + end diff --git a/test/functions_ir.jl b/test/functions_ir.jl index 204ef2f..16bc027 100644 --- a/test/functions_ir.jl +++ b/test/functions_ir.jl @@ -329,6 +329,312 @@ x^42.0 3 (call %₁ %₂ 42.0) 4 (return %₃) +######################################## +# Simple positional args with defaults +function f(x::T, y::S=1, z::U=2) + (x,y) +end +#--------------------- +1 (method :f) +2 (call core.Typeof %₁) +3 TestMod.T +4 (call core.svec %₂ %₃) +5 (call core.svec) +6 (call core.svec %₄ %₅ :($(QuoteNode(:(#= line 1 =#))))) +7 --- method core.nothing %₆ + 1 (call slot₁/#self# slot₂/x 1 2) + 2 (return %₁) +8 (call core.Typeof %₁) +9 TestMod.T +10 TestMod.S +11 (call core.svec %₈ %₉ %₁₀) +12 (call core.svec) +13 (call core.svec %₁₁ %₁₂ :($(QuoteNode(:(#= line 1 =#))))) +14 --- method core.nothing %₁₃ + 1 (call slot₁/#self# slot₂/x slot₃/y 2) + 2 (return %₁) +15 (call core.Typeof %₁) +16 TestMod.T +17 TestMod.S +18 TestMod.U +19 (call core.svec %₁₅ %₁₆ %₁₇ %₁₈) +20 (call core.svec) +21 (call core.svec %₁₉ %₂₀ :($(QuoteNode(:(#= line 1 =#))))) +22 --- method core.nothing %₂₁ + 1 (call core.tuple slot₂/x slot₃/y) + 2 (return %₁) +23 (return %₁) + +######################################## +# Default positional args which depend on other args +function f(x=1, y=x) + (x,y) +end +#--------------------- +1 (method :f) +2 (call core.Typeof %₁) +3 (call core.svec %₂) +4 (call core.svec) +5 (call core.svec %₃ %₄ :($(QuoteNode(:(#= line 1 =#))))) +6 --- method core.nothing %₅ + 1 (call slot₁/#self# 1) + 2 (return %₁) +7 (call core.Typeof %₁) +8 (call core.svec %₇ core.Any) +9 (call core.svec) +10 (call core.svec %₈ %₉ :($(QuoteNode(:(#= line 1 =#))))) +11 --- method core.nothing %₁₀ + 1 (call slot₁/#self# slot₂/x slot₂/x) + 2 (return %₁) +12 (call core.Typeof %₁) +13 (call core.svec %₁₂ core.Any core.Any) +14 (call core.svec) +15 (call core.svec %₁₃ %₁₄ :($(QuoteNode(:(#= line 1 =#))))) +16 --- method core.nothing %₁₅ + 1 (call core.tuple slot₂/x slot₃/y) + 2 (return %₁) +17 (return %₁) + +######################################## +# Default positional args with missing arg names (implicit placeholders) +function f(::Int, y=1, z=2) + (y, z) +end +#--------------------- +1 (method :f) +2 (call core.Typeof %₁) +3 TestMod.Int +4 (call core.svec %₂ %₃) +5 (call core.svec) +6 (call core.svec %₄ %₅ :($(QuoteNode(:(#= line 1 =#))))) +7 --- method core.nothing %₆ + 1 (call slot₁/#self# slot₂/_ 1 2) + 2 (return %₁) +8 (call core.Typeof %₁) +9 TestMod.Int +10 (call core.svec %₈ %₉ core.Any) +11 (call core.svec) +12 (call core.svec %₁₀ %₁₁ :($(QuoteNode(:(#= line 1 =#))))) +13 --- method core.nothing %₁₂ + 1 (call slot₁/#self# slot₂/_ slot₃/y 2) + 2 (return %₁) +14 (call core.Typeof %₁) +15 TestMod.Int +16 (call core.svec %₁₄ %₁₅ core.Any core.Any) +17 (call core.svec) +18 (call core.svec %₁₆ %₁₇ :($(QuoteNode(:(#= line 1 =#))))) +19 --- method core.nothing %₁₈ + 1 (call core.tuple slot₃/y slot₄/z) + 2 (return %₁) +20 (return %₁) + +######################################## +# Default positional args with placeholders +function f(_::Int, x=1) + x +end +#--------------------- +1 (method :f) +2 (call core.Typeof %₁) +3 TestMod.Int +4 (call core.svec %₂ %₃) +5 (call core.svec) +6 (call core.svec %₄ %₅ :($(QuoteNode(:(#= line 1 =#))))) +7 --- method core.nothing %₆ + 1 (call slot₁/#self# slot₂/_ 1) + 2 (return %₁) +8 (call core.Typeof %₁) +9 TestMod.Int +10 (call core.svec %₈ %₉ core.Any) +11 (call core.svec) +12 (call core.svec %₁₀ %₁₁ :($(QuoteNode(:(#= line 1 =#))))) +13 --- method core.nothing %₁₂ + 1 slot₃/x + 2 (return %₁) +14 (return %₁) + +######################################## +# Positional args with defaults and `where` clauses +function f(x::T, y::S=1, z::U=2) where {T,S<:T,U<:S} + (x,y,z) +end +#--------------------- +1 (= slot₂/T (call core.TypeVar :T)) +2 slot₂/T +3 (= slot₁/S (call core.TypeVar :S %₂)) +4 slot₁/S +5 (= slot₃/U (call core.TypeVar :U %₄)) +6 (method :f) +7 (call core.Typeof %₆) +8 slot₂/T +9 (call core.svec %₇ %₈) +10 slot₂/T +11 (call core.svec %₁₀) +12 (call core.svec %₉ %₁₁ :($(QuoteNode(:(#= line 1 =#))))) +13 --- method core.nothing %₁₂ + 1 (call slot₁/#self# slot₂/x 1 2) + 2 (return %₁) +14 (call core.Typeof %₆) +15 slot₂/T +16 slot₁/S +17 (call core.svec %₁₄ %₁₅ %₁₆) +18 slot₂/T +19 slot₁/S +20 (call core.svec %₁₈ %₁₉) +21 (call core.svec %₁₇ %₂₀ :($(QuoteNode(:(#= line 1 =#))))) +22 --- method core.nothing %₂₁ + 1 (call slot₁/#self# slot₂/x slot₃/y 2) + 2 (return %₁) +23 (call core.Typeof %₆) +24 slot₂/T +25 slot₁/S +26 slot₃/U +27 (call core.svec %₂₃ %₂₄ %₂₅ %₂₆) +28 slot₂/T +29 slot₁/S +30 slot₃/U +31 (call core.svec %₂₈ %₂₉ %₃₀) +32 (call core.svec %₂₇ %₃₁ :($(QuoteNode(:(#= line 1 =#))))) +33 --- method core.nothing %₃₂ + 1 (call core.tuple slot₂/x slot₃/y slot₄/z) + 2 (return %₁) +34 (return %₆) + +######################################## +# Positional args and type parameters with transitive dependencies +# See https://github.com/JuliaLang/julia/issues/49275 - the first method +# generated here for only `x` should contain zero type parameters. +function f(x, y::S=[1], z::U=2) where {T, S<:AbstractVector{T}, U} + (x, y, z, T, S, U) +end +#--------------------- +1 (= slot₂/T (call core.TypeVar :T)) +2 TestMod.AbstractVector +3 slot₂/T +4 (call core.apply_type %₂ %₃) +5 (= slot₁/S (call core.TypeVar :S %₄)) +6 (= slot₃/U (call core.TypeVar :U)) +7 (method :f) +8 (call core.Typeof %₇) +9 (call core.svec %₈ core.Any) +10 (call core.svec) +11 (call core.svec %₉ %₁₀ :($(QuoteNode(:(#= line 1 =#))))) +12 --- method core.nothing %₁₁ + 1 (call top.vect 1) + 2 (call slot₁/#self# slot₂/x %₁ 2) + 3 (return %₂) +13 (call core.Typeof %₇) +14 slot₁/S +15 (call core.svec %₁₃ core.Any %₁₄) +16 slot₂/T +17 slot₁/S +18 (call core.svec %₁₆ %₁₇) +19 (call core.svec %₁₅ %₁₈ :($(QuoteNode(:(#= line 1 =#))))) +20 --- method core.nothing %₁₉ + 1 (call slot₁/#self# slot₂/x slot₃/y 2) + 2 (return %₁) +21 (call core.Typeof %₇) +22 slot₁/S +23 slot₃/U +24 (call core.svec %₂₁ core.Any %₂₂ %₂₃) +25 slot₂/T +26 slot₁/S +27 slot₃/U +28 (call core.svec %₂₅ %₂₆ %₂₇) +29 (call core.svec %₂₄ %₂₈ :($(QuoteNode(:(#= line 1 =#))))) +30 --- method core.nothing %₂₉ + 1 static_parameter₁ + 2 static_parameter₂ + 3 static_parameter₃ + 4 (call core.tuple slot₂/x slot₃/y slot₄/z %₁ %₂ %₃) + 5 (return %₄) +31 (return %₇) + +######################################## +# Default positional args are allowed before trailing slurp with no default +function f(x=1, ys...) + ys +end +#--------------------- +1 (method :f) +2 (call core.Typeof %₁) +3 (call core.svec %₂) +4 (call core.svec) +5 (call core.svec %₃ %₄ :($(QuoteNode(:(#= line 1 =#))))) +6 --- method core.nothing %₅ + 1 (call slot₁/#self# 1) + 2 (return %₁) +7 (call core.Typeof %₁) +8 (call core.apply_type core.Vararg core.Any) +9 (call core.svec %₇ core.Any %₈) +10 (call core.svec) +11 (call core.svec %₉ %₁₀ :($(QuoteNode(:(#= line 1 =#))))) +12 --- method core.nothing %₁₁ + 1 slot₃/ys + 2 (return %₁) +13 (return %₁) + +######################################## +# Error: Default positional args after a slurp +function f(x=1, ys..., z=2) + ys +end +#--------------------- +LoweringError: +function f(x=1, ys..., z=2) +# └────┘ ── `...` may only be used for the last function argument + ys +end + +######################################## +# Positional arg with slurp and default +function f(xs...=1) + xs +end +#--------------------- +1 (method :f) +2 (call core.Typeof %₁) +3 (call core.svec %₂) +4 (call core.svec) +5 (call core.svec %₃ %₄ :($(QuoteNode(:(#= line 1 =#))))) +6 --- method core.nothing %₅ + 1 (call slot₁/#self# 1) + 2 (return %₁) +7 (call core.Typeof %₁) +8 (call core.apply_type core.Vararg core.Any) +9 (call core.svec %₇ %₈) +10 (call core.svec) +11 (call core.svec %₉ %₁₀ :($(QuoteNode(:(#= line 1 =#))))) +12 --- method core.nothing %₁₁ + 1 slot₂/xs + 2 (return %₁) +13 (return %₁) + +######################################## +# Positional arg with slurp and splatted default value +function f(xs...=(1,2)...) + xs +end +#--------------------- +1 (method :f) +2 (call core.Typeof %₁) +3 (call core.svec %₂) +4 (call core.svec) +5 (call core.svec %₃ %₄ :($(QuoteNode(:(#= line 1 =#))))) +6 --- method core.nothing %₅ + 1 (call core.tuple 1 2) + 2 (call core._apply_iterate top.iterate slot₁/#self# %₁) + 3 (return %₂) +7 (call core.Typeof %₁) +8 (call core.apply_type core.Vararg core.Any) +9 (call core.svec %₇ %₈) +10 (call core.svec) +11 (call core.svec %₉ %₁₀ :($(QuoteNode(:(#= line 1 =#))))) +12 --- method core.nothing %₁₁ + 1 slot₂/xs + 2 (return %₁) +13 (return %₁) + ######################################## # Binding docs to functions """