Skip to content

Commit 76c64a0

Browse files
committed
Recompile regexes when escaped from module attributes (#14381)
1 parent 6ac1d10 commit 76c64a0

File tree

2 files changed

+43
-13
lines changed

2 files changed

+43
-13
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3716,26 +3716,25 @@ defmodule Kernel do
37163716

37173717
case function? do
37183718
true ->
3719-
value =
3720-
case Module.__get_attribute__(env.module, name, line, false) do
3721-
{_, doc} when doc_attr? -> doc
3722-
other -> other
3723-
end
3719+
case Module.__get_attribute__(env.module, name, line, false) do
3720+
{_, doc} when doc_attr? ->
3721+
do_at_escape(name, doc)
3722+
3723+
%{__struct__: Regex, source: source, opts: opts} = regex ->
3724+
case :erlang.system_info(:otp_release) < [?2, ?8] do
3725+
true -> do_at_escape(name, regex)
3726+
false -> quote(do: Regex.compile!(unquote(source), unquote(opts)))
3727+
end
37243728

3725-
try do
3726-
:elixir_quote.escape(value, :none, false)
3727-
rescue
3728-
ex in [ArgumentError] ->
3729-
raise ArgumentError,
3730-
"cannot inject attribute @#{name} into function/macro because " <>
3731-
Exception.message(ex)
3729+
value ->
3730+
do_at_escape(name, value)
37323731
end
37333732

37343733
false when doc_attr? ->
37353734
quote do
37363735
case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do
37373736
{_, doc} -> doc
3738-
other -> other
3737+
value -> value
37393738
end
37403739
end
37413740

@@ -3770,6 +3769,17 @@ defmodule Kernel do
37703769
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
37713770
end
37723771

3772+
defp do_at_escape(name, value) do
3773+
try do
3774+
:elixir_quote.escape(value, :none, false)
3775+
rescue
3776+
ex in [ArgumentError] ->
3777+
raise ArgumentError,
3778+
"cannot inject attribute @#{name} into function/macro because " <>
3779+
Exception.message(ex)
3780+
end
3781+
end
3782+
37733783
# Those are always compile-time dependencies, so we can skip the trace.
37743784
defp collect_traces(:before_compile, arg, _env), do: {arg, []}
37753785
defp collect_traces(:after_compile, arg, _env), do: {arg, []}
@@ -6461,6 +6471,7 @@ defmodule Kernel do
64616471
end
64626472

64636473
defp compile_regex(binary_or_tuple, options) do
6474+
# TODO: Remove this when we require Erlang/OTP 28+
64646475
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
64656476
true ->
64666477
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
@@ -5,6 +5,25 @@ defmodule RegexTest do
55

66
doctest Regex
77

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

0 commit comments

Comments
 (0)