Skip to content

Commit 176c86b

Browse files
committed
Fix quoting
1 parent 46d4aa3 commit 176c86b

File tree

6 files changed

+157
-12
lines changed

6 files changed

+157
-12
lines changed

lib/elixir/lib/module/types/expr.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,10 @@ defmodule Module.Types.Expr do
522522
end
523523
end
524524

525+
def of_expr(node, expected, expr, stack, context) do
526+
Module.Types.Literal.of_expr(node, expected, expr, stack, context)
527+
end
528+
525529
## Tuples
526530

527531
defp of_tuple(elems, _expected, expr, %{mode: :traversal} = stack, context) do
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule Module.Types.Literal do
2+
import Module.Types.Descr
3+
4+
def of_expr({tag, _meta, _bracket_type, _args}, _expected, _expr, _stack, context)
5+
when tag in [
6+
:sequence_block,
7+
:sequence_literal,
8+
:sequence_paren,
9+
:sequence_brace,
10+
:sequence_bracket
11+
] do
12+
{dynamic(), context}
13+
end
14+
end

lib/elixir/src/elixir_expand.erl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,11 @@ expand(Pid, S, E) when is_pid(Pid) ->
556556
{Pid, S, E}
557557
end;
558558

559-
expand(Other, _S, E) ->
560-
file_error([{line, 0}], ?key(E, file), ?MODULE, {invalid_quoted_expr, Other}).
559+
expand(Node, S, E) ->
560+
case elixir_literal:expand(Node, S, E) of
561+
false -> file_error([{line, 0}], ?key(E, file), ?MODULE, {invalid_quoted_expr, Node});
562+
Result -> Result
563+
end.
561564

562565
%% Helpers
563566

@@ -1291,4 +1294,4 @@ format_error({parens_map_lookup, Map, Field, Context}) ->
12911294
format_error({super_in_genserver, {Name, Arity}}) ->
12921295
io_lib:format("calling super for GenServer callback ~ts/~B is deprecated", [Name, Arity]);
12931296
format_error('__cursor__') ->
1294-
"reserved special form __cursor__ cannot be expanded, it is used exclusively to annotate ASTs".
1297+
"reserved special form __cursor__ cannot be expanded, it is used exclusively to annotate ASTs".

lib/elixir/src/elixir_literal.erl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
%% Module for handling sequence literal AST nodes
2+
%% This module centralizes all sequence-related logic for the Elixir compiler
3+
-module(elixir_literal).
4+
5+
-export([is_sequence_node/1, is_valid_ast/1, quote_node/2, escape_node/2, expand/3]).
6+
7+
%% Check if a node is a sequence node
8+
is_sequence_node({sequence_block, _Meta, _BracketType, _Args}) -> true;
9+
is_sequence_node({sequence_literal, _Meta, _Args}) -> true;
10+
is_sequence_node({sequence_paren, _Meta, _Args}) -> true;
11+
is_sequence_node({sequence_bracket, _Meta, _Args}) -> true;
12+
is_sequence_node({sequence_brace, _Meta, _Args}) -> true;
13+
is_sequence_node(_) -> false.
14+
15+
%% Validate sequence AST nodes
16+
%% For sequence_block (4-tuple format), validate the inner args
17+
is_valid_ast({sequence_block, Meta, BracketType, Args})
18+
when is_list(Meta), is_atom(BracketType), is_list(Args) ->
19+
% For sequence_block, we assume the args are valid since they will be processed by Paxir
20+
true;
21+
%% For other sequence nodes (3-tuple format), they're always valid
22+
is_valid_ast({sequence_literal, Meta, Args})
23+
when is_list(Meta), is_list(Args) -> true;
24+
is_valid_ast({sequence_paren, Meta, Args})
25+
when is_list(Meta), is_list(Args) -> true;
26+
is_valid_ast({sequence_bracket, Meta, Args})
27+
when is_list(Meta), is_list(Args) -> true;
28+
is_valid_ast({sequence_brace, Meta, Args})
29+
when is_list(Meta), is_list(Args) -> true;
30+
is_valid_ast(_) -> false.
31+
32+
%% Quote sequence nodes - keep them as-is without transforming internals
33+
quote_node({sequence_block, _Meta, _BracketType, _Args} = Node, _Q) -> Node;
34+
quote_node({sequence_literal, _Meta, _Args} = Node, _Q) -> Node;
35+
quote_node({sequence_paren, _Meta, _Args} = Node, _Q) -> Node;
36+
quote_node({sequence_bracket, _Meta, _Args} = Node, _Q) -> Node;
37+
quote_node({sequence_brace, _Meta, _Args} = Node, _Q) -> Node;
38+
quote_node(_, _) -> false.
39+
40+
%% Escape sequence nodes - keep them as-is without transforming internals
41+
escape_node({sequence_block, _Meta, _BracketType, _Args} = Node, _Q) -> Node;
42+
escape_node({sequence_literal, _Meta, _Args} = Node, _Q) -> Node;
43+
escape_node({sequence_paren, _Meta, _Args} = Node, _Q) -> Node;
44+
escape_node({sequence_bracket, _Meta, _Args} = Node, _Q) -> Node;
45+
escape_node({sequence_brace, _Meta, _Args} = Node, _Q) -> Node;
46+
escape_node(_, _) -> false.
47+
48+
%% Expand sequence nodes - keep them as-is since they will be processed by Paxir
49+
expand({sequence_block, _Meta, _BracketType, _Args} = Node, S, E) -> {Node, S, E};
50+
expand({sequence_literal, _Meta, _Args} = Node, S, E) -> {Node, S, E};
51+
expand({sequence_paren, _Meta, _Args} = Node, S, E) -> {Node, S, E};
52+
expand({sequence_bracket, _Meta, _Args} = Node, S, E) -> {Node, S, E};
53+
expand({sequence_brace, _Meta, _Args} = Node, S, E) -> {Node, S, E};
54+
expand(_, _, _) -> false.

lib/elixir/src/elixir_quote.erl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,11 @@ do_escape(Fun, _) when is_function(Fun) ->
195195
false -> bad_escape(Fun)
196196
end;
197197

198-
do_escape(Other, _) ->
199-
bad_escape(Other).
198+
do_escape(Node, Q) ->
199+
case elixir_literal:escape_node(Node, Q) of
200+
false -> bad_escape(Node);
201+
Result -> Result
202+
end.
200203

201204
escape_map_key_value(K, V, Map, Q) ->
202205
MaybeRef = if
@@ -305,7 +308,9 @@ valid_ast_elem(Expr) when is_list(Expr); is_atom(Expr); is_binary(Expr); is_numb
305308
valid_ast_elem({Left, Right}) -> valid_ast_elem(Left) andalso valid_ast_elem(Right);
306309
valid_ast_elem({Atom, Meta, Args}) when is_atom(Atom), is_list(Meta), is_atom(Args) orelse is_list(Args) -> true;
307310
valid_ast_elem({Call, Meta, Args}) when is_list(Meta), is_list(Args) -> shallow_valid_ast(Call);
308-
valid_ast_elem(_Term) -> false.
311+
%% Support sequence nodes - delegate to elixir_literal module
312+
valid_ast_elem(Node) ->
313+
elixir_literal:is_valid_ast(Node).
309314

310315
quote({unquote_splicing, _, [_]}, #elixir_quote{unquote=true}) ->
311316
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
@@ -447,8 +452,11 @@ do_quote([H | T], #elixir_quote{unquote=false} = Q) ->
447452
do_quote([H | T], Q) ->
448453
do_quote_tail(lists:reverse(T, [H]), Q);
449454

450-
do_quote(Other, _) ->
451-
Other.
455+
do_quote(Node, Q) ->
456+
case elixir_literal:quote_node(Node, Q) of
457+
false -> Node;
458+
Result -> Result
459+
end.
452460

453461
import_meta(Meta, Name, Arity, Q, E) ->
454462
case (keyfind(imports, Meta) == false) andalso

lib/elixir/test/elixir/kernel/parser_sequence_literal_test.exs

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ defmodule Kernel.ParserSequenceLiteralTest do
355355
end
356356

357357
test "comments with different token types" do
358-
# Comments should work with numbers and atoms
358+
# Comments should work with numbers and atoms
359359
result_with_comment = parse!("~~(42 # comment\n:atom)")
360360
result_without_comment = parse!("~~(42\n:atom)")
361361

@@ -438,16 +438,79 @@ defmodule Kernel.ParserSequenceLiteralTest do
438438
test "comments at different positions in multiline sequence literals" do
439439
# Test comments at various positions to isolate the issue
440440

441-
# Comment at the beginning - this might work
442441
result1 = parse!("# comment before\n~~(valid code)")
443442
assert {:sequence_literal, [line: 2], _} = result1
444443

445-
# Comment at the end - this might work
446444
result2 = parse!("~~(valid code)\n# comment after")
447445
assert {:sequence_literal, [line: 1], _} = result2
448446
end
449447
end
450448

449+
describe "sequence blocks in quote expressions" do
450+
test "sequence_block nodes with tuple destructuring patterns compile correctly" do
451+
# This test reproduces the issue where sequence_block nodes with tuple patterns
452+
# inside quoted expressions fail to compile
453+
454+
# The issue occurs when we have a nested structure like:
455+
# {:sequence_block, meta, :"()", [{:sequence_block, meta2, :{}, [elements]}]}
456+
# This represents something like ({a b}) in the sequence literal
457+
458+
ast =
459+
{:sequence_literal, [line: 1],
460+
[
461+
{:sequence_paren, [line: 1],
462+
[
463+
{:def, [line: 1], nil},
464+
{:match_tuple, [line: 1], nil},
465+
{:sequence_block, [line: 1, column: 24], :"()",
466+
[
467+
{:sequence_block, [line: 1, column: 25], :{},
468+
[
469+
{:a, [line: 1, column: 26], nil},
470+
{:b, [line: 1, column: 28], nil}
471+
]}
472+
]},
473+
{:a, [line: 1], nil}
474+
]}
475+
]}
476+
477+
# This should not raise an error when used in a quote block
478+
result =
479+
quote do
480+
unquote(ast)
481+
end
482+
483+
# Verify the AST is preserved correctly
484+
assert {:sequence_literal, _, _} = result
485+
end
486+
487+
test "sequence_block with parentheses type compiles in quote" do
488+
# Test that sequence_block with :"()" type works in quote expressions
489+
ast = {:sequence_block, [line: 1], :"()", [{:a, [line: 1], nil}]}
490+
491+
# Should not raise an error
492+
result =
493+
quote do
494+
unquote(ast)
495+
end
496+
497+
assert {:sequence_block, _, :"()", _} = result
498+
end
499+
500+
test "sequence_block with braces type compiles in quote" do
501+
# Test that sequence_block with :{} type works in quote expressions
502+
ast = {:sequence_block, [line: 1], :{}, [{:x, [line: 1], nil}, {:y, [line: 1], nil}]}
503+
504+
# Should not raise an error
505+
result =
506+
quote do
507+
unquote(ast)
508+
end
509+
510+
assert {:sequence_block, _, :{}, _} = result
511+
end
512+
end
513+
451514
describe "tokenizer behavior verification" do
452515
# These tests verify that our tokenizer produces the expected token types
453516

@@ -534,7 +597,6 @@ defmodule Kernel.ParserSequenceLiteralTest do
534597
_ -> false
535598
end)
536599

537-
# Should have sequence_end token
538600
has_end =
539601
Enum.any?(tokens, fn
540602
{:sequence_end, {_, _, _}, :")"} -> true

0 commit comments

Comments
 (0)