Skip to content

Commit c1ef879

Browse files
committed
Type Tuple.insert_at/3
1 parent f0f42eb commit c1ef879

File tree

5 files changed

+91
-39
lines changed

5 files changed

+91
-39
lines changed

lib/elixir/lib/module/types/descr.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,7 +1933,7 @@ defmodule Module.Types.Descr do
19331933
@doc """
19341934
Insert an element at the tuple.
19351935
1936-
It returns the same as `tuple_fetch/2` but with `:badrange` instead of `:badindex`.
1936+
It returns the same as `tuple_fetch/2`. Notice, however, the range for indexes is inclusive.
19371937
"""
19381938
def tuple_insert_at(:term, _key, _type), do: :badtuple
19391939

@@ -1944,7 +1944,7 @@ defmodule Module.Types.Descr do
19441944
end
19451945
end
19461946

1947-
def tuple_insert_at(_, _, _), do: :badrange
1947+
def tuple_insert_at(_, _, _), do: :badindex
19481948

19491949
defp tuple_insert_at_checked(descr, index, type) do
19501950
case :maps.take(:dynamic, descr) do
@@ -1955,7 +1955,7 @@ defmodule Module.Types.Descr do
19551955

19561956
cond do
19571957
is_proper_tuple? and is_proper_size? -> tuple_insert_static(descr, index, type)
1958-
is_proper_tuple? -> :badrange
1958+
is_proper_tuple? -> :badindex
19591959
true -> :badtuple
19601960
end
19611961

@@ -1976,7 +1976,7 @@ defmodule Module.Types.Descr do
19761976

19771977
# Highlight the case where the issue is an index out of range from the tuple
19781978
is_proper_tuple? ->
1979-
:badrange
1979+
:badindex
19801980

19811981
true ->
19821982
:badtuple

lib/elixir/lib/module/types/of.ex

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,17 +296,33 @@ defmodule Module.Types.Of do
296296
## Apply
297297

298298
# TODO: Implement element without a literal index
299-
# TODO: Add a test for an open tuple (inferred from a guard)
300-
# TODO: Implement set_element
301299

302-
def apply(:erlang, :element, [_, type], {_, meta, [index, _]} = expr, stack, context)
300+
def apply(:erlang, :element, [_, tuple], {_, meta, [index, _]} = expr, stack, context)
303301
when is_integer(index) do
304-
case tuple_fetch(type, index - 1) do
302+
case tuple_fetch(tuple, index - 1) do
305303
{_optional?, value_type} ->
306304
{value_type, context}
307305

308306
reason ->
309-
{error_type(), error({reason, expr, type, index - 1, context}, meta, stack, context)}
307+
{error_type(), error({reason, expr, tuple, index - 1, context}, meta, stack, context)}
308+
end
309+
end
310+
311+
def apply(
312+
:erlang,
313+
:insert_element,
314+
[_, tuple, value],
315+
{_, meta, [index, _, _]} = expr,
316+
stack,
317+
context
318+
)
319+
when is_integer(index) do
320+
case tuple_insert_at(tuple, index - 1, value) do
321+
value_type when is_descr(value_type) ->
322+
{value_type, context}
323+
324+
reason ->
325+
{error_type(), error({reason, expr, tuple, index - 2, context}, meta, stack, context)}
310326
end
311327
end
312328

@@ -571,15 +587,15 @@ defmodule Module.Types.Of do
571587
}
572588
end
573589

574-
def format_diagnostic({:badtuple, expr, type, index, context}) do
590+
def format_diagnostic({:badtuple, expr, type, _index, context}) do
575591
traces = collect_traces(expr, context)
576592

577593
%{
578594
details: %{typing_traces: traces},
579595
message:
580596
IO.iodata_to_binary([
581597
"""
582-
expected a tuple when accessing element at index #{index} in expression:
598+
expected a tuple in expression:
583599
584600
#{expr_to_string(expr) |> indent(4)}
585601
@@ -600,7 +616,7 @@ defmodule Module.Types.Of do
600616
message:
601617
IO.iodata_to_binary([
602618
"""
603-
out of range tuple access at index #{index} in expression:
619+
expected a tuple with at least #{pluralize(index + 1, "element", "elements")} in expression:
604620
605621
#{expr_to_string(expr) |> indent(4)}
606622
@@ -749,6 +765,9 @@ defmodule Module.Types.Of do
749765
}
750766
end
751767

768+
defp pluralize(1, singular, _), do: "1 #{singular}"
769+
defp pluralize(i, _, plural), do: "#{i} #{plural}"
770+
752771
defp dot_var?(expr) do
753772
match?({{:., _, [var, _fun]}, _, _args} when is_var(var), expr)
754773
end

lib/elixir/lib/tuple.ex

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,8 @@ defmodule Tuple do
8787
:erlang.insert_element(index + 1, tuple, value)
8888
end
8989

90-
@doc """
91-
Inserts an element at the end of a tuple.
92-
93-
Returns a new tuple with the element appended at the end, and contains
94-
the elements in `tuple` followed by `value` as the last element.
95-
96-
Inlined by the compiler.
97-
98-
## Examples
99-
100-
iex> tuple = {:foo, :bar}
101-
iex> Tuple.append(tuple, :baz)
102-
{:foo, :bar, :baz}
103-
104-
"""
105-
@spec append(tuple, term) :: tuple
90+
@doc false
91+
@deprecated "Use insert_at instead"
10692
def append(tuple, value) do
10793
:erlang.append_element(tuple, value)
10894
end

lib/elixir/test/elixir/module/types/descr_test.exs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -641,13 +641,21 @@ defmodule Module.Types.DescrTest do
641641
test "tuple_fetch" do
642642
assert tuple_fetch(term(), 0) == :badtuple
643643
assert tuple_fetch(integer(), 0) == :badtuple
644+
644645
assert tuple_fetch(tuple([integer(), atom()]), 0) == {false, integer()}
645646
assert tuple_fetch(tuple([integer(), atom()]), 1) == {false, atom()}
646647
assert tuple_fetch(tuple([integer(), atom()]), 2) == :badindex
648+
649+
assert tuple_fetch(open_tuple([integer(), atom()]), 0) == {false, integer()}
650+
assert tuple_fetch(open_tuple([integer(), atom()]), 1) == {false, atom()}
651+
assert tuple_fetch(open_tuple([integer(), atom()]), 2) == :badindex
652+
647653
assert tuple_fetch(tuple([integer(), atom()]), -1) == :badindex
648654
assert tuple_fetch(empty_tuple(), 0) == :badindex
649655
assert difference(tuple(), tuple()) |> tuple_fetch(0) == :badindex
650-
assert tuple([atom()]) |> difference(empty_tuple()) |> tuple_fetch(0) == {false, atom()}
656+
657+
assert tuple([atom()]) |> difference(empty_tuple()) |> tuple_fetch(0) ==
658+
{false, atom()}
651659

652660
assert difference(tuple([union(integer(), atom())]), open_tuple([atom()]))
653661
|> tuple_fetch(0) == {false, integer()}
@@ -742,14 +750,14 @@ defmodule Module.Types.DescrTest do
742750
end
743751

744752
test "tuple_insert_at" do
745-
assert tuple_insert_at(tuple([integer(), atom()]), 3, boolean()) == :badrange
746-
assert tuple_insert_at(tuple([integer(), atom()]), -1, boolean()) == :badrange
753+
assert tuple_insert_at(tuple([integer(), atom()]), 3, boolean()) == :badindex
754+
assert tuple_insert_at(tuple([integer(), atom()]), -1, boolean()) == :badindex
747755
assert tuple_insert_at(integer(), 0, boolean()) == :badtuple
748756
assert tuple_insert_at(term(), 0, boolean()) == :badtuple
749757

750758
# Out-of-bounds in a union
751759
assert union(tuple([integer(), atom()]), tuple([float()]))
752-
|> tuple_insert_at(2, boolean()) == :badrange
760+
|> tuple_insert_at(2, boolean()) == :badindex
753761

754762
# Test inserting into a closed tuple
755763
assert tuple_insert_at(tuple([integer(), atom()]), 1, boolean()) ==

lib/elixir/test/elixir/module/types/expr_test.exs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,18 +248,16 @@ defmodule Module.Types.ExprTest do
248248
assert typecheck!([x], {:ok, x}) == dynamic(tuple([atom([:ok]), term()]))
249249
end
250250

251-
test "accessing tuples" do
251+
test "elem/2" do
252252
assert typecheck!(elem({:ok, 123}, 0)) == atom([:ok])
253253
assert typecheck!(elem({:ok, 123}, 1)) == integer()
254254
assert typecheck!([x], elem({:ok, x}, 0)) == dynamic(atom([:ok]))
255255
assert typecheck!([x], elem({:ok, x}, 1)) == dynamic(term())
256-
end
257256

258-
test "accessing an index on not a map" do
259257
assert typewarn!([<<x::integer>>], elem(x, 0)) ==
260258
{dynamic(),
261259
~l"""
262-
expected a tuple when accessing element at index 0 in expression:
260+
expected a tuple in expression:
263261
264262
elem(x, 0)
265263
@@ -273,13 +271,11 @@ defmodule Module.Types.ExprTest do
273271
# from: types_test.ex:LINE-2
274272
<<x::integer>>
275273
"""}
276-
end
277274

278-
test "accessing an out of range index" do
279275
assert typewarn!(elem({:ok, 123}, 2)) ==
280276
{dynamic(),
281277
~l"""
282-
out of range tuple access at index 2 in expression:
278+
expected a tuple with at least 3 elements in expression:
283279
284280
elem({:ok, 123}, 2)
285281
@@ -288,6 +284,49 @@ defmodule Module.Types.ExprTest do
288284
{:ok, integer()}
289285
"""}
290286
end
287+
288+
test "Tuple.insert_at/3" do
289+
assert typecheck!(Tuple.insert_at({}, 0, "foo")) == tuple([binary()])
290+
291+
assert typecheck!(Tuple.insert_at({:ok, 123}, 0, "foo")) ==
292+
tuple([binary(), atom([:ok]), integer()])
293+
294+
assert typecheck!(Tuple.insert_at({:ok, 123}, 1, "foo")) ==
295+
tuple([atom([:ok]), binary(), integer()])
296+
297+
assert typecheck!(Tuple.insert_at({:ok, 123}, 2, "foo")) ==
298+
tuple([atom([:ok]), integer(), binary()])
299+
300+
assert typewarn!([<<x::integer>>], Tuple.insert_at(x, 0, "foo")) ==
301+
{dynamic(),
302+
~l"""
303+
expected a tuple in expression:
304+
305+
Tuple.insert_at(x, 0, "foo")
306+
307+
but got type:
308+
309+
integer()
310+
311+
where "x" was given the type:
312+
313+
# type: integer()
314+
# from: types_test.ex:LINE-2
315+
<<x::integer>>
316+
"""}
317+
318+
assert typewarn!(Tuple.insert_at({:ok, 123}, 3, "foo")) ==
319+
{dynamic(),
320+
~l"""
321+
expected a tuple with at least 3 elements in expression:
322+
323+
Tuple.insert_at({:ok, 123}, 3, "foo")
324+
325+
the given type does not have the given index:
326+
327+
{:ok, integer()}
328+
"""}
329+
end
291330
end
292331

293332
describe "maps/structs" do

0 commit comments

Comments
 (0)