Skip to content

Commit 6b2423a

Browse files
authored
Use recursion when marking variables for change tracking (#3852)
1 parent 44fa879 commit 6b2423a

File tree

2 files changed

+40
-57
lines changed

2 files changed

+40
-57
lines changed

lib/phoenix_live_view/tag_engine.ex

Lines changed: 33 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ defmodule Phoenix.LiveView.TagEngine do
11971197
# validate_quoted_special_attr
11981198
{:<-, for_meta, [lhs, rhs]} = for_expr
11991199
# now we mark all eligible variables in the left-hand side as `:change_track`able
1200-
{lhs, variables} = mark_variables_as_change_tracked(lhs)
1200+
{lhs, variables} = mark_variables_as_change_tracked(lhs, %{})
12011201
for_expr = {:<-, for_meta, [lhs, rhs]}
12021202

12031203
# now we build the new ast that we pass to the engine
@@ -1206,7 +1206,7 @@ defmodule Phoenix.LiveView.TagEngine do
12061206
Phoenix.LiveView.TagEngine.keyed_comprehension(
12071207
{unquote(state.caller.module), unquote(tag_meta.line), unquote(tag_meta.column),
12081208
unquote(key_expr)},
1209-
%{unquote_splicing(variables)},
1209+
%{unquote_splicing(Map.to_list(variables))},
12101210
do: unquote(invoke_subengine(state, :handle_end, [[meta: [root: true]]]))
12111211
)
12121212
end
@@ -1228,57 +1228,39 @@ defmodule Phoenix.LiveView.TagEngine do
12281228
end
12291229

12301230
@doc false
1231-
def mark_variables_as_change_tracked(ast) do
1232-
# we traverse the AST and mark elements that should not considered
1233-
# to be variables as "skip";
1234-
# we need to handle both the pre and post phases to properly unmark
1235-
# the skip after we handled an expression
1236-
{ast, {_, vars}} =
1237-
Macro.traverse(
1238-
ast,
1239-
# we start in collect mode (true) and an empty list of vars
1240-
{true, []},
1241-
fn
1242-
# skip pinned expressions, marking the pin expression and setting
1243-
# collect to false
1244-
{:^, meta, [expr]}, {true, vars} ->
1245-
{{:^, Keyword.put(meta, :skip_mark, true), [expr]}, {false, vars}}
1246-
1247-
# skip the right hand side in something like <<foo::binary>>
1248-
{:"::", meta, [left, right]}, {true, vars} ->
1249-
# we need to return a list for prewalk to walk the left hand side
1250-
{new_left, inner_vars} = mark_variables_as_change_tracked(left)
1251-
1252-
{{:"::", Keyword.put(meta, :skip_mark, true), [new_left, right]},
1253-
{false, vars ++ inner_vars}}
1254-
1255-
# collect mode -> mark the variable as change_track and collect
1256-
# it for passing it to the KeyedComprehension component
1257-
{name, meta, context}, {true, vars}
1258-
when is_atom(name) and is_list(meta) and is_atom(context) ->
1259-
var = {name, Keyword.put(meta, :change_track, true), context}
1260-
{var, {true, [{name, var} | vars]}}
1261-
1262-
other, acc ->
1263-
{other, acc}
1264-
end,
1265-
fn
1266-
{op, meta, args}, {false, vars} when is_atom(op) and is_list(meta) and is_list(args) ->
1267-
case Keyword.pop(meta, :skip_mark) do
1268-
{nil, meta} ->
1269-
{{op, meta, args}, {false, vars}}
1270-
1271-
{true, meta} ->
1272-
# reset the collect mode
1273-
{{op, meta, args}, {true, vars}}
1274-
end
1231+
def mark_variables_as_change_tracked({:^, _, [_]} = ast, vars) do
1232+
{ast, vars}
1233+
end
12751234

1276-
other, acc ->
1277-
{other, acc}
1278-
end
1279-
)
1235+
def mark_variables_as_change_tracked({:"::", meta, [left, right]}, vars) do
1236+
{left, vars} = mark_variables_as_change_tracked(left, vars)
1237+
{{:"::", meta, [left, right]}, vars}
1238+
end
12801239

1281-
{ast, vars}
1240+
def mark_variables_as_change_tracked({name, meta, context}, vars)
1241+
when is_atom(name) and is_list(meta) and is_atom(context) do
1242+
var = {name, [change_track: true] ++ meta, context}
1243+
{var, Map.put(vars, name, var)}
1244+
end
1245+
1246+
def mark_variables_as_change_tracked({left, meta, right}, vars) do
1247+
{left, vars} = mark_variables_as_change_tracked(left, vars)
1248+
{right, vars} = mark_variables_as_change_tracked(right, vars)
1249+
{{left, meta, right}, vars}
1250+
end
1251+
1252+
def mark_variables_as_change_tracked({left, right}, vars) do
1253+
{left, vars} = mark_variables_as_change_tracked(left, vars)
1254+
{right, vars} = mark_variables_as_change_tracked(right, vars)
1255+
{{left, right}, vars}
1256+
end
1257+
1258+
def mark_variables_as_change_tracked([_ | _] = list, vars) do
1259+
Enum.map_reduce(list, vars, &mark_variables_as_change_tracked/2)
1260+
end
1261+
1262+
def mark_variables_as_change_tracked(other, vars) do
1263+
{other, vars}
12821264
end
12831265

12841266
## build_self_close_component_assigns/build_component_assigns

test/phoenix_live_view/tag_engine_test.exs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ defmodule Phoenix.LiveView.TagEngineTest do
1010
%{foo: foo, bar: ^bar, bin: <<thebin::binary>>, other: other}
1111
end
1212

13-
assert {new_ast, variables} = TagEngine.mark_variables_as_change_tracked(ast)
13+
assert {new_ast, variables} = TagEngine.mark_variables_as_change_tracked(ast, %{})
14+
assert map_size(variables) == 3
1415

15-
assert [
16-
{:foo, {:foo, [change_track: true], _}},
17-
{:other, {:other, [change_track: true], _}},
18-
{:thebin, {:thebin, [change_track: true], _}}
19-
] = Enum.sort_by(variables, &elem(&1, 0))
16+
assert %{
17+
foo: {:foo, [change_track: true], _},
18+
other: {:other, [change_track: true], _},
19+
thebin: {:thebin, [change_track: true], _}
20+
} = variables
2021

2122
assert new_ast != ast
2223
end

0 commit comments

Comments
 (0)