Skip to content

Commit

Permalink
Property destructuring
Browse files Browse the repository at this point in the history
  • Loading branch information
c42f committed Sep 23, 2024
1 parent 71a647d commit 45ef358
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 20 deletions.
69 changes: 52 additions & 17 deletions src/desugaring.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,6 @@ function _tuple_sides_match(lhs, rhs)
end
end

function _in_assignment_lhs(lhss, x_rhs)
for e in lhss
x = kind(e) == K"..." ? e[1] : e
if is_same_identifier_like(x, x_rhs)
return true
end
end
return false
end

# Lower `(lhss...) = rhs` in contexts where `rhs` must be a tuple at runtime
# by assuming that `getfield(rhs, i)` works and is efficient.
function lower_tuple_assignment(ctx, assignment_srcref, lhss, rhs)
Expand Down Expand Up @@ -267,7 +257,43 @@ function _destructure(ctx, assignment_srcref, stmts, lhs, rhs)
stmts
end

# Expands all cases of general tuple destructuring
# Expands cases of property destructuring
function expand_property_destruct(ctx, ex)
@assert numchildren(ex) == 2
lhs = ex[1]
@assert kind(lhs) == K"tuple"
if numchildren(lhs) != 1
throw(LoweringError(ex, "Property destructuring must use a single `;` before the property names, eg `(; a, b) = rhs`"))
end
params = lhs[1]
@assert kind(params) == K"parameters"
rhs = ex[2]
stmts = SyntaxList(ctx)
rhs1 = if is_ssa(ctx, rhs) || (is_identifier_like(rhs) &&
!any(is_same_identifier_like(l, rhs) for l in children(params)))
rhs
else
emit_assign_tmp(stmts, ctx, expand_forms_2(ctx, rhs))
end
for prop in children(params)
propname = kind(prop) == K"Identifier" ? prop :
kind(prop) == K"::" && kind(prop[1]) == K"Identifier" ? prop[1] :
throw(LoweringError(prop, "invalid assignment location"))
push!(stmts, expand_forms_2(ctx, @ast ctx rhs1 [K"="
prop
[K"call"
"getproperty"::K"top"
rhs1
propname=>K"Symbol"
]
]))
end
push!(stmts, @ast ctx rhs1 [K"unnecessary" rhs1])
makenode(ctx, ex, K"block", stmts)
end

# Expands all cases of general tuple destructuring, eg
# (x,y) = (a,b)
function expand_tuple_destruct(ctx, ex)
lhs = ex[1]
@assert kind(lhs) == K"tuple"
Expand All @@ -281,14 +307,23 @@ function expand_tuple_destruct(ctx, ex)
end
end

if kind(rhs) == K"tuple" && !any_assignment(children(rhs)) &&
!has_parameters(rhs) && _tuple_sides_match(children(lhs), children(rhs))
return expand_forms_2(ctx, tuple_to_assignments(ctx, ex))
if kind(rhs) == K"tuple"
num_splat = sum(kind(rh) == K"..." for rh in children(rhs))
if num_splat == 0 && (numchildren(lhs) - num_slurp) > numchildren(rhs)
throw(LoweringError(ex, "More variables on left hand side than right hand in tuple assignment"))
end

if !any_assignment(children(rhs)) && !has_parameters(rhs) &&
_tuple_sides_match(children(lhs), children(rhs))
return expand_forms_2(ctx, tuple_to_assignments(ctx, ex))
end
end

stmts = SyntaxList(ctx)
rhs1 = if is_ssa(ctx, rhs) || (is_identifier_like(rhs) &&
!_in_assignment_lhs(children(lhs), rhs))
rhs1 = if is_ssa(ctx, rhs) ||
(is_identifier_like(rhs) &&
!any(is_same_identifier_like(kind(l) == K"..." ? l[1] : l, rhs)
for l in children(lhs)))
rhs
else
emit_assign_tmp(stmts, ctx, expand_forms_2(ctx, rhs))
Expand Down Expand Up @@ -418,7 +453,7 @@ function expand_assignment(ctx, ex)
elseif kl == K"tuple"
# TODO: has_parameters
if has_parameters(lhs)
TODO(lhs, "Destructuring with named fields")
expand_property_destruct(ctx, ex)
else
expand_tuple_destruct(ctx, ex)
end
Expand Down
15 changes: 15 additions & 0 deletions test/demo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,21 @@ let x = 1
end
"""

src = """
let
(a, bs...,) = (1,2,3)
bs
end
"""

src = """
let
rhs = 1 + 2*Base.im
(; im, re) = rhs
(re, im)
end
"""

ex = parsestmt(SyntaxTree, src, filename="foo.jl")
ex = ensure_attributes(ex, var_id=Int)
#ex = softscope_test(ex)
Expand Down
18 changes: 17 additions & 1 deletion test/destructuring.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ end
@testset "Tuples on both sides" begin

# lhs variable name in rhs
@test_broken JuliaLowering.include_string(test_mod, """
@test JuliaLowering.include_string(test_mod, """
let
x = 1
y = 2
Expand All @@ -112,4 +112,20 @@ end

end

@testset "Property destructuring" begin

# TODO: Move named tuple inside test case once we can lower it
Base.eval(test_mod, :(some_named_tuple = (a=1,b=2)))
@test JuliaLowering.include_string(test_mod, """
let
(; a, b) = some_named_tuple
(a, b)
end
""") == (1, 2)

@test_throws LoweringError JuliaLowering.include_string(test_mod, "(x ; a, b) = rhs")
@test_throws LoweringError JuliaLowering.include_string(test_mod, "(; a=1, b) = rhs")

end

end
60 changes: 60 additions & 0 deletions test/destructuring_ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,63 @@ end
9 (call core.tuple %%%₈)
10 (return %₉)

########################################
# Property destructuring
let
(; x, y) = rhs
end
#---------------------
1 TestMod.rhs
2 (= slot₁/x (call top.getproperty %:x))
3 TestMod.rhs
4 (= slot₂/y (call top.getproperty %:y))
5 TestMod.rhs
6 (return %₅)

########################################
# Property destructuring with colliding symbolic lhs/rhs
let
local x
(; x, y) = x
end
#---------------------
1 slot₁/x
2 (= slot₁/x (call top.getproperty %:x))
3 (= slot₂/y (call top.getproperty %:y))
4 (return %₁)

########################################
# Property destructuring with nontrivial rhs
let
(; x) = f()
end
#---------------------
1 TestMod.f
2 (call %₁)
3 (= slot₁/x (call top.getproperty %:x))
4 (return %₂)

########################################
# Property destructuring with type decl
let
(; x::T) = rhs
end
#---------------------
1 TestMod.rhs
2 (call top.getproperty %:x)
3 (= slot₂/tmp %₂)
4 slot₂/tmp
5 TestMod.T
6 (call core.isa %%₅)
7 (gotoifnot %₆ label₉)
8 (goto label₁₄)
9 TestMod.T
10 slot₂/tmp
11 (call top.convert %%₁₀)
12 TestMod.T
13 (= slot₂/tmp (call core.typeassert %₁₁ %₁₂))
14 slot₂/tmp
15 (= slot₁/x %₁₄)
16 TestMod.rhs
17 (return %₁₆)

9 changes: 7 additions & 2 deletions test/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,16 @@ function desugar(mod::Module, src::String)
end

function match_ir_test_case(case_str)
m = match(r"# *([^\n]*)\n((?:.|\n)*)#----*\n((?:.|\n)*)"m, strip(case_str))
m = match(r"# *([^\n]*)\n((?:.|\n)*)"m, strip(case_str))
if isnothing(m)
error("Malformatted IR test case:\n$(repr(case_str))")
end
(description=strip(m[1]), input=strip(m[2]), output=strip(m[3]))
description = strip(m[1])
inout = split(m[2], r"#----*")
input, output = length(inout) == 2 ? inout :
length(inout) == 1 ? (inout[1], "") :
error("Too many sections in IR test case")
(; description=strip(description), input=strip(input), output=strip(output))
end

function read_ir_test_cases(filename)
Expand Down

0 comments on commit 45ef358

Please sign in to comment.