Skip to content

Commit a4ffd55

Browse files
committed
Merge pull request #4076 from lexmag/access-error
Make Access syntax fail to compile for unsupported values
2 parents 860db84 + 16eac65 commit a4ffd55

File tree

6 files changed

+45
-14
lines changed

6 files changed

+45
-14
lines changed

lib/elixir/lib/access.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ defmodule Access do
6060
end
6161
end
6262

63+
def fetch(list, key) when is_list(list) do
64+
raise ArgumentError,
65+
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
66+
end
67+
6368
def fetch(nil, _key) do
6469
:error
6570
end

lib/elixir/lib/kernel.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,7 @@ defmodule Kernel do
16431643
@doc """
16441644
Gets a value from a nested structure.
16451645
1646-
Uses the `Access` protocol to traverse the structures
1646+
Uses the `Access` module to traverse the structures
16471647
according to the given `keys`, unless the `key` is a
16481648
function.
16491649
@@ -1662,7 +1662,7 @@ defmodule Kernel do
16621662
27
16631663
16641664
In case any of entries in the middle returns `nil`, `nil` will be returned
1665-
as per the Access protocol:
1665+
as per the Access module:
16661666
16671667
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
16681668
iex> get_in(users, ["unknown", :age])
@@ -1702,7 +1702,7 @@ defmodule Kernel do
17021702
@doc """
17031703
Puts a value in a nested structure.
17041704
1705-
Uses the `Access` protocol to traverse the structures
1705+
Uses the `Access` module to traverse the structures
17061706
according to the given `keys`, unless the `key` is a
17071707
function. If the key is a function, it will be invoked
17081708
as specified in `get_and_update_in/3`.
@@ -1724,7 +1724,7 @@ defmodule Kernel do
17241724
@doc """
17251725
Updates a key in a nested structure.
17261726
1727-
Uses the `Access` protocol to traverse the structures
1727+
Uses the `Access` module to traverse the structures
17281728
according to the given `keys`, unless the `key` is a
17291729
function. If the key is a function, it will be invoked
17301730
as specified in `get_and_update_in/3`.
@@ -1749,7 +1749,7 @@ defmodule Kernel do
17491749
It expects a tuple to be returned, containing the value
17501750
retrieved and the update one.
17511751
1752-
Uses the `Access` protocol to traverse the structures
1752+
Uses the `Access` module to traverse the structures
17531753
according to the given `keys`, unless the `key` is a
17541754
function.
17551755

lib/elixir/src/elixir_exp.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
516516
ok
517517
end,
518518
{EArgs, EA} = expand_args(Args, E),
519-
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs),
519+
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs, EA),
520520
elixir_env:mergev(EL, EA)}.
521521

522522
%% Lexical helpers

lib/elixir/src/elixir_rewrite.erl

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
-module(elixir_rewrite).
2-
-export([rewrite/5, inline/3]).
2+
-export([rewrite/6, inline/3]).
33
-include("elixir.hrl").
44

5+
-define(is_literal(Arg), (is_binary(Arg) orelse is_number(Arg) orelse is_atom(Arg))).
6+
57
%% Convenience variables
68

79
-define(atom, 'Elixir.Atom').
10+
-define(access, 'Elixir.Access').
811
-define(enum, 'Elixir.Enum').
912
-define(float, 'Elixir.Float').
1013
-define(io, 'Elixir.IO').
@@ -173,9 +176,17 @@ inline(_, _, _) -> false.
173176

174177
%% Complex rewrite rules
175178

176-
rewrite(?string_chars, _DotMeta, 'to_string', _Meta, [String]) when is_binary(String) ->
179+
rewrite(?access, _DotMeta, 'get', _Meta, [nil, Arg], _Env)
180+
when ?is_literal(Arg) orelse (is_atom(element(1, Arg)) andalso element(3, Arg) == nil) ->
181+
nil;
182+
rewrite(?access, _DotMeta, 'get', Meta, [Arg, _], Env)
183+
when ?is_literal(Arg) orelse element(1, Arg) == '{}' orelse element(1, Arg) == '<<>>' ->
184+
elixir_errors:compile_error(Meta, ?m(Env, file),
185+
"the Access syntax and calls to Access.get/2 are not available for the value: ~ts",
186+
['Elixir.Macro':to_string(Arg)]);
187+
rewrite(?string_chars, _DotMeta, 'to_string', _Meta, [String], _File) when is_binary(String) ->
177188
String;
178-
rewrite(?string_chars, DotMeta, 'to_string', Meta, [String]) ->
189+
rewrite(?string_chars, DotMeta, 'to_string', Meta, [String], _Env) ->
179190
Var = {'rewrite', Meta, 'Elixir'},
180191
Guard = {{'.', ?generated, [erlang, is_binary]}, ?generated, [Var]},
181192
Slow = remote(?string_chars, DotMeta, 'to_string', Meta, [Var]),
@@ -186,9 +197,9 @@ rewrite(?string_chars, DotMeta, 'to_string', Meta, [String]) ->
186197
{'->', ?generated, [[Var], Slow]}]
187198
}]]};
188199

189-
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) when is_list(List) ->
200+
rewrite(?enum, DotMeta, 'reverse', Meta, [List], _Env) when is_list(List) ->
190201
remote(lists, DotMeta, 'reverse', Meta, [List]);
191-
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) ->
202+
rewrite(?enum, DotMeta, 'reverse', Meta, [List], _Env) ->
192203
Var = {'rewrite', Meta, 'Elixir'},
193204
Guard = {{'.', ?generated, [erlang, is_list]}, ?generated, [Var]},
194205
Slow = remote(?enum, DotMeta, 'reverse', Meta, [Var]),
@@ -199,7 +210,7 @@ rewrite(?enum, DotMeta, 'reverse', Meta, [List]) ->
199210
{'->', ?generated, [[Var], Slow]}]
200211
}]]};
201212

202-
rewrite(Receiver, DotMeta, Right, Meta, Args) ->
213+
rewrite(Receiver, DotMeta, Right, Meta, Args, _Env) ->
203214
{EReceiver, ERight, EArgs} = rewrite(Receiver, Right, Args),
204215
remote(EReceiver, DotMeta, ERight, Meta, EArgs).
205216

lib/elixir/test/elixir/access_test.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ defmodule AccessTest do
3636
assert Access.fetch([foo: :bar], :foo) == {:ok, :bar}
3737
assert Access.fetch([foo: :bar], :bar) == :error
3838

39-
assert_raise FunctionClauseError, fn ->
40-
Access.fetch([{"foo", :bar}], "foo")
39+
msg = ~r/the Access calls for keywords expect the key to be an atom/
40+
assert_raise ArgumentError, msg, fn ->
41+
Access.fetch([], "foo")
4142
end
4243

4344
assert Access.get([foo: :bar], :foo) == :bar

lib/elixir/test/elixir/kernel/errors_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ defmodule Kernel.ErrorsTest do
5353
'fn 1 end'
5454
end
5555

56+
test "invalid Access" do
57+
msg = fn(val) ->
58+
"nofile:1: the Access syntax and calls to Access.get/2" <>
59+
" are not available for the value: " <> val
60+
end
61+
62+
assert_compile_fail CompileError, msg.("1"), "1[:foo]"
63+
assert_compile_fail CompileError, msg.("1.1"), "1.1[:foo]"
64+
assert_compile_fail CompileError, msg.("{}"), "{}[:foo]"
65+
assert_compile_fail CompileError, msg.(":foo"), ":foo[:foo]"
66+
assert_compile_fail CompileError, msg.("\"\""), "\"\"[:foo]"
67+
assert_compile_fail CompileError, msg.("<<>>"), "<<>>[:foo]"
68+
end
69+
5670
test "kw missing space" do
5771
msg = "nofile:1: keyword argument must be followed by space after: foo:"
5872

0 commit comments

Comments
 (0)