diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 13d96127012..8f941e4124e 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -435,6 +435,12 @@ defmodule Module.Types.Apply do end end + defp do_remote(:erlang, :element, [index, tuple], _expected, expr, stack, context, of_fun) + when is_integer(index) and index < 1 do + {_, context} = of_fun.(tuple, open_tuple([]), expr, stack, context) + remote_error({:negindex, index - 1}, :erlang, :element, 2, expr, stack, context) + end + defp do_remote(:erlang, :element, [index, tuple], expected, expr, stack, context, of_fun) when is_integer(index) do tuple_type = open_tuple(List.duplicate(term(), max(index - 1, 0)) ++ [expected]) @@ -458,6 +464,13 @@ defmodule Module.Types.Apply do {return(tuple(List.duplicate(elem_type, size)), [elem_type], stack), context} end + defp do_remote(:erlang, :insert_element, [index, tuple, elem], _, expr, stack, context, of_fun) + when is_integer(index) and index < 1 do + {_, context} = of_fun.(tuple, open_tuple([]), expr, stack, context) + {_, context} = of_fun.(elem, term(), expr, stack, context) + remote_error({:negindex, index - 1}, :erlang, :insert_element, 3, expr, stack, context) + end + defp do_remote(:erlang, :insert_element, [index, tuple, elem], _, expr, stack, context, of_fun) when is_integer(index) do tuple_type = open_tuple(List.duplicate(term(), max(index - 1, 0))) @@ -479,6 +492,12 @@ defmodule Module.Types.Apply do end end + defp do_remote(:erlang, :delete_element, [index, tuple], _, expr, stack, context, of_fun) + when is_integer(index) and index < 1 do + {_, context} = of_fun.(tuple, open_tuple([]), expr, stack, context) + remote_error({:negindex, index - 1}, :erlang, :delete_element, 2, expr, stack, context) + end + defp do_remote(:erlang, :delete_element, [index, tuple], _, expr, stack, context, of_fun) when is_integer(index) do tuple_type = open_tuple(List.duplicate(term(), max(index, 1))) @@ -1807,6 +1826,29 @@ defmodule Module.Types.Apply do } end + def format_diagnostic({{:negindex, index}, mfac, expr, context}) do + traces = collect_traces(expr, context) + {mod, fun, arity, _converter} = mfac + mfa = Exception.format_mfa(mod, fun, arity) + + %{ + details: %{typing_traces: traces}, + message: + IO.iodata_to_binary([ + """ + expected a non-negative integer as index in #{mfa}: + + #{expr_to_string(expr) |> indent(4)} + + got the index: + + #{index} + """, + format_traces(traces) + ]) + } + end + def format_diagnostic({{:badkeydomain, map, key, error}, mfac, expr, context}) do {mod, fun, arity, _converter} = mfac mfa = Exception.format_mfa(mod, fun, arity) diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 5842f53fc35..5ace329d3a5 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -813,6 +813,17 @@ defmodule Module.Types.ExprTest do {:ok, integer()} """ + + assert typeerror!(elem({1, 2}, -1)) == + ~l""" + expected a non-negative integer as index in Kernel.elem/2: + + elem({1, 2}, -1) + + got the index: + + -1 + """ end test "Tuple.insert_at/3" do @@ -858,6 +869,17 @@ defmodule Module.Types.ExprTest do {:ok, integer()} """ + + assert typeerror!(Tuple.insert_at({1, 2}, -1, :x)) == + ~l""" + expected a non-negative integer as index in Tuple.insert_at/3: + + Tuple.insert_at({1, 2}, -1, :x) + + got the index: + + -1 + """ end test "Tuple.delete_at/2" do @@ -897,6 +919,17 @@ defmodule Module.Types.ExprTest do {:ok, integer()} """ + + assert typeerror!(Tuple.delete_at({1, 2}, -1)) == + ~l""" + expected a non-negative integer as index in Tuple.delete_at/2: + + Tuple.delete_at({1, 2}, -1) + + got the index: + + -1 + """ end test "Tuple.duplicate/2" do