diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index e8f91d4615a..e3827f84be3 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3723,26 +3723,32 @@ 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 + 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} = regex -> + # 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 + ) + + 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 - 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) + 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 +3783,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 +6517,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..189f8e3bf55 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")