Skip to content

Commit f38b14a

Browse files
committedJul 22, 2022
Improve error messages for dot syntax
1 parent 42d3ce2 commit f38b14a

File tree

5 files changed

+77
-28
lines changed

5 files changed

+77
-28
lines changed
 

‎lib/elixir/lib/exception.ex

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -820,15 +820,8 @@ defmodule ArgumentError do
820820
"you attempted to apply a function named #{inspect(function)} on module #{inspect(module)} " <>
821821
"with arguments #{inspect(args)}. Arguments (the third argument of apply) must always be a proper list"
822822

823-
# Note that args may be an empty list even if they were supplied
824-
not is_atom(module) and is_atom(function) and args == [] ->
825-
"you attempted to apply a function named #{inspect(function)} on #{inspect(module)}. " <>
826-
"If you are using Kernel.apply/3, make sure the module is an atom. " <>
827-
"If you are using the dot syntax, such as map.field or module.function(), " <>
828-
"make sure the left side of the dot is an atom or a map"
829-
830823
not is_atom(module) ->
831-
"you attempted to apply a function on #{inspect(module)}. " <>
824+
"you attempted to apply a function named #{inspect(function)} on #{inspect(module)}. " <>
832825
"Modules (the first argument of apply) must always be an atom"
833826

834827
not is_atom(function) ->
@@ -1639,6 +1632,22 @@ defmodule ErlangError do
16391632
%KeyError{key: key, term: map}
16401633
end
16411634

1635+
def normalize({:baddot, true, key, map}, _stacktrace) do
1636+
%ArgumentError{
1637+
message:
1638+
"could not find key #{inspect(key)} on #{inspect(map)}. " <>
1639+
"When using the map.field syntax, make sure the left side of the dot is a map"
1640+
}
1641+
end
1642+
1643+
def normalize({:baddot, false, key, module}, _stacktrace) do
1644+
%ArgumentError{
1645+
message:
1646+
"could not find function #{inspect(key)} on #{inspect(module)}. " <>
1647+
"When using the module.function() syntax, make sure the left side of the dot is a module"
1648+
}
1649+
end
1650+
16421651
def normalize({:case_clause, term}, _stacktrace) do
16431652
%CaseClauseError{term: term}
16441653
end

‎lib/elixir/src/elixir_erl_pass.erl

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ translate({{'.', _, [Left, Right]}, Meta, []}, _Ann, S) when is_tuple(Left), is_
232232
Generated = erl_anno:set_generated(true, Ann),
233233
{Var, SV} = elixir_erl_var:build('_', SL),
234234
TVar = {var, Generated, Var},
235-
TError = {tuple, Ann, [{atom, Ann, badkey}, TRight, TVar]},
235+
TBadKeyError = {tuple, Ann, [{atom, Ann, badkey}, TRight, TVar]},
236+
TBadDotError = build_baddot_error(TVar, TRight, Ann, Meta),
236237

237238
{{'case', Generated, TLeft, [
238239
{clause, Generated,
@@ -242,11 +243,22 @@ translate({{'.', _, [Left, Right]}, Meta, []}, _Ann, S) when is_tuple(Left), is_
242243
{clause, Generated,
243244
[TVar],
244245
[[?remote(Generated, erlang, is_map, [TVar])]],
245-
[?remote(Ann, erlang, error, [TError])]},
246+
[?remote(Ann, erlang, error, [TBadKeyError])]},
247+
{clause, Generated,
248+
[TVar],
249+
[[{op, Ann, 'orelse',
250+
{op, Ann, '=:=', TVar, {atom, Ann, nil}},
251+
?remote(Generated, erlang, is_boolean, [TVar])
252+
}]],
253+
[?remote(Ann, erlang, error, [TBadDotError])]},
254+
{clause, Generated,
255+
[TVar],
256+
[[?remote(Generated, erlang, is_atom, [TVar])]],
257+
[{call, Generated, {remote, Generated, TVar, TRight}, []}]},
246258
{clause, Generated,
247259
[TVar],
248260
[],
249-
[{call, Generated, {remote, Generated, TVar, TRight}, []}]}
261+
[?remote(Ann, erlang, error, [TBadDotError])]}
250262
]}, SV};
251263

252264
translate({{'.', _, [Left, Right]}, Meta, Args}, _Ann, S)
@@ -596,6 +608,12 @@ translate_remote(maps, merge, Meta, [Map1, Map2], S) ->
596608
{[TMap1, TMap2], TS} ->
597609
{{call, Ann, {remote, Ann, {atom, Ann, maps}, {atom, Ann, merge}}, [TMap1, TMap2]}, TS}
598610
end;
611+
612+
translate_remote(Left, Right, Meta, _Args, S)
613+
when (Left =:= nil orelse is_boolean(Left)), is_atom(Right) ->
614+
Ann = ?ann(Meta),
615+
TBadDotError = build_baddot_error({atom, Ann, Left}, {atom, Ann, Right}, Ann, Meta),
616+
{?remote(Ann, erlang, error, [TBadDotError]), S};
599617
translate_remote(Left, Right, Meta, Args, S) ->
600618
Ann = ?ann(Meta),
601619
{TLeft, SL} = translate(Left, Ann, S),
@@ -616,6 +634,10 @@ translate_remote(Left, Right, Meta, Args, S) ->
616634
{{call, Ann, {remote, Ann, TLeft, TRight}, TArgs}, SA}
617635
end.
618636

637+
build_baddot_error(Left, Right, Ann, Meta) ->
638+
NoParens = proplists:get_value(no_parens, Meta, false),
639+
{tuple, Ann, [{atom, Ann, baddot}, {atom, Ann, NoParens}, Right, Left]}.
640+
619641
generate_struct_name_guard([{map_field_exact, Ann, {atom, _, '__struct__'} = Key, Var} | Rest], Acc, S0) ->
620642
{ModuleVarName, S1} = elixir_erl_var:build('_', S0),
621643
Generated = erl_anno:set_generated(true, Ann),

‎lib/elixir/src/elixir_erl_try.erl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,13 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.KeyError') ->
203203
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
204204
erl_or(Meta,
205205
{erl(Meta, '=='), Meta, [Var, badarg]},
206-
erl_and(Meta,
207-
erl_tuple_size(Meta, Var, 2),
208-
erl_record_compare(Meta, Var, badarg)));
206+
erl_or(Meta,
207+
erl_and(Meta,
208+
erl_tuple_size(Meta, Var, 2),
209+
erl_record_compare(Meta, Var, badarg)),
210+
erl_and(Meta,
211+
erl_tuple_size(Meta, Var, 4),
212+
erl_record_compare(Meta, Var, baddot))));
209213

210214
erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
211215
Condition =

‎lib/elixir/test/elixir/exception_test.exs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -477,16 +477,37 @@ defmodule ExceptionTest do
477477
"""
478478
end
479479

480-
test "annotates badarg on apply" do
480+
test "annotates dot syntax on invalid argument errors" do
481481
assert blame_message([], & &1.foo) ==
482-
"you attempted to apply a function named :foo on []. If you are using Kernel.apply/3, make sure " <>
483-
"the module is an atom. If you are using the dot syntax, such as " <>
484-
"map.field or module.function(), make sure the left side of the dot is an atom or a map"
482+
"could not find key :foo on []. When using the map.field syntax, " <>
483+
"make sure the left side of the dot is a map"
484+
485+
assert blame_message([], & &1.foo()) ==
486+
"could not find function :foo on []. When using the module.function() " <>
487+
"syntax, make sure the left side of the dot is a module"
488+
489+
# atoms that are invalid module name:
490+
assert blame_message(nil, & &1.foo()) ==
491+
"could not find function :foo on nil. When using the module.function() " <>
492+
"syntax, make sure the left side of the dot is a module"
493+
494+
assert blame_message(false, & &1.foo()) ==
495+
"could not find function :foo on false. When using the module.function() " <>
496+
"syntax, make sure the left side of the dot is a module"
497+
498+
assert blame_message(nil, fn _ -> nil.foo() end) ==
499+
"could not find function :foo on nil. When using the module.function() " <>
500+
"syntax, make sure the left side of the dot is a module"
485501

502+
assert blame_message(false, fn _ -> false.foo() end) ==
503+
"could not find function :foo on false. When using the module.function() " <>
504+
"syntax, make sure the left side of the dot is a module"
505+
end
506+
507+
test "annotates badarg on apply" do
486508
assert blame_message([], &apply(&1, :foo, [])) ==
487-
"you attempted to apply a function named :foo on []. If you are using Kernel.apply/3, make sure " <>
488-
"the module is an atom. If you are using the dot syntax, such as " <>
489-
"map.field or module.function(), make sure the left side of the dot is an atom or a map"
509+
"you attempted to apply a function named :foo on []. Modules " <>
510+
"(the first argument of apply) must always be an atom"
490511

491512
assert blame_message([], &apply(Kernel, &1, [1, 2])) ==
492513
"you attempted to apply a function named [] on module Kernel. However [] is not a valid function name. " <>
@@ -588,12 +609,6 @@ defmodule ExceptionTest do
588609
"function :erlang.hash/2 is undefined or private, use erlang:phash2/2 instead"
589610
end
590611

591-
test "annotates undefined function clause error with nil hints" do
592-
assert blame_message(nil, & &1.foo) ==
593-
"function nil.foo/0 is undefined. If you are using the dot syntax, " <>
594-
"such as map.field or module.function(), make sure the left side of the dot is an atom or a map"
595-
end
596-
597612
test "annotates key error with suggestions if keys are atoms" do
598613
message = blame_message(%{first: nil, second: nil}, fn map -> map.firts end)
599614

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,6 @@ defmodule Kernel.ErrorsTest do
826826
rescue
827827
ArgumentError ->
828828
assert [
829-
{:erlang, :apply, [1, :foo, []], _},
830829
{__MODULE__, :bad_remote_call, 1, [file: _, line: _]} | _
831830
] = __STACKTRACE__
832831
end

0 commit comments

Comments
 (0)