From d59e92e49514b8797ab79ace515259da86fc6292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 30 Mar 2025 12:19:19 +0200 Subject: [PATCH 1/5] Recompile regexes when escaped from get attributes --- lib/elixir/lib/kernel.ex | 43 ++++++++++++++++++--------- lib/elixir/test/elixir/regex_test.exs | 19 ++++++++++++ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index e8f91d4615a..43437679ef3 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3723,26 +3723,29 @@ defmodule Kernel do case function? do true -> - value = - case Module.__get_attribute__(env.module, name, line, false) do - {_, doc} when doc_attr? -> doc - other -> other - end - - try do - :elixir_quote.escape(value, :none, false) - rescue - ex in [ArgumentError] -> - raise ArgumentError, - "cannot inject attribute @#{name} into function/macro because " <> - Exception.message(ex) + case Module.__get_attribute__(env.module, name, line, false) do + {_, doc} when doc_attr? -> + do_at_escape(name, doc) + + %{__struct__: Regex, source: source, opts: opts} -> + # TODO: Remove this in Elixir v2.0 + IO.warn( + "storing and reading regexes from module attributes is deprecated, " <> + "inline the regex inside the function definition instead", + env + ) + + quote(do: Regex.compile!(unquote(source), unquote(opts))) + + value -> + do_at_escape(name, value) end false when doc_attr? -> quote do case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do {_, doc} -> doc - other -> other + value -> value end end @@ -3777,6 +3780,17 @@ defmodule Kernel do raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}" end + defp do_at_escape(name, value) do + try do + :elixir_quote.escape(value, :none, false) + rescue + ex in [ArgumentError] -> + raise ArgumentError, + "cannot inject attribute @#{name} into function/macro because " <> + Exception.message(ex) + end + end + # Those are always compile-time dependencies, so we can skip the trace. defp collect_traces(:before_compile, arg, _env), do: {arg, []} defp collect_traces(:after_compile, arg, _env), do: {arg, []} @@ -6500,6 +6514,7 @@ defmodule Kernel do end defp compile_regex(binary_or_tuple, options) do + # TODO: Remove this when we require Erlang/OTP 28+ case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do true -> Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options))) diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs index 5db7ca0d661..5bb2e130126 100644 --- a/lib/elixir/test/elixir/regex_test.exs +++ b/lib/elixir/test/elixir/regex_test.exs @@ -9,6 +9,25 @@ defmodule RegexTest do doctest Regex + if System.otp_release() >= "28" do + test "module attribute" do + assert ExUnit.CaptureIO.capture_io(:stderr, fn -> + defmodule ModAttr do + @regex ~r/example/ + def regex, do: @regex + + @bare_regex :erlang.term_to_binary(@regex) + def bare_regex, do: :erlang.binary_to_term(@bare_regex) + + # We don't rewrite outside of functions + assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern + end + + assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern + end) =~ "storing and reading regexes from module attributes is deprecated" + end + end + test "multiline" do refute Regex.match?(~r/^b$/, "a\nb\nc") assert Regex.match?(~r/^b$/m, "a\nb\nc") From 62b722b78de5d59499596ce2a6b11066fdd106d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 30 Mar 2025 12:24:47 +0200 Subject: [PATCH 2/5] mix format --- lib/elixir/test/elixir/regex_test.exs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs index 5bb2e130126..189f8e3bf55 100644 --- a/lib/elixir/test/elixir/regex_test.exs +++ b/lib/elixir/test/elixir/regex_test.exs @@ -12,19 +12,19 @@ defmodule RegexTest do if System.otp_release() >= "28" do test "module attribute" do assert ExUnit.CaptureIO.capture_io(:stderr, fn -> - defmodule ModAttr do - @regex ~r/example/ - def regex, do: @regex + defmodule ModAttr do + @regex ~r/example/ + def regex, do: @regex - @bare_regex :erlang.term_to_binary(@regex) - def bare_regex, do: :erlang.binary_to_term(@bare_regex) + @bare_regex :erlang.term_to_binary(@regex) + def bare_regex, do: :erlang.binary_to_term(@bare_regex) - # We don't rewrite outside of functions - assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern - end + # We don't rewrite outside of functions + assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern + end - assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern - end) =~ "storing and reading regexes from module attributes is deprecated" + assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern + end) =~ "storing and reading regexes from module attributes is deprecated" end end From 5dd1f1b4b7a80f475a4952cdc06f0bf1cbd4164e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 30 Mar 2025 12:43:37 +0200 Subject: [PATCH 3/5] Update lib/elixir/lib/kernel.ex --- lib/elixir/lib/kernel.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 43437679ef3..6deca3ee91b 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3727,7 +3727,7 @@ defmodule Kernel do {_, doc} when doc_attr? -> do_at_escape(name, doc) - %{__struct__: Regex, source: source, opts: opts} -> + %{__struct__: Regex, source: source, opts: opts} = regex -> # TODO: Remove this in Elixir v2.0 IO.warn( "storing and reading regexes from module attributes is deprecated, " <> @@ -3735,7 +3735,11 @@ defmodule Kernel do env ) - quote(do: Regex.compile!(unquote(source), unquote(opts))) + if :erlang.system_info(:otp_release) < [?2, ?8] do + do_at_escape(name, regex) + else + quote(do: Regex.compile!(unquote(source), unquote(opts))) + end value -> do_at_escape(name, value) From 0f170f9ef3d798dd81e7f7802290adb90de8846e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 30 Mar 2025 12:45:29 +0200 Subject: [PATCH 4/5] Update lib/elixir/lib/kernel.ex --- lib/elixir/lib/kernel.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 6deca3ee91b..d8ffc6c153f 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3735,10 +3735,9 @@ defmodule Kernel do env ) - if :erlang.system_info(:otp_release) < [?2, ?8] do - do_at_escape(name, regex) - else - quote(do: Regex.compile!(unquote(source), unquote(opts))) + case :erlang.system_info(:otp_release) < [?2, ?8] do + true -> do_at_escape(name, regex) + false -> quote(do: Regex.compile!(unquote(source), unquote(opts))) end value -> From 116ecaae5e6cc9de4269b53b2e7dd662e8b7562c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 30 Mar 2025 13:19:15 +0200 Subject: [PATCH 5/5] Update lib/elixir/lib/kernel.ex --- lib/elixir/lib/kernel.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index d8ffc6c153f..e3827f84be3 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3738,7 +3738,7 @@ defmodule Kernel do case :erlang.system_info(:otp_release) < [?2, ?8] do true -> do_at_escape(name, regex) false -> quote(do: Regex.compile!(unquote(source), unquote(opts))) - end + end value -> do_at_escape(name, value)