Skip to content

Commit d59e92e

Browse files
committed
Recompile regexes when escaped from get attributes
1 parent 5a6735e commit d59e92e

File tree

2 files changed

+48
-14
lines changed

2 files changed

+48
-14
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3723,26 +3723,29 @@ 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
3731-
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)
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} ->
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+
quote(do: Regex.compile!(unquote(source), unquote(opts)))
3739+
3740+
value ->
3741+
do_at_escape(name, value)
37393742
end
37403743

37413744
false when doc_attr? ->
37423745
quote do
37433746
case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do
37443747
{_, doc} -> doc
3745-
other -> other
3748+
value -> value
37463749
end
37473750
end
37483751

@@ -3777,6 +3780,17 @@ defmodule Kernel do
37773780
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
37783781
end
37793782

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

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

lib/elixir/test/elixir/regex_test.exs

Lines changed: 19 additions & 0 deletions
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)