Skip to content

Commit 138b442

Browse files
committed
Improve error message on Access module
1 parent 119580f commit 138b442

File tree

1 file changed

+55
-6
lines changed

1 file changed

+55
-6
lines changed

lib/elixir/lib/access.ex

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,13 @@ defmodule Access do
191191
case __STACKTRACE__ do
192192
[unquote(top) | _] ->
193193
reason =
194-
"#{inspect(unquote(module))} does not implement the Access behaviour. " <>
195-
"If you are using get_in/put_in/update_in, you can specify the field " <>
196-
"to be accessed using Access.key!/1"
194+
"""
195+
#{inspect(unquote(module))} does not implement the Access behaviour
196+
197+
You can use the "struct.field" syntax to access struct fields. \
198+
You can also use Access.key!/1 to access struct fields dynamically \
199+
inside get_in/put_in/update_in\
200+
"""
197201

198202
%{unquote(exception) | reason: reason}
199203

@@ -326,9 +330,23 @@ defmodule Access do
326330
end
327331
end
328332

333+
def get(list, key, _default) when is_list(list) and is_integer(key) do
334+
raise ArgumentError, """
335+
the Access module does not support accessing lists by index, got: #{inspect(key)}
336+
337+
Accessing a list by index is typically discouraged in Elixir, \
338+
instead we prefer to use the Enum module to manipulate lists \
339+
as a whole. If you really must access a list element by index, \
340+
you can Enum.at/1 or the functions in the List module\
341+
"""
342+
end
343+
329344
def get(list, key, _default) when is_list(list) do
330-
raise ArgumentError,
331-
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
345+
raise ArgumentError, """
346+
the Access module supports only keyword lists (with atom keys), got: #{inspect(key)}
347+
348+
If you want to search lists of tuples, use List.keyfind/3\
349+
"""
332350
end
333351

334352
def get(nil, _key, default) do
@@ -377,10 +395,26 @@ defmodule Access do
377395
Map.get_and_update(map, key, fun)
378396
end
379397

380-
def get_and_update(list, key, fun) when is_list(list) do
398+
def get_and_update(list, key, fun) when is_list(list) and is_atom(key) do
381399
Keyword.get_and_update(list, key, fun)
382400
end
383401

402+
def get_and_update(list, key, _fun) when is_list(list) and is_integer(key) do
403+
raise ArgumentError, """
404+
the Access module does not support accessing lists by index, got: #{inspect(key)}
405+
406+
Accessing a list by index is typically discouraged in Elixir, \
407+
instead we prefer to use the Enum module to manipulate lists \
408+
as a whole. If you really must mostify a list element by index, \
409+
you can Access.at/1 or the functions in the List module\
410+
"""
411+
end
412+
413+
def get_and_update(list, key, _fun) when is_list(list) do
414+
raise ArgumentError,
415+
"the Access module supports only keyword lists (with atom keys), got: " <> inspect(key)
416+
end
417+
384418
def get_and_update(nil, key, _fun) do
385419
raise ArgumentError, "could not put/update key #{inspect(key)} on a nil value"
386420
end
@@ -507,6 +541,21 @@ defmodule Access do
507541
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
508542
** (KeyError) key :unknown not found in: %{name: \"john\"}
509543
544+
The examples above could be partially written as:
545+
546+
iex> map = %{user: %{name: "john"}}
547+
iex> map.user.name
548+
"john"
549+
iex> get_and_update_in(map.user.name, fn prev ->
550+
...> {prev, String.upcase(prev)}
551+
...> end)
552+
{"john", %{user: %{name: "JOHN"}}}
553+
554+
However, it is not possible to remove fields using the dot notation,
555+
as it is implified those fields must also be present. In any case,
556+
`Access.key!/1` is useful when the key is not known in advance
557+
and must be accessed dynamically.
558+
510559
An error is raised if the accessed structure is not a map/struct:
511560
512561
iex> get_in([], [Access.key!(:foo)])

0 commit comments

Comments
 (0)