Skip to content

Commit 7908377

Browse files
committed
fix: support LiveView 1.1 comprehensions
1 parent c2efa51 commit 7908377

1 file changed

Lines changed: 65 additions & 13 deletions

File tree

lib/beacon/runtime_renderer.ex

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,7 +1426,7 @@ defmodule Beacon.RuntimeRenderer do
14261426
end
14271427

14281428
# Recursively search for {:=, [], [{:dynamic, ...}, fn_def]}
1429-
defp find_dynamic_fn({:=, [], [{:dynamic, [], _}, fn_ast]}), do: fn_ast
1429+
defp find_dynamic_fn({:=, _, [{:dynamic, _, _}, fn_ast]}), do: fn_ast
14301430

14311431
defp find_dynamic_fn({:__block__, [], children}) when is_list(children) do
14321432
Enum.find_value(children, fn child -> find_dynamic_fn(child) end)
@@ -1457,15 +1457,16 @@ defmodule Beacon.RuntimeRenderer do
14571457
# v1 = case ...
14581458
# [v0, v1, ...] (return list)
14591459
# end
1460-
defp extract_dynamics({:fn, [], [{:->, [], [[_track_changes], body]}]}) do
1461-
{:__block__, [], [_changed_setup | rest]} = body
1460+
defp extract_dynamics({:fn, _, [{:->, _, [[_track_changes], body]}]}) do
1461+
{:__block__, _, body_parts} = body
1462+
rest = drop_dynamic_setup(body_parts)
14621463

14631464
{all_assigns, return_vars} =
14641465
case rest do
14651466
# No dynamic expressions (static template): empty block + empty list
1466-
[{:__block__, [], []}, []] -> {[], []}
1467+
[{:__block__, _, []}, []] -> {[], []}
14671468
# Multiple dynamic expressions: __block__ wrapping assignments + return list
1468-
[{:__block__, [], assigns}, return_list] when is_list(assigns) -> {assigns, return_list}
1469+
[{:__block__, _, assigns}, return_list] when is_list(assigns) -> {assigns, return_list}
14691470
# Single dynamic expression: one assignment + return list
14701471
[{:=, _, _} = single_assign, return_list] -> {[single_assign], return_list}
14711472
# Fallback
@@ -1498,6 +1499,16 @@ defmodule Beacon.RuntimeRenderer do
14981499
binding_irs ++ output_irs
14991500
end
15001501

1502+
defp drop_dynamic_setup([{:=, _, [{:changed, _, Phoenix.LiveView.Engine}, _]} | rest]) do
1503+
drop_dynamic_setup(rest)
1504+
end
1505+
1506+
defp drop_dynamic_setup([{:=, _, [{:vars_changed, _, Phoenix.LiveView.Engine}, _]} | rest]) do
1507+
drop_dynamic_setup(rest)
1508+
end
1509+
1510+
defp drop_dynamic_setup(rest), do: rest
1511+
15011512
defp extract_one_dynamic({:=, _meta, [{_var, _, _}, case_expr]}) do
15021513
extract_case_expr(case_expr)
15031514
end
@@ -1663,11 +1674,21 @@ defmodule Beacon.RuntimeRenderer do
16631674
transform_comprehension(comp_struct, nil)
16641675
end
16651676

1677+
# Phoenix.LiveView.LiveStream.annotate_comprehension(struct, enum)
1678+
defp transform_expr({{:., [], [{:__aliases__, _, [:Phoenix, :LiveView, :LiveStream]}, :annotate_comprehension]}, [], [comp_struct, _enum]}) do
1679+
transform_comprehension(comp_struct, nil)
1680+
end
1681+
16661682
# Phoenix.LiveView.Comprehension.__mark_consumable__(enum)
16671683
defp transform_expr({{:., [], [{:__aliases__, _, [:Phoenix, :LiveView, :Comprehension]}, :__mark_consumable__]}, [], [enum_expr]}) do
16681684
transform_expr(enum_expr)
16691685
end
16701686

1687+
# Phoenix.LiveView.LiveStream.mark_consumable(enum)
1688+
defp transform_expr({{:., [], [{:__aliases__, _, [:Phoenix, :LiveView, :LiveStream]}, :mark_consumable]}, [], [enum_expr]}) do
1689+
transform_expr(enum_expr)
1690+
end
1691+
16711692
# List literal
16721693
defp transform_expr(list) when is_list(list) do
16731694
{:list, Enum.map(list, &transform_expr/1)}
@@ -1722,7 +1743,7 @@ defmodule Beacon.RuntimeRenderer do
17221743
# for comprehension expression
17231744
defp transform_expr({:for, _, [{:<-, _, [binding, enum_expr]} | opts]}) do
17241745
var_name = extract_var_name(binding)
1725-
body = Keyword.get(opts, :do, nil)
1746+
body = opts |> List.last() |> Keyword.get(:do, nil)
17261747
{:for_expr, var_name, transform_expr(enum_expr), transform_expr(body)}
17271748
end
17281749

@@ -1920,6 +1941,9 @@ defmodule Beacon.RuntimeRenderer do
19201941
{:=, [], [{:for, _, _}, {{:., [], [{:__aliases__, _, [:Phoenix, :LiveView, :Comprehension]}, :__mark_consumable__]}, [], [enum_expr]}]} ->
19211942
transform_expr(enum_expr)
19221943

1944+
{:=, [], [{:for, _, _}, {{:., [], [{:__aliases__, _, [:Phoenix, :LiveView, :LiveStream]}, :mark_consumable]}, [], [enum_expr]}]} ->
1945+
transform_expr(enum_expr)
1946+
19231947
_ ->
19241948
nil
19251949
end)
@@ -1928,6 +1952,9 @@ defmodule Beacon.RuntimeRenderer do
19281952
{{:., [], [{:__aliases__, _, [:Phoenix, :LiveView, :Comprehension]}, :__annotate__]}, [], [comp, _]} ->
19291953
{:ok, transform_comprehension(comp, enum_source)}
19301954

1955+
{{:., [], [{:__aliases__, _, [:Phoenix, :LiveView, :LiveStream]}, :annotate_comprehension]}, [], [comp, _]} ->
1956+
{:ok, transform_comprehension(comp, enum_source)}
1957+
19311958
_ ->
19321959
nil
19331960
end)
@@ -1936,13 +1963,18 @@ defmodule Beacon.RuntimeRenderer do
19361963
defp transform_comprehension({:%, [], [_aliases, {:%{}, [], fields}]}, enum_source) do
19371964
static = Keyword.fetch!(fields, :static)
19381965
fingerprint = Keyword.fetch!(fields, :fingerprint)
1939-
dynamics_expr = Keyword.fetch!(fields, :dynamics)
1966+
has_key? = Keyword.get(fields, :has_key?, false)
1967+
1968+
dynamics_ir =
1969+
cond do
1970+
dynamics_expr = fields[:dynamics] ->
1971+
transform_for_dynamics(dynamics_expr, enum_source)
19401972

1941-
# The dynamics is a `for` comprehension. Extract var name and body,
1942-
# but replace the enum with the actual source (from __mark_consumable__).
1943-
dynamics_ir = transform_for_dynamics(dynamics_expr, enum_source)
1973+
entries_expr = fields[:entries] ->
1974+
transform_for_entries(entries_expr, enum_source)
1975+
end
19441976

1945-
{:comprehension, %{static: static, fingerprint: fingerprint, dynamics: dynamics_ir}}
1977+
{:comprehension, %{static: static, fingerprint: fingerprint, dynamics: dynamics_ir, has_key?: has_key?}}
19461978
end
19471979

19481980
defp transform_for_dynamics({:for, _, [{:<-, _, [binding, _for_var]}, [do: body]]}, enum_source) do
@@ -1952,6 +1984,26 @@ defmodule Beacon.RuntimeRenderer do
19521984

19531985
defp transform_for_dynamics(other, _enum_source), do: transform_expr(other)
19541986

1987+
defp transform_for_entries({:for, _, [{:<-, _, [binding, _for_var]} | opts]}, enum_source) do
1988+
var_name = extract_var_name(binding)
1989+
body = opts |> List.last() |> Keyword.get(:do, nil)
1990+
{:for_expr, var_name, enum_source || {:literal, []}, transform_entry_body(body)}
1991+
end
1992+
1993+
defp transform_for_entries(other, _enum_source), do: transform_expr(other)
1994+
1995+
defp transform_entry_body({:{}, _, [_key_expr, _vars_map, {:fn, _, [{:->, _, [_args, body]}]}]}) do
1996+
transform_entry_fn_body(body)
1997+
end
1998+
1999+
defp transform_entry_body(other), do: transform_for_body(other)
2000+
2001+
defp transform_entry_fn_body({:__block__, _meta, parts}) do
2002+
parts |> List.last() |> transform_for_body()
2003+
end
2004+
2005+
defp transform_entry_fn_body(other), do: transform_for_body(other)
2006+
19552007
# The for body is typically: __block__ [v0 = live_to_iodata(item), [v0]]
19562008
# We need to extract the actual expressions and return them as a list.
19572009
# Some for bodies also contain local variable assignments from <% var = expr %>.
@@ -2403,7 +2455,7 @@ defmodule Beacon.RuntimeRenderer do
24032455
end
24042456

24052457
# Comprehension: produces %Phoenix.LiveView.Comprehension{}
2406-
defp eval_ir({:comprehension, %{static: static, fingerprint: fp, dynamics: dyn_expr}}, a, b) do
2458+
defp eval_ir({:comprehension, %{static: static, fingerprint: fp, dynamics: dyn_expr} = meta}, a, b) do
24072459
dynamics = eval_comprehension_dynamics(dyn_expr, a, b)
24082460

24092461
# Convert dynamics (list of lists) to entries format for LiveView 1.1+
@@ -2414,7 +2466,7 @@ defmodule Beacon.RuntimeRenderer do
24142466

24152467
%Phoenix.LiveView.Comprehension{
24162468
static: static,
2417-
has_key?: false,
2469+
has_key?: Map.get(meta, :has_key?, false),
24182470
entries: entries,
24192471
fingerprint: fp,
24202472
stream: nil

0 commit comments

Comments
 (0)