Skip to content

Commit e5dd569

Browse files
committed
Desugaring of remaining let syntax forms
`let` was done very early in prototyping so only understood a very basic left hand side. Fix this to now support the remaining fancy syntax of type declarations, tuple unpacking and function definitions. There's a couple of variations in the lowering here vs the flisp lowering which I think are bug fixes: let f() = y local y # <- f does not capture this `y` end let x::T = rhs local T # x is of type `T` from outside the `let`, not this one. end Also some cleanup * Remove local_def in favour of using `local` and a separate `always_defined` intermediate form * Some cleanup in linearization.
1 parent e1679b0 commit e5dd569

File tree

8 files changed

+268
-47
lines changed

8 files changed

+268
-47
lines changed

JuliaLowering/src/ast.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,10 +594,45 @@ function is_simple_atom(ctx, ex)
594594
is_literal(k) || k == K"Symbol" || k == K"Value" || is_ssa(ctx, ex) || is_core_nothing(ex)
595595
end
596596

597+
function is_identifier_like(ex)
598+
k = kind(ex)
599+
k == K"Identifier" || k == K"BindingId" || k == K"Placeholder"
600+
end
601+
597602
function decl_var(ex)
598603
kind(ex) == K"::" ? ex[1] : ex
599604
end
600605

606+
# Given the signature of a `function`, return the symbol that will ultimately
607+
# be assigned to in local/global scope, if any.
608+
function assigned_function_name(ex)
609+
while kind(ex) == K"where"
610+
# f() where T
611+
ex = ex[1]
612+
end
613+
if kind(ex) == K"::" && numchildren(ex) == 2
614+
# f()::T
615+
ex = ex[1]
616+
end
617+
if kind(ex) != K"call"
618+
throw(LoweringError(ex, "Expected call syntax in function signature"))
619+
end
620+
ex = ex[1]
621+
if kind(ex) == K"curly"
622+
# f{T}()
623+
ex = ex[1]
624+
end
625+
if kind(ex) == K"::" || kind(ex) == K"."
626+
# (obj::CallableType)(args)
627+
# A.b.c(args)
628+
nothing
629+
elseif is_identifier_like(ex)
630+
ex
631+
else
632+
throw(LoweringError(ex, "Unexpected name in function signature"))
633+
end
634+
end
635+
601636
# Remove empty parameters block, eg, in the arg list of `f(x, y;)`
602637
function remove_empty_parameters(args)
603638
i = length(args)

JuliaLowering/src/desugaring.jl

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ end
2020

2121
#-------------------------------------------------------------------------------
2222

23-
function is_identifier_like(ex)
24-
k = kind(ex)
25-
k == K"Identifier" || k == K"BindingId" || k == K"Placeholder"
26-
end
27-
2823
# Return true when `x` and `y` are "the same identifier", but also works with
2924
# bindings (and hence ssa vars). See also `is_identifier_like()`
3025
function is_same_identifier_like(ex::SyntaxTree, y::SyntaxTree)
@@ -1394,22 +1389,71 @@ function expand_let(ctx, ex)
13941389
elseif kb == K"=" && numchildren(binding) == 2
13951390
lhs = binding[1]
13961391
rhs = binding[2]
1397-
if is_sym_decl(lhs)
1392+
kl = kind(lhs)
1393+
if kl == K"Identifier" || kl == K"BindingId"
13981394
blk = @ast ctx binding [K"block"
13991395
tmp := rhs
14001396
[K"scope_block"(ex, scope_type=scope_type)
1401-
# TODO: Use single child for scope_block?
1402-
[K"local_def"(lhs) lhs] # TODO: Use K"local" with attr?
1403-
[K"="(rhs)
1404-
decl_var(lhs)
1405-
tmp
1406-
]
1397+
[K"local"(lhs) lhs]
1398+
[K"always_defined" lhs]
1399+
[K"="(binding) lhs tmp]
1400+
blk
1401+
]
1402+
]
1403+
elseif kl == K"::"
1404+
var = lhs[1]
1405+
if !(kind(var) in KSet"Identifier BindingId")
1406+
throw(LoweringError(var, "Invalid assignment location in let syntax"))
1407+
end
1408+
blk = @ast ctx binding [K"block"
1409+
tmp := rhs
1410+
type := lhs[2]
1411+
[K"scope_block"(ex, scope_type=scope_type)
1412+
[K"local"(lhs) [K"::" var type]]
1413+
[K"always_defined" var]
1414+
[K"="(binding) var tmp]
1415+
blk
1416+
]
1417+
]
1418+
elseif kind(lhs) == K"tuple"
1419+
lhs_locals = SyntaxList(ctx)
1420+
foreach_lhs_var(lhs) do var
1421+
push!(lhs_locals, @ast ctx var [K"local" var])
1422+
push!(lhs_locals, @ast ctx var [K"always_defined" var])
1423+
end
1424+
blk = @ast ctx binding [K"block"
1425+
tmp := rhs
1426+
[K"scope_block"(ex, scope_type=scope_type)
1427+
lhs_locals...
1428+
[K"="(binding) lhs tmp]
14071429
blk
14081430
]
14091431
]
14101432
else
1411-
TODO("Functions and multiple assignment")
1433+
throw(LoweringError(lhs, "Invalid assignment location in let syntax"))
14121434
end
1435+
elseif kind(binding) == K"function"
1436+
sig = binding[1]
1437+
func_name = assigned_function_name(sig)
1438+
if isnothing(func_name)
1439+
# Some valid function syntaxes define methods on existing types and
1440+
# don't really make sense with let:
1441+
# let A.f() = 1 ... end
1442+
# let (obj::Callable)() = 1 ... end
1443+
throw(LoweringError(sig, "Function signature does not define a local function name"))
1444+
end
1445+
blk = @ast ctx binding [K"block"
1446+
[K"scope_block"(ex, scope_type=scope_type)
1447+
[K"local"(func_name) func_name]
1448+
[K"always_defined" func_name]
1449+
binding
1450+
[K"scope_block"(ex, scope_type=scope_type)
1451+
# The inside of the block is isolated from the closure,
1452+
# which itself can only capture values from the outside.
1453+
blk
1454+
]
1455+
]
1456+
]
14131457
else
14141458
throw(LoweringError(binding, "Invalid binding in let"))
14151459
continue
@@ -1801,10 +1845,14 @@ end
18011845

18021846
function foreach_lhs_var(f::Function, ex)
18031847
k = kind(ex)
1804-
if k == K"Identifier"
1848+
if k == K"Identifier" || k == K"BindingId"
18051849
f(ex)
18061850
elseif k == K"Placeholder"
18071851
# Ignored
1852+
elseif k == K"tuple"
1853+
for e in children(ex)
1854+
foreach_lhs_var(f, e)
1855+
end
18081856
else
18091857
TODO(ex, "LHS vars")
18101858
end
@@ -3091,7 +3139,8 @@ function expand_abstract_or_primitive_type(ctx, ex)
30913139
@ast ctx ex [K"block"
30923140
[K"scope_block"(scope_type=:hard)
30933141
[K"block"
3094-
[K"local_def" name]
3142+
[K"local" name]
3143+
[K"always_defined" name]
30953144
typevar_stmts...
30963145
[K"="
30973146
newtype_var
@@ -3624,7 +3673,8 @@ function expand_struct_def(ctx, ex, docs)
36243673
[K"block"
36253674
[K"global" global_struct_name]
36263675
[K"const" global_struct_name]
3627-
[K"local_def" struct_name]
3676+
[K"local" struct_name]
3677+
[K"always_defined" struct_name]
36283678
typevar_stmts...
36293679
[K"="
36303680
newtype_var

JuliaLowering/src/kinds.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function _register_kinds()
2121
"meta"
2222
# TODO: Use `meta` for inbounds and loopinfo etc?
2323
"inbounds"
24+
"boundscheck"
2425
"inline"
2526
"noinline"
2627
"loopinfo"
@@ -30,6 +31,11 @@ function _register_kinds()
3031
"opaque_closure"
3132
# Test whether a variable is defined
3233
"isdefined"
34+
# [K"throw_undef_if_not" var cond]
35+
# This form is used internally in Core.Compiler but might be
36+
# emitted by packages such as Diffractor. In principle it needs to
37+
# be passed through lowering in a similar way to `isdefined`
38+
"throw_undef_if_not"
3339
# named labels for `@label` and `@goto`
3440
"symbolic_label"
3541
# Goto named label
@@ -53,8 +59,11 @@ function _register_kinds()
5359
# Various heads harvested from flisp lowering.
5460
# (TODO: May or may not need all these - assess later)
5561
"break_block"
62+
# Like block, but introduces a lexical scope; used during scope resolution.
5663
"scope_block"
57-
"local_def" # TODO: Replace with K"local" plus BindingFlags attribute?
64+
# [K"always_defined" x] is an assertion that variable `x` is assigned before use
65+
# ('local-def in flisp implementation is K"local" plus K"always_defined"
66+
"always_defined"
5867
"_while"
5968
"_do_while"
6069
"_typevars" # used for supplying already-allocated `TypeVar`s to `where`

JuliaLowering/src/linear_ir.jl

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -268,17 +268,18 @@ function _actually_return(ctx, ex)
268268
end
269269

270270
function emit_return(ctx, srcref, ex)
271+
# todo: Mark implicit returns
271272
if isnothing(ex)
272273
return
273274
elseif isempty(ctx.handler_token_stack)
274275
_actually_return(ctx, ex)
275276
return
276277
end
277-
# FIXME: What's this !is_ssa(ctx, ex) here about?
278+
# TODO: What's this !is_ssa(ctx, ex) here about?
278279
x = if is_simple_atom(ctx, ex) && !(is_ssa(ctx, ex) && !isempty(ctx.finally_handlers))
279280
ex
280281
elseif !isempty(ctx.finally_handlers)
281-
# TODO: Why does flisp lowering create a mutable variable here even
282+
# todo: Why does flisp lowering create a mutable variable here even
282283
# though we don't mutate it?
283284
# tmp = ssavar(ctx, srcref, "returnval_via_finally") # <- can we use this?
284285
tmp = new_local_binding(ctx, srcref, "returnval_via_finally")
@@ -293,8 +294,6 @@ function emit_return(ctx, srcref, ex)
293294
emit(ctx, @ast ctx srcref [K"leave" ctx.handler_token_stack...])
294295
_actually_return(ctx, x)
295296
end
296-
# Should we return `x` here? The flisp code does, but that doesn't seem
297-
# useful as any returned value cannot be used?
298297
return nothing
299298
end
300299

@@ -563,12 +562,7 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
563562
k = kind(ex)
564563
if k == K"BindingId" || is_literal(k) || k == K"quote" || k == K"inert" ||
565564
k == K"top" || k == K"core" || k == K"Value" || k == K"Symbol" ||
566-
k == K"Placeholder" || k == K"SourceLocation"
567-
# TODO: other kinds: copyast $ globalref thismodule cdecl stdcall fastcall thiscall llvmcall
568-
if needs_value && k == K"Placeholder"
569-
# TODO: ensure outterref, globalref work here
570-
throw(LoweringError(ex, "all-underscore identifiers are write-only and their values cannot be used in expressions"))
571-
end
565+
k == K"SourceLocation"
572566
if in_tail_pos
573567
emit_return(ctx, ex)
574568
elseif needs_value
@@ -579,6 +573,14 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
579573
end
580574
nothing
581575
end
576+
elseif k == K"Placeholder"
577+
if needs_value
578+
throw(LoweringError(ex, "all-underscore identifiers are write-only and their values cannot be used in expressions"))
579+
end
580+
nothing
581+
elseif k == K"TOMBSTONE"
582+
@chk !needs_value (ex,"TOMBSTONE encountered in value position")
583+
nothing
582584
elseif k == K"call" || k == K"new" || k == K"splatnew" || k == K"foreigncall" ||
583585
k == K"new_opaque_closure"
584586
# TODO k ∈ cfunction cglobal
@@ -704,9 +706,6 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
704706
else
705707
nothing
706708
end
707-
elseif k == K"TOMBSTONE"
708-
@chk !needs_value (ex,"TOMBSTONE encountered in value position")
709-
nothing
710709
elseif k == K"if" || k == K"elseif"
711710
@chk numchildren(ex) <= 3
712711
has_else = numchildren(ex) > 2
@@ -828,7 +827,8 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
828827
end
829828
emit(ctx, ex)
830829
nothing
831-
elseif k == K"isdefined" || k == K"captured_local" # TODO || k == K"throw_undef_if_not" (See upstream #53875)
830+
elseif k == K"isdefined" || k == K"captured_local" || k == K"throw_undef_if_not" ||
831+
k == K"boundscheck"
832832
if in_tail_pos
833833
emit_return(ctx, ex)
834834
elseif needs_value

JuliaLowering/src/scope_analysis.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function _find_scope_vars!(ctx, assignments, locals, destructured_args, globals,
3636
elseif is_leaf(ex) || is_quoted(k) ||
3737
k in KSet"scope_block lambda module toplevel"
3838
return
39-
elseif k == K"local" || k == K"local_def"
39+
elseif k == K"local"
4040
if getmeta(ex, :is_destructured_arg, false)
4141
push!(destructured_args, ex[1])
4242
else
@@ -433,7 +433,7 @@ function _resolve_scopes(ctx, ex::SyntaxTree)
433433
end
434434
end
435435
ex_out
436-
elseif k == K"local_def"
436+
elseif k == K"always_defined"
437437
id = lookup_var(ctx, NameKey(ex[1]))
438438
update_binding!(ctx, id; is_always_defined=true)
439439
makeleaf(ctx, ex, K"TOMBSTONE")

JuliaLowering/test/branching.jl

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,6 @@ Base.eval(test_mod, quote
99
using JuliaSyntax
1010
end)
1111

12-
Base.eval(test_mod, quote
13-
function var"@label"(__context__::JuliaLowering.MacroContext, ex)
14-
@chk kind(ex) == JuliaSyntax.K"Identifier"
15-
@ast __context__ ex ex=>JuliaSyntax.K"symbolic_label"
16-
end
17-
18-
function var"@goto"(__context__::JuliaLowering.MacroContext, ex)
19-
@chk kind(ex) == JuliaSyntax.K"Identifier"
20-
@ast __context__ ex ex=>JuliaSyntax.K"symbolic_goto"
21-
end
22-
end)
23-
2412
#-------------------------------------------------------------------------------
2513
@testset "Tail position" begin
2614

JuliaLowering/test/scopes.jl

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,47 @@ end
1717
""") == (1, 2)
1818

1919
JuliaLowering.include_string(test_mod, """
20-
x = 101
21-
y = 202
20+
x = 101
21+
y = 202
2222
""")
2323
@test test_mod.x == 101
2424
@test test_mod.y == 202
2525
@test JuliaLowering.include_string(test_mod, "x + y") == 303
2626

27+
@test JuliaLowering.include_string(test_mod, """
28+
begin
29+
local x = 1
30+
local x = 2
31+
let (x,y) = (:x,:y)
32+
(y,x)
33+
end
34+
end
35+
""") === (:y,:x)
36+
37+
# Types on left hand side of type decls refer to the outer scope
38+
# (In the flisp implementation they refer to the inner scope, but this seems
39+
# like a bug.)
40+
@test JuliaLowering.include_string(test_mod, """
41+
let x::Int = 10.0
42+
local Int = Float64
43+
x
44+
end
45+
""") === 10
46+
47+
# Closures in let syntax can only capture values from the outside
48+
# (In the flisp implementation it captures from inner scope, but this is
49+
# inconsistent with let assignment where the rhs refers to the outer scope and
50+
# thus seems like a bug.)
51+
@test JuliaLowering.include_string(test_mod, """
52+
begin
53+
local y = :outer_y
54+
let f() = y
55+
local y = :inner_y
56+
f()
57+
end
58+
end
59+
""") === :outer_y
60+
2761
# wrap expression in scope block of `scope_type`
2862
function wrapscope(ex, scope_type)
2963
g = JuliaLowering.ensure_attributes(ex._graph, scope_type=Symbol)

0 commit comments

Comments
 (0)