Skip to content

Commit

Permalink
Support Erlang/OTP 28
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Feb 20, 2025
1 parent d800e9c commit c1e79d1
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 182 deletions.
82 changes: 23 additions & 59 deletions lib/elixir/lib/regex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ defmodule Regex do
check and recompile the regex if necessary.
"""

defstruct re_pattern: nil, source: "", opts: [], re_version: ""
defstruct re_pattern: nil, source: "", opts: []

@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary | [term]}

Expand All @@ -213,38 +213,35 @@ defmodule Regex do
## Examples
iex> Regex.compile("foo")
{:ok, ~r/foo/}
Regex.compile("foo")
#=> {:ok, ~r/foo/}
iex> Regex.compile("*foo")
{:error, {~c"nothing to repeat", 0}}
Regex.compile("foo", "i")
#=> {:ok, ~r/foo/i}
iex> Regex.compile("foo", "i")
{:ok, ~r/foo/i}
iex> Regex.compile("foo", [:caseless])
{:ok, Regex.compile!("foo", [:caseless])}
Regex.compile("*foo")
#=> {:error, {~c"quantifier does not follow a repeatable item", 0}}
"""
@spec compile(binary, binary | [term]) :: {:ok, t} | {:error, term}
def compile(source, opts \\ "") when is_binary(source) do
compile(source, opts, version())
do_compile(source, opts)
end

defp compile(source, opts, version) when is_binary(opts) do
defp do_compile(source, opts) when is_binary(opts) do
case translate_options(opts, []) do
{:error, rest} ->
{:error, {:invalid_option, rest}}

translated_opts ->
compile(source, translated_opts, version)
do_compile(source, translated_opts)
end
end

defp compile(source, opts, version) when is_list(opts) do
defp do_compile(source, opts) when is_list(opts) do
case :re.compile(source, opts) do
{:ok, re_pattern} ->
{:ok, %Regex{re_pattern: re_pattern, re_version: version, source: source, opts: opts}}
{:ok, %Regex{re_pattern: re_pattern, source: source, opts: opts}}

error ->
error
Expand All @@ -268,38 +265,29 @@ defmodule Regex do
This checks the version stored in the regular expression
and recompiles the regex in case of version mismatch.
"""
# Remove me on Elixir v1.22
@doc deprecated: "It can be removed and it has no effect"
@doc since: "1.4.0"
@spec recompile(t) :: {:ok, t} | {:error, any}
def recompile(%Regex{} = regex) do
version = version()

case regex do
%{re_version: ^version} ->
{:ok, regex}

_ ->
%{source: source, opts: opts} = regex
compile(source, opts, version)
end
{:ok, regex}
end

@doc """
Recompiles the existing regular expression and raises `Regex.CompileError` in case of errors.
"""
# Remove me on Elixir v1.22
@doc deprecated: "It can be removed and it has no effect"
@doc since: "1.4.0"
@spec recompile!(t) :: t
def recompile!(regex) do
case recompile(regex) do
{:ok, regex} -> regex
{:error, {reason, at}} -> raise Regex.CompileError, "#{reason} at position #{at}"
end
regex
end

@doc """
Returns the version of the underlying Regex engine.
"""
# Remove me on Elixir v1.22
@doc deprecated: "Use :re.version() instead"
@doc since: "1.4.0"
@spec version :: term()
def version do
{:re.version(), :erlang.system_info(:endian)}
end
Expand Down Expand Up @@ -455,17 +443,7 @@ defmodule Regex do
"""
@spec names(t) :: [String.t()]
def names(%Regex{re_pattern: compiled, re_version: version, source: source}) do
re_pattern =
case version() do
^version ->
compiled

_ ->
{:ok, recompiled} = :re.compile(source)
recompiled
end

def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
names
end
Expand Down Expand Up @@ -528,22 +506,8 @@ defmodule Regex do
end
end

defp safe_run(
%Regex{re_pattern: compiled, source: source, re_version: version, opts: compile_opts},
string,
options
) do
case version() do
^version ->
:re.run(string, compiled, options)

_ when is_list(compile_opts) ->
:re.run(string, source, compile_opts ++ options)

# TODO: This clause is kept for compatibility with previous Elixir versions. Remove on v2.0+.
_ when is_binary(compile_opts) ->
:re.run(string, source, translate_options(compile_opts, options))
end
defp safe_run(%Regex{re_pattern: re_pattern}, string, options) do
:re.run(string, re_pattern, options)
end

@doc """
Expand Down
7 changes: 6 additions & 1 deletion lib/elixir/src/elixir_erl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ dynamic_form(#{module := Module, relative_file := RelativeFile,
{Def, Defmacro, Macros, Exports, Functions} =
split_definition(Definitions, Unreachable, Line, [], [], [], [], {[], []}),

FilteredOpts = lists:filter(fun({no_warn_undefined, _}) -> false; (_) -> true end, Opts),
FilteredOpts = lists:filter(fun(
{no_warn_undefined, _}) -> false;
(debug_info) -> false;
(_) -> true
end, Opts),

Location = {elixir_utils:characters_to_list(RelativeFile), Line},

Prefix = [{attribute, Line, file, Location},
Expand Down
29 changes: 0 additions & 29 deletions lib/elixir/test/elixir/fixtures/dialyzer/protocol_opaque.ex

This file was deleted.

17 changes: 0 additions & 17 deletions lib/elixir/test/elixir/kernel/dialyzer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -120,23 +120,6 @@ defmodule Kernel.DialyzerTest do
assert_dialyze_no_warnings!(context)
end

@tag warnings: [:specdiffs]
test "no warnings on protocol calls with opaque types", context do
alias Dialyzer.ProtocolOpaque

copy_beam!(context, ProtocolOpaque)
copy_beam!(context, ProtocolOpaque.Entity)
copy_beam!(context, ProtocolOpaque.Entity.Any)
copy_beam!(context, ProtocolOpaque.Duck)
assert_dialyze_no_warnings!(context)

# Also ensure no warnings after consolidation.
Code.prepend_path(context.base_dir)
{:ok, binary} = Protocol.consolidate(ProtocolOpaque.Entity, [ProtocolOpaque.Duck, Any])
File.write!(Path.join(context.outdir, "#{ProtocolOpaque.Entity}.beam"), binary)
assert_dialyze_no_warnings!(context)
end

test "no warnings on and/2 and or/2", context do
copy_beam!(context, Dialyzer.BooleanCheck)
assert_dialyze_no_warnings!(context)
Expand Down
5 changes: 2 additions & 3 deletions lib/elixir/test/elixir/option_parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,9 @@ end
defmodule OptionsParserDeprecationsTest do
use ExUnit.Case, async: true

@warning ~r[not passing the :switches or :strict option to OptionParser is deprecated]

def assert_deprecated(fun) do
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~ @warning
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~
"not passing the :switches or :strict option to OptionParser is deprecated"
end

test "parses boolean option" do
Expand Down
70 changes: 0 additions & 70 deletions lib/elixir/test/elixir/regex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,6 @@ Code.require_file("test_helper.exs", __DIR__)
defmodule RegexTest do
use ExUnit.Case, async: true

@re_21_3_little %Regex{
re_pattern:
{:re_pattern, 1, 0, 0,
<<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133,
0, 7, 0, 1, 29, 100, 119, 0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
re_version: {"8.42 2018-03-20", :little},
source: "c(?<foo>d|e)"
}

@re_21_3_big %Regex{
re_pattern:
{:re_pattern, 1, 0, 0,
<<80, 67, 82, 69, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 17, 255, 255, 255, 255, 255, 255, 255,
255, 0, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 56, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133, 0, 7, 0, 1, 29, 100, 119,
0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
re_version: {"8.42 2018-03-20", :big},
source: "c(?<foo>d|e)"
}

@re_19_3_little %Regex{
re_pattern:
{:re_pattern, 1, 0, 0,
<<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 125, 0, 20, 29, 99, 127,
0, 7, 0, 1, 29, 100, 113, 0, 5, 29, 101, 114, 0, 12, 114, 0, 20, 0>>},
re_version: {"8.33 2013-05-29", :little},
source: "c(?<foo>d|e)"
}

doctest Regex

test "multiline" do
Expand Down Expand Up @@ -72,16 +39,9 @@ defmodule RegexTest do
test "literal source" do
assert Regex.source(Regex.compile!("foo")) == "foo"
assert Regex.source(~r"foo") == "foo"
assert Regex.re_pattern(Regex.compile!("foo")) == Regex.re_pattern(~r"foo")

assert Regex.source(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) == "\a\b\d\e\f\n\r\s\t\v"
assert Regex.source(~r<\a\b\d\e\f\n\r\s\t\v>) == "\\a\\b\\d\\e\\f\\n\\r\\s\\t\\v"

assert Regex.re_pattern(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) ==
Regex.re_pattern(~r"\x07\x08\x7F\x1B\x0C\x0A\x0D\x20\x09\x0B")

assert Regex.re_pattern(Regex.compile!("\\a\\b\\d\e\f\\n\\r\\s\\t\\v")) ==
Regex.re_pattern(~r"\a\b\d\e\f\n\r\s\t\v")
end

test "Unicode" do
Expand Down Expand Up @@ -120,16 +80,6 @@ defmodule RegexTest do
end
end

test "recompile/1" do
new_regex = ~r/foo/
{:ok, %Regex{}} = Regex.recompile(new_regex)
assert %Regex{} = Regex.recompile!(new_regex)

old_regex = Map.delete(~r/foo/, :re_version)
{:ok, %Regex{}} = Regex.recompile(old_regex)
assert %Regex{} = Regex.recompile!(old_regex)
end

test "opts/1" do
assert Regex.opts(Regex.compile!("foo", "i")) == [:caseless]
assert Regex.opts(Regex.compile!("foo", [:ucp])) == [:ucp]
Expand Down Expand Up @@ -183,16 +133,6 @@ defmodule RegexTest do
assert Regex.run(~r"bar", "foobar", offset: 2, return: :index) == [{3, 3}]
end

test "run/3 with regexes compiled in different systems" do
assert Regex.run(@re_21_3_little, "abcd abce", capture: :all_names) == ["d"]
assert Regex.run(@re_21_3_big, "abcd abce", capture: :all_names) == ["d"]
assert Regex.run(@re_19_3_little, "abcd abce", capture: :all_names) == ["d"]
end

test "run/3 with regexes with options compiled in different systems" do
assert Regex.run(%{~r/foo/i | re_version: "bad version"}, "FOO") == ["FOO"]
end

test "scan/2" do
assert Regex.scan(~r"c(d|e)", "abcd abce") == [["cd", "d"], ["ce", "e"]]
assert Regex.scan(~r"c(?:d|e)", "abcd abce") == [["cd"], ["ce"]]
Expand All @@ -211,16 +151,6 @@ defmodule RegexTest do
assert Regex.scan(~r"^foo", "foobar", offset: 1) == []
end

test "scan/2 with regexes compiled in different systems" do
assert Regex.scan(@re_21_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
assert Regex.scan(@re_21_3_big, "abcd abce", capture: :all_names) == [["d"], ["e"]]
assert Regex.scan(@re_19_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
end

test "scan/2 with regexes with options compiled in different systems" do
assert Regex.scan(%{~r/foo/i | re_version: "bad version"}, "FOO") == [["FOO"]]
end

test "split/2,3" do
assert Regex.split(~r",", "") == [""]
assert Regex.split(~r",", "", trim: true) == []
Expand Down
4 changes: 2 additions & 2 deletions lib/ex_unit/test/ex_unit/assertions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ defmodule ExUnit.AssertionsTest do
rescue
error in [ExUnit.AssertionError] ->
"foo" = error.left
~r{a} = error.right
%Regex{} = error.right
end
end

Expand All @@ -726,7 +726,7 @@ defmodule ExUnit.AssertionsTest do
rescue
error in [ExUnit.AssertionError] ->
"foo" = error.left
~r"o" = error.right
%Regex{} = error.right
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/iex/test/iex/helpers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ defmodule IEx.HelpersTest do
assert captured =~ "-spec sleep(Time) -> ok when Time :: timeout()."
else
assert captured =~ "sleep(Time)"
assert captured =~ "@spec sleep(time) :: :ok when time: timeout()"
assert captured =~ "@spec sleep(time) :: :ok when time: "
end
end

Expand Down

0 comments on commit c1e79d1

Please sign in to comment.