From d783853f68ef94240351bad0e694a0184f5a611e Mon Sep 17 00:00:00 2001 From: sabiwara Date: Fri, 14 Nov 2025 07:47:24 +0900 Subject: [PATCH 1/3] Add Regex.import/1 --- lib/elixir/lib/regex.ex | 39 +++++++++++++++++++++++++-- lib/elixir/test/elixir/regex_test.exs | 13 +++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index 7a5c242476..c70d1e63b3 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -102,8 +102,8 @@ defmodule Regex do (the previous `r` option is deprecated in favor of `U`) * `:export` (E) (since Elixir 1.19.3) - uses an exported pattern - which can be shared across nodes or through config, at the cost of a runtime - overhead every time to re-import it every time it is executed. + which can be shared across nodes or passed through config, at the cost of a runtime + overhead to re-import it every time it is executed. This modifier only has an effect starting on Erlang/OTP 28, and it is ignored on older versions (i.e. `~r/foo/E == ~r/foo/`). This is because patterns cannot and do not need to be exported in order to be shared in these versions. @@ -275,6 +275,41 @@ defmodule Regex do regex end + @doc """ + Accepts an exported `regex` and imports it. + + This means it will lose the ability to be sent across nodes or passed through config, + but will be faster since it won't need to be imported on the fly every time it is executed. + + Importing only has an effect on Erlang OTP versions 28 or above. For older versions, + it does nothing and returns the `regex` as is. + + ## Examples + + Regex.import(~r/foo/E) + ~r/foo/ + + # on OTP 28+: + Regex.import(~r/foo/) + ** (ArgumentError) Expected an exported Regex, got: ~r/foo/ + + """ + @doc since: "1.19.4" + @spec import(t) :: t + def import(%Regex{re_pattern: re_pattern} = regex) do + case re_pattern do + {:re_exported_pattern, _, _, _, _} -> + %{regex | re_pattern: :re.import(re_pattern), opts: regex.opts -- [:export]} + + {:re_pattern, _, _, _, _} -> + if Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) do + raise ArgumentError, "Expected an exported Regex, got: #{inspect(regex)}" + else + regex + end + end + end + @doc """ Returns the version of the underlying Regex engine. """ diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs index 1fed09c248..671311c527 100644 --- a/lib/elixir/test/elixir/regex_test.exs +++ b/lib/elixir/test/elixir/regex_test.exs @@ -132,6 +132,19 @@ defmodule RegexTest do end end + @tag :re_import + test "import/1 (on exported Regex)" do + imported = Regex.import(~r/foo/E) + + assert imported.opts == [] + assert "foo" =~ imported + assert {:re_pattern, _, _, _, _} = imported.re_pattern + + assert_raise ArgumentError, "Expected an exported Regex, got: ~r/foo/", fn -> + Regex.import(~r/foo/) + end + end + test "opts/1" do assert Regex.opts(Regex.compile!("foo", "i")) == [:caseless] assert Regex.opts(Regex.compile!("foo", [:ucp])) == [:ucp] From 5efccba549c115d3fffc9fceba11dea9ea11c9c2 Mon Sep 17 00:00:00 2001 From: sabiwara Date: Fri, 14 Nov 2025 21:04:31 +0900 Subject: [PATCH 2/3] Make it a no-op for non-exported patterns --- lib/elixir/lib/regex.ex | 16 +++++----------- lib/elixir/test/elixir/regex_test.exs | 11 +++++------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index c70d1e63b3..4ca5f50f84 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -276,22 +276,20 @@ defmodule Regex do end @doc """ - Accepts an exported `regex` and imports it. + Imports a `regex` that has been exported, otherwise returns the `regex` unchanged. This means it will lose the ability to be sent across nodes or passed through config, but will be faster since it won't need to be imported on the fly every time it is executed. - Importing only has an effect on Erlang OTP versions 28 or above. For older versions, - it does nothing and returns the `regex` as is. + Exported regexes only exist on OTP 28, so this has no effect on older versions. ## Examples Regex.import(~r/foo/E) ~r/foo/ - # on OTP 28+: Regex.import(~r/foo/) - ** (ArgumentError) Expected an exported Regex, got: ~r/foo/ + ~r/foo/ """ @doc since: "1.19.4" @@ -301,12 +299,8 @@ defmodule Regex do {:re_exported_pattern, _, _, _, _} -> %{regex | re_pattern: :re.import(re_pattern), opts: regex.opts -- [:export]} - {:re_pattern, _, _, _, _} -> - if Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) do - raise ArgumentError, "Expected an exported Regex, got: #{inspect(regex)}" - else - regex - end + _ -> + regex end end diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs index 671311c527..926c53435c 100644 --- a/lib/elixir/test/elixir/regex_test.exs +++ b/lib/elixir/test/elixir/regex_test.exs @@ -132,17 +132,16 @@ defmodule RegexTest do end end - @tag :re_import - test "import/1 (on exported Regex)" do + test "import/1" do + # no-op for non-exported regexes + regex = ~r/foo/ + assert Regex.import(regex) == regex + imported = Regex.import(~r/foo/E) assert imported.opts == [] assert "foo" =~ imported assert {:re_pattern, _, _, _, _} = imported.re_pattern - - assert_raise ArgumentError, "Expected an exported Regex, got: ~r/foo/", fn -> - Regex.import(~r/foo/) - end end test "opts/1" do From d142babda8591c733a6caefee7c722b782d07811 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sat, 15 Nov 2025 07:15:51 +0900 Subject: [PATCH 3/3] Update doc since --- lib/elixir/lib/regex.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index 4ca5f50f84..b422a3e0bf 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -292,7 +292,7 @@ defmodule Regex do ~r/foo/ """ - @doc since: "1.19.4" + @doc since: "1.20.0" @spec import(t) :: t def import(%Regex{re_pattern: re_pattern} = regex) do case re_pattern do