diff options
author | Björn Gustavsson <[email protected]> | 2015-03-31 08:47:57 +0200 |
---|---|---|
committer | Björn Gustavsson <[email protected]> | 2015-04-15 15:12:33 +0200 |
commit | 5bb8262bd4ae8dbcc7438e80de5cedac7ccee1af (patch) | |
tree | 5e238aa9ab7e7dad021a56cb7ac9d0c07a54b46e /lib/compiler/src | |
parent | 44c5a955c8409cc98a5fbdec7898de46b9e5dfbe (diff) | |
download | otp-5bb8262bd4ae8dbcc7438e80de5cedac7ccee1af.tar.gz otp-5bb8262bd4ae8dbcc7438e80de5cedac7ccee1af.tar.bz2 otp-5bb8262bd4ae8dbcc7438e80de5cedac7ccee1af.zip |
Raise more descriptive error messages for failed map operations
According to EEP-43 for maps, a 'badmap' exception should be
generated when an attempt is made to update non-map term such as:
<<>>#{a=>42}
That was not implemented in the OTP 17.
José Valim suggested that we should take the opportunity to
improve the errors coming from map operations:
http://erlang.org/pipermail/erlang-questions/2015-February/083588.html
This commit implement better errors from map operations similar
to his suggestion.
When a map update operation (Map#{...}) or a BIF that expects a map
is given a non-map term, the exception will be:
{badmap,Term}
This kind of exception is similar to the {badfun,Term} exception
from operations that expect a fun.
When a map operation requires a key that is not present in a map,
the following exception will be raised:
{badkey,Key}
José Valim suggested that the exception should be
{badkey,Key,Map}. We decided not to do that because the map
could potentially be huge and cause problems if the error
propagated through links to other processes.
For BIFs, it could be argued that the exceptions could be simply
'badmap' and 'badkey', because the bad map and bad key can be found in
the argument list for the BIF in the stack backtrace. However, for the
map update operation (Map#{...}), the bad map or bad key will not be
included in the stack backtrace, so that information must be included
in the exception reason itself. For consistency, the BIFs should raise
the same exceptions as update operation.
If more than one key is missing, it is undefined which of
keys that will be reported in the {badkey,Key} exception.
Diffstat (limited to 'lib/compiler/src')
-rw-r--r-- | lib/compiler/src/sys_core_fold.erl | 10 | ||||
-rw-r--r-- | lib/compiler/src/v3_core.erl | 38 |
2 files changed, 25 insertions, 23 deletions
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 0d020578f5..beab2ce897 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -70,7 +70,8 @@ -export([module/2,format_error/1]). -import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,all/2,any/2, - reverse/1,reverse/2,member/2,nth/2,flatten/1,unzip/1]). + reverse/1,reverse/2,member/2,nth/2,flatten/1, + unzip/1,keyfind/3]). -import(cerl, [ann_c_cons/3,ann_c_map/3,ann_c_tuple/2]). @@ -1624,12 +1625,11 @@ eval_case(Case, _) -> Case. eval_case_warn(#c_primop{anno=Anno, name=#c_literal{val=match_fail}, - args=[#c_literal{val=Reason}]}=Core) - when is_atom(Reason) -> - case member(eval_failure, Anno) of + args=[_]}=Core) -> + case keyfind(eval_failure, 1, Anno) of false -> ok; - true -> + {eval_failure,Reason} -> %% Example: M = not_map, M#{k:=v} add_warning(Core, {eval_failure,Reason}) end; diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index c954d21e59..ed7b55df07 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -538,19 +538,8 @@ expr({tuple,L,Es0}, St0) -> {annotate_tuple(A, Es1, St1),Eps,St1}; expr({map,L,Es0}, St0) -> map_build_pairs(#c_literal{val=#{}}, Es0, full_anno(L, St0), St0); -expr({map,L,M0,Es0}, St0) -> - try expr_map(M0,Es0,lineno_anno(L, St0),St0) of - {_,_,_}=Res -> Res - catch - throw:{bad_map,Warning} -> - St = add_warning(L, Warning, St0), - LineAnno = lineno_anno(L, St), - As = [#c_literal{anno=LineAnno,val=badarg}], - {#icall{anno=#a{anno=LineAnno}, %Must have an #a{} - module=#c_literal{anno=LineAnno,val=erlang}, - name=#c_literal{anno=LineAnno,val=error}, - args=As},[],St} - end; +expr({map,L,M,Es}, St) -> + expr_map(M, Es, L, St); expr({bin,L,Es0}, St0) -> try expr_bin(Es0, full_anno(L, St0), St0) of {_,_,_}=Res -> Res @@ -758,11 +747,14 @@ make_bool_switch_guard(L, E, V, T, F) -> {clause,NegL,[V],[],[V]} ]}. -expr_map(M0, Es0, A, St0) -> +expr_map(M0, Es0, L, St0) -> {M1,Eps0,St1} = safe(M0, St0), + Badmap = badmap_term(M1, St1), + A = lineno_anno(L, St1), + Fc = fail_clause([], [{eval_failure,badmap}|A], Badmap), case is_valid_map_src(M1) of true -> - {M2,Eps1,St2} = map_build_pairs(M1, Es0, A, St1), + {M2,Eps1,St2} = map_build_pairs(M1, Es0, full_anno(L, St1), St1), M3 = case Es0 of [] -> M1; [_|_] -> M2 @@ -775,13 +767,23 @@ expr_map(M0, Es0, A, St0) -> name=#c_literal{anno=A,val=is_map}, args=[M1]}], body=[M3]}], - Fc = fail_clause([], [eval_failure|A], #c_literal{val=badarg}), Eps = Eps0 ++ Eps1, {#icase{anno=#a{anno=A},args=[],clauses=Cs,fc=Fc},Eps,St2}; false -> - throw({bad_map,bad_map}) + %% Not a map source. The update will always fail. + St2 = add_warning(L, badmap, St1), + #iclause{body=[Fail]} = Fc, + {Fail,Eps0,St2} end. +badmap_term(_Map, #core{in_guard=true}) -> + %% The code generator cannot handle complex error reasons + %% in guards. But the exact error reason does not matter anyway + %% since it is not user-visible. + #c_literal{val=badmap}; +badmap_term(Map, #core{in_guard=false}) -> + #c_tuple{es=[#c_literal{val=badmap},Map]}. + map_build_pairs(Map, Es0, Ann, St0) -> {Es,Pre,St1} = map_build_pairs_1(Es0, St0), {ann_c_map(Ann, Map, Es),Pre,St1}. @@ -2395,7 +2397,7 @@ format_error(nomatch) -> "pattern cannot possibly match"; format_error(bad_binary) -> "binary construction will fail because of a type mismatch"; -format_error(bad_map) -> +format_error(badmap) -> "map construction will fail because of a type mismatch". add_warning(Line, Term, #core{ws=Ws,file=[{file,File}]}=St) when Line >= 0 -> |