Skip to content

Commit 624ae0c

Browse files
authored
Recompile regexes when escaped from module attributes (#14381)
1 parent 33b6e6d commit 624ae0c

File tree

2 files changed

+50
-13
lines changed

2 files changed

+50
-13
lines changed

lib/elixir/lib/kernel.ex

+31-13
Original file line numberDiff line numberDiff line change
@@ -3723,26 +3723,32 @@ defmodule Kernel do
37233723

37243724
case function? do
37253725
true ->
3726-
value =
3727-
case Module.__get_attribute__(env.module, name, line, false) do
3728-
{_, doc} when doc_attr? -> doc
3729-
other -> other
3730-
end
3726+
case Module.__get_attribute__(env.module, name, line, false) do
3727+
{_, doc} when doc_attr? ->
3728+
do_at_escape(name, doc)
3729+
3730+
%{__struct__: Regex, source: source, opts: opts} = regex ->
3731+
# TODO: Remove this in Elixir v2.0
3732+
IO.warn(
3733+
"storing and reading regexes from module attributes is deprecated, " <>
3734+
"inline the regex inside the function definition instead",
3735+
env
3736+
)
3737+
3738+
case :erlang.system_info(:otp_release) < [?2, ?8] do
3739+
true -> do_at_escape(name, regex)
3740+
false -> quote(do: Regex.compile!(unquote(source), unquote(opts)))
3741+
end
37313742

3732-
try do
3733-
:elixir_quote.escape(value, :none, false)
3734-
rescue
3735-
ex in [ArgumentError] ->
3736-
raise ArgumentError,
3737-
"cannot inject attribute @#{name} into function/macro because " <>
3738-
Exception.message(ex)
3743+
value ->
3744+
do_at_escape(name, value)
37393745
end
37403746

37413747
false when doc_attr? ->
37423748
quote do
37433749
case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do
37443750
{_, doc} -> doc
3745-
other -> other
3751+
value -> value
37463752
end
37473753
end
37483754

@@ -3777,6 +3783,17 @@ defmodule Kernel do
37773783
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
37783784
end
37793785

3786+
defp do_at_escape(name, value) do
3787+
try do
3788+
:elixir_quote.escape(value, :none, false)
3789+
rescue
3790+
ex in [ArgumentError] ->
3791+
raise ArgumentError,
3792+
"cannot inject attribute @#{name} into function/macro because " <>
3793+
Exception.message(ex)
3794+
end
3795+
end
3796+
37803797
# Those are always compile-time dependencies, so we can skip the trace.
37813798
defp collect_traces(:before_compile, arg, _env), do: {arg, []}
37823799
defp collect_traces(:after_compile, arg, _env), do: {arg, []}
@@ -6500,6 +6517,7 @@ defmodule Kernel do
65006517
end
65016518

65026519
defp compile_regex(binary_or_tuple, options) do
6520+
# TODO: Remove this when we require Erlang/OTP 28+
65036521
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
65046522
true ->
65056523
Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))

lib/elixir/test/elixir/regex_test.exs

+19
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@ defmodule RegexTest do
99

1010
doctest Regex
1111

12+
if System.otp_release() >= "28" do
13+
test "module attribute" do
14+
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
15+
defmodule ModAttr do
16+
@regex ~r/example/
17+
def regex, do: @regex
18+
19+
@bare_regex :erlang.term_to_binary(@regex)
20+
def bare_regex, do: :erlang.binary_to_term(@bare_regex)
21+
22+
# We don't rewrite outside of functions
23+
assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern
24+
end
25+
26+
assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern
27+
end) =~ "storing and reading regexes from module attributes is deprecated"
28+
end
29+
end
30+
1231
test "multiline" do
1332
refute Regex.match?(~r/^b$/, "a\nb\nc")
1433
assert Regex.match?(~r/^b$/m, "a\nb\nc")

0 commit comments

Comments
 (0)