Skip to content

Commit da03542

Browse files
author
José Valim
committed
Merge pull request #2294 from ericmj/reraise
reraise should work like raise
2 parents f83eb30 + 2e24e22 commit da03542

File tree

6 files changed

+220
-35
lines changed

6 files changed

+220
-35
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,12 +1328,14 @@ defmodule Kernel do
13281328
end
13291329

13301330
@doc """
1331-
Raises an error.
1331+
Raises an exception.
13321332
13331333
If the argument is a binary, it raises `RuntimeError`
13341334
using the given argument as message.
13351335
1336-
If anything else, becomes a call to `raise(argument, [])`.
1336+
If an atom, it will become a call to `raise(atom, [])`.
1337+
1338+
If anything else, it will just raise the given exception.
13371339
13381340
## Examples
13391341
@@ -1372,15 +1374,19 @@ defmodule Kernel do
13721374
_ ->
13731375
quote do
13741376
case unquote(msg) do
1375-
msg when is_binary(msg) -> :erlang.error RuntimeError.exception(msg)
1376-
msg -> :erlang.error msg.exception([])
1377+
msg when is_binary(msg) ->
1378+
:erlang.error RuntimeError.exception(msg)
1379+
atom when is_atom(atom) ->
1380+
:erlang.error atom.exception([])
1381+
%{__struct__: struct, __exception__: true} = other when is_atom(struct) ->
1382+
:erlang.error other
13771383
end
13781384
end
13791385
end
13801386
end
13811387

13821388
@doc """
1383-
Raises an error.
1389+
Raises an exception.
13841390
13851391
Calls `.exception` on the given argument passing
13861392
the attributes in order to retrieve the appropriate exception
@@ -1402,7 +1408,16 @@ defmodule Kernel do
14021408
end
14031409

14041410
@doc """
1405-
Re-raises an exception with the given stacktrace.
1411+
Raises an exception preserving a previous stacktrace.
1412+
1413+
Works like `raise/1` but does not generate a new stacktrace.
1414+
1415+
Notice that `System.stacktrace` returns the stacktrace
1416+
of the last exception. That said, it is common to assign
1417+
the stacktrace as the first expression inside a `rescue`
1418+
clause as any other exception potentially raised (and
1419+
rescued) in between the rescue clause and the raise call
1420+
may change the `System.stacktrace` value.
14061421
14071422
## Examples
14081423
@@ -1415,24 +1430,59 @@ defmodule Kernel do
14151430
reraise exception, stacktrace
14161431
end
14171432
end
1418-
1419-
Notice that `System.stacktrace` returns the stacktrace
1420-
of the last exception. That said, it is common to assign
1421-
the stacktrace as the first expression inside a `rescue`
1422-
clause as any other exception potentially raised (and
1423-
rescued) in between the rescue clause and the raise call
1424-
may change the `System.stacktrace` value.
14251433
"""
1426-
defmacro reraise(exception, stacktrace) do
1427-
quote do
1428-
:erlang.raise :error, unquote(exception), unquote(stacktrace)
1434+
defmacro reraise(msg, stacktrace) do
1435+
# Try to figure out the type at compilation time
1436+
# to avoid dead code and make dialyzer happy.
1437+
1438+
case Macro.expand(msg, __CALLER__) do
1439+
msg when is_binary(msg) ->
1440+
quote do
1441+
:erlang.raise :error, RuntimeError.exception(unquote(msg)), unquote(stacktrace)
1442+
end
1443+
{:<<>>, _, _} = msg ->
1444+
quote do
1445+
:erlang.raise :error, RuntimeError.exception(unquote(msg)), unquote(stacktrace)
1446+
end
1447+
alias when is_atom(alias) ->
1448+
quote do
1449+
:erlang.raise :error, unquote(alias).exception([]), unquote(stacktrace)
1450+
end
1451+
msg ->
1452+
quote do
1453+
stacktrace = unquote(stacktrace)
1454+
case unquote(msg) do
1455+
msg when is_binary(msg) ->
1456+
:erlang.raise :error, RuntimeError.exception(msg), stacktrace
1457+
atom when is_atom(atom) ->
1458+
:erlang.raise :error, atom.exception([]), stacktrace
1459+
%{__struct__: struct, __exception__: true} = other when is_atom(struct) ->
1460+
:erlang.raise :error, other, stacktrace
1461+
end
1462+
end
14291463
end
14301464
end
14311465

1432-
@doc false
1433-
defmacro raise(exception, _attrs, stacktrace) do
1466+
@doc """
1467+
Raises an exception preserving a previous stacktrace.
1468+
1469+
Works like `raise/2` but does not generate a new stacktrace.
1470+
1471+
See `reraise/2` for more details.
1472+
1473+
## Examples
1474+
1475+
try do
1476+
raise "Oops"
1477+
rescue
1478+
exception ->
1479+
stacktrace = System.stacktrace
1480+
reraise WrapperError, [exception: exception], stacktrace
1481+
end
1482+
"""
1483+
defmacro reraise(exception, attrs, stacktrace) do
14341484
quote do
1435-
:erlang.raise :error, unquote(exception), unquote(stacktrace)
1485+
:erlang.raise :error, unquote(exception).exception(unquote(attrs)), unquote(stacktrace)
14361486
end
14371487
end
14381488

@@ -2894,7 +2944,7 @@ defmodule Kernel do
28942944
implemented;
28952945
28962946
Since exceptions are structs, all the API supported by `defstruct/1`
2897-
is available to in `defexception/1`.
2947+
is also available in `defexception/1`.
28982948
28992949
## Raising exceptions
29002950

lib/elixir/lib/stream.ex

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -656,8 +656,9 @@ defmodule Stream do
656656
user.(val, user_acc)
657657
catch
658658
kind, reason ->
659+
stacktrace = System.stacktrace
659660
next.({:halt, next_acc})
660-
:erlang.raise(kind, reason, :erlang.get_stacktrace)
661+
:erlang.raise(kind, reason, stacktrace)
661662
else
662663
{[], user_acc} ->
663664
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
@@ -700,8 +701,9 @@ defmodule Stream do
700701
next.({:halt, next_acc})
701702
{:halted, h}
702703
kind, reason ->
704+
stacktrace = System.stacktrace
703705
next.({:halt, next_acc})
704-
:erlang.raise(kind, reason, :erlang.get_stacktrace)
706+
:erlang.raise(kind, reason, stacktrace)
705707
else
706708
{_, acc} ->
707709
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -835,8 +837,9 @@ defmodule Stream do
835837
do_zip(zips, acc, callback, [], [])
836838
catch
837839
kind, reason ->
840+
stacktrace = System.stacktrace
838841
do_zip_close(zips)
839-
:erlang.raise(kind, reason, :erlang.get_stacktrace)
842+
:erlang.raise(kind, reason, stacktrace)
840843
else
841844
{:next, buffer, acc} ->
842845
do_zip(buffer, acc, callback)
@@ -1006,8 +1009,9 @@ defmodule Stream do
10061009
end
10071010
catch
10081011
kind, reason ->
1012+
stacktrace = System.stacktrace
10091013
after_fun.(next_acc)
1010-
:erlang.raise(kind, reason, :erlang.get_stacktrace)
1014+
:erlang.raise(kind, reason, stacktrace)
10111015
else
10121016
nil ->
10131017
after_fun.(next_acc)

lib/elixir/test/elixir/kernel/rescue_test.exs renamed to lib/elixir/test/elixir/kernel/raise_test.exs

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,138 @@
11
Code.require_file "../test_helper.exs", __DIR__
22

3-
defmodule Kernel.RescueTest do
3+
defmodule Kernel.RaiseTest do
44
use ExUnit.Case, async: true
55

6+
# Silence warnings
7+
defp atom, do: RuntimeError
8+
defp binary, do: "message"
9+
defp opts, do: [message: "message"]
10+
defp struct, do: %RuntimeError{message: "message"}
11+
12+
@trace [{:foo, :bar, 0, []}]
13+
14+
test "raise message" do
15+
assert_raise RuntimeError, "message", fn ->
16+
raise "message"
17+
end
18+
19+
assert_raise RuntimeError, "message", fn ->
20+
var = binary()
21+
raise var
22+
end
23+
end
24+
25+
test "raise with no arguments" do
26+
assert_raise RuntimeError, fn ->
27+
raise RuntimeError
28+
end
29+
30+
assert_raise RuntimeError, fn ->
31+
var = atom()
32+
raise var
33+
end
34+
end
35+
36+
test "raise with arguments" do
37+
assert_raise RuntimeError, "message", fn ->
38+
raise RuntimeError, message: "message"
39+
end
40+
41+
assert_raise RuntimeError, "message", fn ->
42+
atom = atom()
43+
opts = opts()
44+
raise atom, opts
45+
end
46+
end
47+
48+
test "raise existing exception" do
49+
assert_raise RuntimeError, "message", fn ->
50+
raise %RuntimeError{message: "message"}
51+
end
52+
53+
assert_raise RuntimeError, "message", fn ->
54+
var = struct()
55+
raise var
56+
end
57+
end
58+
59+
test "reraise message" do
60+
try do
61+
reraise "message", @trace
62+
flunk "should not reach"
63+
rescue
64+
RuntimeError ->
65+
assert @trace == :erlang.get_stacktrace()
66+
end
67+
68+
try do
69+
var = binary()
70+
reraise var, @trace
71+
flunk "should not reach"
72+
rescue
73+
RuntimeError ->
74+
assert @trace == :erlang.get_stacktrace()
75+
end
76+
end
77+
78+
test "reraise with no arguments" do
79+
try do
80+
reraise RuntimeError, @trace
81+
flunk "should not reach"
82+
rescue
83+
RuntimeError ->
84+
assert @trace == :erlang.get_stacktrace()
85+
end
86+
87+
try do
88+
var = atom()
89+
reraise var, @trace
90+
flunk "should not reach"
91+
rescue
92+
RuntimeError ->
93+
assert @trace == :erlang.get_stacktrace()
94+
end
95+
end
96+
97+
test "reraise with arguments" do
98+
try do
99+
reraise RuntimeError, [message: "message"], @trace
100+
flunk "should not reach"
101+
rescue
102+
RuntimeError ->
103+
assert @trace == :erlang.get_stacktrace()
104+
end
105+
106+
try do
107+
atom = atom()
108+
opts = opts()
109+
reraise atom, opts, @trace
110+
flunk "should not reach"
111+
rescue
112+
RuntimeError ->
113+
assert @trace == :erlang.get_stacktrace()
114+
end
115+
end
116+
117+
test "reraise existing exception" do
118+
try do
119+
reraise %RuntimeError{message: "message"}, @trace
120+
flunk "should not reach"
121+
rescue
122+
RuntimeError ->
123+
assert @trace == :erlang.get_stacktrace()
124+
end
125+
126+
try do
127+
var = struct()
128+
reraise var, @trace
129+
flunk "should not reach"
130+
rescue
131+
RuntimeError ->
132+
assert @trace == :erlang.get_stacktrace()
133+
end
134+
end
135+
6136
test :rescue_with_underscore_no_exception do
7137
result = try do
8138
RescueUndefinedModule.go
@@ -106,7 +236,7 @@ defmodule Kernel.RescueTest do
106236
x in [FunctionClauseError] -> Exception.message(x)
107237
end
108238

109-
assert result == "no function clause matching in Kernel.RescueTest.zero/1"
239+
assert result == "no function clause matching in Kernel.RaiseTest.zero/1"
110240
end
111241

112242
test :badarg_error do

lib/ex_unit/lib/ex_unit/doc_test.ex

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,10 @@ defmodule ExUnit.DocTest do
235235
unquote_splicing(tests)
236236
rescue
237237
e in [ExUnit.AssertionError] ->
238-
raise e, [], stack
238+
reraise e, [], stack
239239

240240
error ->
241-
raise ExUnit.AssertionError,
241+
reraise ExUnit.AssertionError,
242242
[message: "Doctest failed: got #{inspect(elem(error, 0))} with message #{Exception.message(error)}",
243243
expr: unquote(whole_expr)],
244244
stack
@@ -255,7 +255,7 @@ defmodule ExUnit.DocTest do
255255
case unquote(expr_ast) do
256256
^expected -> :ok
257257
actual ->
258-
raise ExUnit.AssertionError,
258+
reraise ExUnit.AssertionError,
259259
[message: "Doctest failed",
260260
expr: "#{unquote(String.strip(expr))} === #{unquote(String.strip(expected))}",
261261
left: actual],
@@ -273,7 +273,7 @@ defmodule ExUnit.DocTest do
273273
case unquote(expr_ast) do
274274
^expected -> :ok
275275
actual ->
276-
raise ExUnit.AssertionError,
276+
reraise ExUnit.AssertionError,
277277
[ message: "Doctest failed",
278278
expr: "inspect(#{unquote(String.strip(expr))}) === #{unquote(String.strip(expected))}",
279279
left: actual],
@@ -297,14 +297,14 @@ defmodule ExUnit.DocTest do
297297
unless error.__struct__ == unquote(exception) and
298298
Exception.message(error) == unquote(message) do
299299
got = inspect(elem(error, 0)) <> " with message " <> inspect(Exception.message(error))
300-
raise ExUnit.AssertionError,
300+
reraise ExUnit.AssertionError,
301301
[message: "Doctest failed: expected exception #{spec} but got #{got}",
302302
expr: expr],
303303
stack
304304
end
305305
else
306306
_ ->
307-
raise ExUnit.AssertionError,
307+
reraise ExUnit.AssertionError,
308308
[message: "Doctest failed: expected exception #{spec} but nothing was raised",
309309
expr: expr],
310310
stack
@@ -326,7 +326,7 @@ defmodule ExUnit.DocTest do
326326
e ->
327327
message = "(#{inspect e.__struct__}) #{Exception.message(e)}"
328328
quote do
329-
raise ExUnit.AssertionError,
329+
reraise ExUnit.AssertionError,
330330
[message: "Doctest did not compile, got: #{unquote(message)}",
331331
expr: unquote(String.strip(expr))]
332332
unquote(stack)

lib/mix/lib/mix/cli.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ defmodule Mix.CLI do
8383
end
8484
exit(1)
8585
else
86-
raise exception, [], stacktrace
86+
reraise exception, stacktrace
8787
end
8888
end
8989
end

0 commit comments

Comments
 (0)