Skip to content

Commit

Permalink
Raise BadBooleanError on and/or, closes elixir-lang#5297
Browse files Browse the repository at this point in the history
  • Loading branch information
José Valim committed Nov 20, 2016
1 parent ac8bedb commit 6b8db41
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 40 deletions.
12 changes: 12 additions & 0 deletions lib/elixir/lib/exception.ex
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,14 @@ defmodule BadMapError do
end
end

defmodule BadBooleanError do
defexception [:term, :operator]

def message(exception) do
"expected a boolean on left-side of \"#{exception.operator}\", got: #{inspect(exception.term)}"
end
end

defmodule MatchError do
defexception [:term]

Expand Down Expand Up @@ -861,6 +869,10 @@ defmodule ErlangError do
%BadMapError{term: term}
end

def normalize({:badbool, op, term}, _stacktrace) do
%BadBooleanError{operator: op, term: term}
end

def normalize({:badkey, key}, stacktrace) do
term =
case stacktrace || :erlang.get_stacktrace do
Expand Down
8 changes: 4 additions & 4 deletions lib/elixir/src/elixir_exp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,13 @@ expand({{'.', Meta, [erlang, 'orelse']}, _, [Left, Right]}, #{context := nil} =
Generated = ?generated(Meta),
TrueClause = {'->', Generated, [[true], true]},
FalseClause = {'->', Generated, [[false], Right]},
expand_boolean_check(Left, TrueClause, FalseClause, Meta, Env);
expand_boolean_check('or', Left, TrueClause, FalseClause, Meta, Env);

expand({{'.', Meta, [erlang, 'andalso']}, _, [Left, Right]}, #{context := nil} = Env) ->
Generated = ?generated(Meta),
TrueClause = {'->', Generated, [[true], Right]},
FalseClause = {'->', Generated, [[false], false]},
expand_boolean_check(Left, TrueClause, FalseClause, Meta, Env);
expand_boolean_check('and', Left, TrueClause, FalseClause, Meta, Env);

expand({{'.', DotMeta, [Left, Right]}, Meta, Args}, E)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
Expand Down Expand Up @@ -402,15 +402,15 @@ expand(Other, E) ->

%% Helpers

expand_boolean_check(Expr, TrueClause, FalseClause, Meta, Env) ->
expand_boolean_check(Op, Expr, TrueClause, FalseClause, Meta, Env) ->
{EExpr, EnvExpr} = expand(Expr, Env),
Clauses =
case elixir_utils:returns_boolean(EExpr) of
true ->
[TrueClause, FalseClause];
false ->
Other = {other, Meta, ?MODULE},
OtherExpr = {{'.', Meta, [erlang, error]}, Meta, [{badarg, Other}]},
OtherExpr = {{'.', Meta, [erlang, error]}, Meta, [{'{}', [], [badbool, Op, Other]}]},
[TrueClause, FalseClause, {'->', ?generated(Meta), [[Other], OtherExpr]}]
end,
{EClauses, EnvCase} = elixir_exp_clauses:'case'(Meta, [{do, Clauses}], EnvExpr),
Expand Down
5 changes: 5 additions & 0 deletions lib/elixir/src/elixir_try.erl
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.BadMapError') ->
erl_tuple_size(Meta, Var, 2),
erl_record_compare(Meta, Var, badmap));

erl_rescue_guard_for(Meta, Var, 'Elixir.BadBooleanError') ->
erl_and(Meta,
erl_tuple_size(Meta, Var, 3),
erl_record_compare(Meta, Var, badbool));

erl_rescue_guard_for(Meta, Var, 'Elixir.KeyError') ->
erl_and(Meta,
erl_or(Meta,
Expand Down
10 changes: 10 additions & 0 deletions lib/elixir/test/elixir/kernel/raise_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,16 @@ defmodule Kernel.RaiseTest do
assert result == "expected a map, got: 0"
end

test "bad boolean error" do
result = try do
1 and true
rescue
x in [BadBooleanError] -> Exception.message(x)
end

assert result == "expected a boolean on left-side of \"and\", got: 1"
end

test "case clause error" do
x = :example
result = try do
Expand Down
22 changes: 22 additions & 0 deletions lib/elixir/test/elixir/kernel_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,28 @@ defmodule KernelTest do
end
end

test "and/2" do
assert (true and false) == false
assert (true and true) == true
assert (true and 0) == 0
assert (false and false) == false
assert (false and true) == false
assert (false and 0) == false
assert (false and raise "oops") == false
assert_raise BadBooleanError, fn -> 0 and 1 end
end

test "or/2" do
assert (true or false) == true
assert (true or true) == true
assert (true or 0) == true
assert (true or raise "foo") == true
assert (false or false) == false
assert (false or true) == true
assert (false or 0) == 0
assert_raise BadBooleanError, fn -> 0 or 1 end
end

describe "in/2" do
test "with literals on right side" do
assert 2 in [1, 2, 3]
Expand Down
36 changes: 0 additions & 36 deletions lib/elixir/test/erlang/control_test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -123,42 +123,6 @@ integer_and_float_test() ->
{false, _} = eval("1 === 1.0"),
{true, _} = eval("1 !== 1.0").

and_test() ->
F = fun() ->
eval("defmodule Bar do\ndef foo, do: true\ndef bar, do: false\n def baz(x), do: x == 1\nend"),
{true, _} = eval("true and true"),
{false, _} = eval("true and false"),
{false, _} = eval("false and true"),
{false, _} = eval("false and false"),
{true, _} = eval("Bar.foo and Bar.foo"),
{false, _} = eval("Bar.foo and Bar.bar"),
{true, _} = eval("Bar.foo and Bar.baz 1"),
{false, _} = eval("Bar.foo and Bar.baz 2"),
{true, _} = eval("false and false or true"),
{3, _} = eval("Bar.foo and 1 + 2"),
{false, _} = eval("Bar.bar and :erlang.error(:bad)"),
?assertError({badarg, 1}, eval("1 and 2"))
end,
test_helper:run_and_remove(F, ['Elixir.Bar']).

or_test() ->
F = fun() ->
eval("defmodule Bar do\ndef foo, do: true\ndef bar, do: false\n def baz(x), do: x == 1\nend"),
{true, _} = eval("true or true"),
{true, _} = eval("true or false"),
{true, _} = eval("false or true"),
{false, _} = eval("false or false"),
{true, _} = eval("Bar.foo or Bar.foo"),
{true, _} = eval("Bar.foo or Bar.bar"),
{false, _} = eval("Bar.bar or Bar.bar"),
{true, _} = eval("Bar.bar or Bar.baz 1"),
{false, _} = eval("Bar.bar or Bar.baz 2"),
{3, _} = eval("Bar.bar or 1 + 2"),
{true, _} = eval("Bar.foo or :erlang.error(:bad)"),
?assertError({badarg, 1}, eval("1 or 2"))
end,
test_helper:run_and_remove(F, ['Elixir.Bar']).

not_test() ->
{false, _} = eval("not true"),
{true, _} = eval("not false"),
Expand Down

0 comments on commit 6b8db41

Please sign in to comment.