Skip to content

Commit b3f59d9

Browse files
fertapricjosevalim
authored andcommitted
Raise compile errors on bad range types
Both sides of the range must be integers (after expansion), and the "first" value must be greater than the "last" value. These checks were previously performed by erl_lint.
1 parent 24ff3df commit b3f59d9

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

lib/elixir/lib/kernel/typespec.ex

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -588,9 +588,12 @@ defmodule Kernel.Typespec do
588588
end
589589

590590
# Handle ranges
591-
defp typespec({:.., meta, args}, vars, caller, state) do
592-
{args, state} = Enum.map_reduce(args, state, &typespec(&1, vars, caller, &2))
593-
{{:type, line(meta), :range, args}, state}
591+
defp typespec({:.., meta, [left, right]}, vars, caller, state) do
592+
{left, state} = typespec(left, vars, caller, state)
593+
{right, state} = typespec(right, vars, caller, state)
594+
:ok = validate_range(left, right, caller)
595+
596+
{{:type, line(meta), :range, [left, right]}, state}
594597
end
595598

596599
# Handle special forms
@@ -874,6 +877,26 @@ defmodule Kernel.Typespec do
874877
compile_error(caller, "unexpected list in typespec: #{Macro.to_string(original)}")
875878
end
876879

880+
defp validate_range({:op, _, :-, {:integer, meta, first}}, last, caller) do
881+
validate_range({:integer, meta, -first}, last, caller)
882+
end
883+
884+
defp validate_range(first, {:op, _, :-, {:integer, meta, last}}, caller) do
885+
validate_range(first, {:integer, meta, -last}, caller)
886+
end
887+
888+
defp validate_range({:integer, _, first}, {:integer, _, last}, _caller) when first < last do
889+
:ok
890+
end
891+
892+
defp validate_range(_, _, caller) do
893+
message =
894+
"invalid range specification, expected both sides to be integers, " <>
895+
"with the left side lower than the right side"
896+
897+
compile_error(caller, message)
898+
end
899+
877900
defp fn_args(meta, args, return, vars, caller, state) do
878901
{fun_args, state} = fn_args(meta, args, vars, caller, state)
879902
{spec, state} = typespec(return, vars, caller, state)

lib/elixir/test/elixir/typespec_test.exs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,25 @@ defmodule TypespecTest do
377377
test "@type with a range op" do
378378
bytecode =
379379
test_module do
380-
@type my_type :: 1..10
380+
@type range1 :: 1..10
381+
@type range2 :: -1..1
381382
end
382383

383-
assert [type: {:my_type, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []}] =
384-
types(bytecode)
384+
assert [
385+
{:type, {:range1, {:type, _, :range, range1_args}, []}},
386+
{:type, {:range2, {:type, _, :range, range2_args}, []}}
387+
] = types(bytecode)
388+
389+
assert [{:integer, _, 1}, {:integer, _, 10}] = range1_args
390+
assert [{:op, _, :-, {:integer, _, 1}}, {:integer, _, 1}] = range2_args
391+
end
392+
393+
test "@type with invalid range" do
394+
assert_raise CompileError, ~r"invalid range specification", fn ->
395+
test_module do
396+
@type my_type :: atom..10
397+
end
398+
end
385399
end
386400

387401
test "@type with a keyword map" do

0 commit comments

Comments
 (0)