Skip to content

Commit f47e748

Browse files
committed
Use new OTP28.1 :re.import in escaped regex AST
erlang/otp#9976
1 parent 5a8ba81 commit f47e748

File tree

3 files changed

+56
-7
lines changed

3 files changed

+56
-7
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6646,16 +6646,26 @@ defmodule Kernel do
66466646
end
66476647

66486648
defp compile_regex(binary_or_tuple, options) do
6649-
# TODO: Remove this when we require Erlang/OTP 28+
6650-
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
6649+
bin_opts = :binary.list_to_bin(options)
6650+
6651+
# TODO: Remove this when we require Erlang/OTP 28.1+
6652+
case is_binary(binary_or_tuple) and compile_time_regexes_supported?() do
66516653
true ->
6652-
Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
6654+
Macro.escape(Regex.compile!(binary_or_tuple, bin_opts))
66536655

66546656
false ->
6655-
quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(:binary.list_to_bin(options))))
6657+
quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(bin_opts)))
66566658
end
66576659
end
66586660

6661+
defp compile_time_regexes_supported? do
6662+
# OTP 28.0 introduced refs in patterns, which can't be used in AST anymore
6663+
# OTP 28.1 introduced :re.import/1 which allows us to fix this in Macro.escape
6664+
:erlang.system_info(:otp_release) < [?2, ?8] or
6665+
(Code.ensure_loaded?(:re) and
6666+
function_exported?(:re, :import, 1))
6667+
end
6668+
66596669
@doc ~S"""
66606670
Handles the sigil `~D` for dates.
66616671

lib/elixir/src/elixir_quote.erl

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,28 @@ do_escape(BitString, _) when is_bitstring(BitString) ->
168168
{'<<>>', [], [{'::', [], [Bits, {size, [], [Size]}]}, {'::', [], [Bytes, {binary, [], nil}]}]}
169169
end;
170170

171+
do_escape(#{
172+
'__struct__' := 'Elixir.Regex',
173+
're_pattern' := {re_pattern, _, _, _, Ref},
174+
'source' := Source,
175+
'opts' := Opts
176+
} = Map, Q) when is_reference(Ref), is_binary(Source), is_list(Opts) ->
177+
case erlang:function_exported(re, import, 1) of
178+
true ->
179+
{ok, ExportedPattern} = re:compile(Source, [export | Opts]),
180+
PatternAst = {{'.', [], ['re', 'import']}, [], [do_escape(ExportedPattern, Q)]},
181+
{'%{}', [], [
182+
{'__struct__', 'Elixir.Regex'},
183+
{'re_pattern', PatternAst},
184+
{'source', Source},
185+
{'opts', do_escape(Opts, Q)}
186+
]};
187+
false ->
188+
escape_map(Map, Q)
189+
end;
190+
171191
do_escape(Map, Q) when is_map(Map) ->
172-
TT = [escape_map_key_value(K, V, Map, Q) || {K, V} <- lists:sort(maps:to_list(Map))],
173-
{'%{}', [], TT};
192+
escape_map(Map, Q);
174193

175194
do_escape([], _) ->
176195
[];
@@ -203,6 +222,10 @@ do_escape(Fun, _) when is_function(Fun) ->
203222
do_escape(Other, _) ->
204223
bad_escape(Other).
205224

225+
escape_map(Map, Q) ->
226+
TT = [escape_map_key_value(K, V, Map, Q) || {K, V} <- lists:sort(maps:to_list(Map))],
227+
{'%{}', [], TT}.
228+
206229
escape_map_key_value(K, V, Map, Q) ->
207230
MaybeRef = if
208231
is_reference(V) -> V;

lib/elixir/test/elixir/macro_test.exs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,27 @@ defmodule MacroTest do
141141
assert Macro.escape({:quote, [], [[do: :foo]]}) == {:{}, [], [:quote, [], [[do: :foo]]]}
142142
end
143143

144-
test "inspects container when a reference cannot be escaped" do
144+
@tag skip: System.otp_release() < "28" or function_exported?(:re, :import, 1)
145+
test "escape container when a reference cannot be escaped" do
145146
assert_raise ArgumentError, ~r"~r/foo/ contains a reference", fn ->
146147
Macro.escape(%{~r/foo/ | re_pattern: {:re_pattern, 0, 0, 0, make_ref()}})
147148
end
148149
end
150+
151+
@tag skip: not function_exported?(:re, :import, 1)
152+
test "escape regex will remove references and replace it by a call to :re.import/1" do
153+
assert {
154+
:%{},
155+
[],
156+
[
157+
__struct__: Regex,
158+
re_pattern:
159+
{{:., [], [:re, :import]}, [], [{:{}, [], [:re_exported_pattern | _]}]},
160+
source: "foo",
161+
opts: []
162+
]
163+
} = Macro.escape(%{~r/foo/ | re_pattern: {:re_pattern, 0, 0, 0, make_ref()}})
164+
end
149165
end
150166

151167
describe "expand_once/2" do

0 commit comments

Comments
 (0)