Skip to content

Commit ebb5500

Browse files
author
José Valim
committed
Clean up Elixir internal errors
In particular, we reserve SyntaxError for synatx errors (doh) and for macros that expects a given format at compilation time. For example, defmodule/2 expects a :do keyword in the second argument, not providing one is a syntax error. In other places we impose similar restrictions bue the values are allowed to be any expression as long as the final result at compilation time is a given expression, for example: when foo in @value The right side of in can be anything as long it is a list or a range at compilation time. Those kind of errors are now CompileError.
1 parent 9754f05 commit ebb5500

File tree

5 files changed

+72
-59
lines changed

5 files changed

+72
-59
lines changed

lib/elixir/src/elixir_def.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
204204
{ [], S };
205205

206206
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
207-
elixir_errors:syntax_error(Line, File, "missing keyword do in ~ts", [Kind]);
207+
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
208208

209209
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
210210
Expr = expr_from_body(Line, Body),

lib/elixir/src/elixir_macros.erl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
-export([translate/2]).
55
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
66
-import(elixir_scope, [umergec/2, umergea/2]).
7-
-import(elixir_errors, [syntax_error/3, syntax_error/4,
8-
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
7+
-import(elixir_errors, [compile_error/3, compile_error/4,
8+
syntax_error/3, syntax_error/4,
9+
assert_no_function_scope/3, assert_module_scope/3,
10+
assert_no_match_or_guard_scope/3]).
911

1012
-include("elixir.hrl").
1113
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
@@ -70,14 +72,15 @@ translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(
7072
end,
7173

7274
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
73-
false -> syntax_error(WrappedMeta, S#elixir_scope.file,
74-
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
75+
false -> compile_error(WrappedMeta, S#elixir_scope.file,
76+
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
7577
Else -> Else
7678
end;
7779

78-
translate({ function, Meta, [_] }, S) ->
80+
translate({ function, Meta, [Arg] }, S) ->
7981
assert_no_match_or_guard_scope(Meta, 'function', S),
80-
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
82+
syntax_error(Meta, S#elixir_scope.file, "invalid args for function/1: ~ts",
83+
['Elixir.Macro':to_string(Arg)]);
8184

8285
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
8386
assert_no_match_or_guard_scope(Meta, 'function', S),
@@ -191,7 +194,7 @@ translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
191194

192195
Block = case lists:keyfind(do, 1, KV) of
193196
{ do, DoValue } -> DoValue;
194-
false -> syntax_error(Meta, S#elixir_scope.file, "expected do: argument in defmodule")
197+
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
195198
end,
196199

197200
{ FRef, FS } = case TRef of
@@ -307,8 +310,8 @@ translate_in(Meta, Left, Right, S) ->
307310
true ->
308311
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
309312
false ->
310-
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit list "
311-
" or an explicit range on the right side when used in guard expressions")
313+
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
314+
"compile time list or range on the right side when used in guard expressions")
312315
end
313316
end,
314317

lib/elixir/src/elixir_translator.erl

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
-export([translate/2, translate_each/2, translate_arg/2,
66
translate_args/2, translate_apply/7, translate_fn/3]).
77
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
8-
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
9-
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
10-
assert_no_match_or_guard_scope/3]).
8+
-import(elixir_errors, [syntax_error/3, syntax_error/4,
9+
compile_error/3, compile_error/4,
10+
assert_function_scope/3, assert_module_scope/3,
11+
assert_no_guard_scope/3, assert_no_match_or_guard_scope/3]).
1112
-include("elixir.hrl").
1213

1314
forms(String, StartLine, File, Opts) ->
@@ -26,8 +27,10 @@ forms(String, StartLine, File, Opts) ->
2627

2728
'forms!'(String, StartLine, File, Opts) ->
2829
case forms(String, StartLine, File, Opts) of
29-
{ ok, Forms } -> Forms;
30-
{ error, { Line, Error, Token } } -> parse_error(Line, File, Error, Token)
30+
{ ok, Forms } ->
31+
Forms;
32+
{ error, { Line, Error, Token } } ->
33+
elixir_errors:parse_error(Line, File, Error, Token)
3134
end.
3235

3336
translate(Forms, S) ->
@@ -91,7 +94,7 @@ translate_each({ alias, Meta, [Ref, KV] }, S) ->
9194
{ atom, _, Old } ->
9295
translate_alias(Meta, true, Old, TKV, ST);
9396
_ ->
94-
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
97+
compile_error(Meta, S#elixir_scope.file, "invalid args for alias, expected a compile time atom or alias as argument")
9598
end;
9699

97100
translate_each({ require, Meta, [Ref] }, S) ->
@@ -108,7 +111,7 @@ translate_each({ require, Meta, [Ref, KV] }, S) ->
108111
elixir_aliases:ensure_loaded(Meta, Old, ST),
109112
translate_require(Meta, Old, TKV, ST);
110113
_ ->
111-
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
114+
compile_error(Meta, S#elixir_scope.file, "invalid args for require, expected a compile time atom or alias as argument")
112115
end;
113116

114117
translate_each({ import, Meta, [Left] }, S) ->
@@ -134,7 +137,7 @@ translate_each({ import, Meta, [Left, Right, KV] }, S) ->
134137

135138
Selector = case TSelector of
136139
{ atom, _, SelectorAtom } -> SelectorAtom;
137-
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
140+
_ -> compile_error(Meta, S#elixir_scope.file, "invalid selector for import, expected a compile time atom")
138141
end,
139142

140143
case TRef of
@@ -143,7 +146,7 @@ translate_each({ import, Meta, [Left, Right, KV] }, S) ->
143146
SF = elixir_import:import(Meta, Old, TKV, Selector, ST),
144147
translate_require(Meta, Old, TKV, SF);
145148
_ ->
146-
syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
149+
compile_error(Meta, S#elixir_scope.file, "invalid name for import, expected a compile time atom or alias")
147150
end;
148151

149152
%% Pseudo variables
@@ -185,14 +188,14 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
185188
%% Quoting
186189

187190
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
188-
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
191+
compile_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
189192

190193
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
191194
case lists:keyfind(do, 1, Opts) of
192195
{ do, Do } ->
193196
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
194197
false ->
195-
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, do is missing")
198+
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
196199
end;
197200

198201
translate_each({ quote, Meta, [_] }, S) ->
@@ -202,7 +205,7 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
202205
Exprs =
203206
case lists:keyfind(do, 1, Do) of
204207
{ do, E } -> E;
205-
false -> syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
208+
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
206209
end,
207210

208211
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, binding, bind_quoted],
@@ -219,7 +222,7 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
219222
{ context, Atom } when is_atom(Atom) ->
220223
Atom;
221224
{ context, _ } ->
222-
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
225+
compile_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected a compile time atom or an alias");
223226
false ->
224227
case S#elixir_scope.module of
225228
nil -> 'Elixir';
@@ -250,7 +253,7 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
250253
File == nil ->
251254
Exprs;
252255
true ->
253-
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
256+
compile_error(Meta, S#elixir_scope.file, "invalid :file for quote, expected a compile time binary")
254257
end,
255258

256259
{ Binding, DefaultUnquote } = case lists:keyfind(binding, 1, TKV) of
@@ -290,7 +293,8 @@ translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name),
290293
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
291294
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
292295
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
293-
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
296+
compile_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing "
297+
"variable or be a part of a match", [Name])
294298
end);
295299

296300
translate_each({ 'var!', Meta, [_, _] }, S) ->
@@ -323,7 +327,8 @@ translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
323327
length(Args) == Arity ->
324328
translate_args(Args, S);
325329
true ->
326-
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
330+
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
331+
"arguments as the current function")
327332
end,
328333

329334
Super = elixir_def_overridable:name(Module, Function),
@@ -344,11 +349,11 @@ translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match
344349
{ ok, Value } ->
345350
{ { var, Meta, Value }, S };
346351
error ->
347-
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
352+
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
348353
end;
349354

350355
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
351-
syntax_error(Meta, S#elixir_scope.file,
356+
compile_error(Meta, S#elixir_scope.file,
352357
"cannot use ^~ts outside of match clauses", [Name]);
353358

354359
translate_each({ '^', Meta, [ Expr ] }, S) ->
@@ -376,13 +381,16 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
376381
Callback = fun() ->
377382
case S#elixir_scope.context of
378383
match ->
379-
syntax_error(Meta, S#elixir_scope.file, "cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
384+
compile_error(Meta, S#elixir_scope.file,
385+
"cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
380386
guard ->
381387
Arity = length(Args),
382388
File = S#elixir_scope.file,
383389
case Arity of
384-
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~ts/~B inside guard", [Atom, Atom, Arity]);
385-
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
390+
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
391+
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
392+
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
393+
[Atom, Arity])
386394
end;
387395
_ ->
388396
translate_local(Meta, Atom, Args, S)
@@ -415,7 +423,7 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
415423
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
416424
case S#elixir_scope.context of
417425
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
418-
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
426+
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
419427
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
420428
_ ->
421429
Callback()
@@ -424,7 +432,7 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
424432
_ ->
425433
case S#elixir_scope.context of
426434
Context when Context == match; Context == guard ->
427-
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
435+
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
428436
[Right, length(Args), Context]);
429437
_ ->
430438
Callback()
@@ -485,11 +493,12 @@ literal_opts(_) -> false.
485493

486494
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
487495
[begin
488-
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
496+
compile_error(Meta, S#elixir_scope.file,
497+
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
489498
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
490499

491500
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
492-
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
501+
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
493502

494503
%% Quote
495504

@@ -499,7 +508,7 @@ expand_quote_context(Meta, Alias, Msg, S) ->
499508
{ { atom, _, Atom }, _ } ->
500509
Atom;
501510
_ ->
502-
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
511+
compile_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
503512
end.
504513

505514
%% Require
@@ -526,14 +535,14 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
526535
true -> Old
527536
end;
528537
_ ->
529-
syntax_error(Meta, S#elixir_scope.file,
530-
"invalid args for alias, expected an atom or alias in option :as")
538+
compile_error(Meta, S#elixir_scope.file,
539+
"invalid :as for alias, expected a compile time atom or alias")
531540
end,
532541

533542
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
534543
true -> ok;
535-
false -> syntax_error(Meta, S#elixir_scope.file,
536-
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
544+
false -> compile_error(Meta, S#elixir_scope.file,
545+
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
537546
end,
538547

539548
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
@@ -576,7 +585,7 @@ translate_fn(Meta, Clauses, S) ->
576585
%% Locals
577586

578587
translate_local(Meta, Name, Args, #elixir_scope{local=nil,function=nil} = S) ->
579-
elixir_errors:compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
588+
compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
580589

581590
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
582591
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
@@ -660,8 +669,9 @@ assert_no_ambiguous_op(Name, Meta, [Arg], S) ->
660669
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
661670
error -> ok;
662671
_ ->
663-
syntax_error(Meta, S#elixir_scope.file, "\"~ts ~ts\" looks like a function call but there is a variable named \"~ts\", "
664-
"please use explicit parenthesis or even spaces", [Name, 'Elixir.Macro':to_string(Arg), Name])
672+
syntax_error(Meta, S#elixir_scope.file, "\"~ts ~ts\" looks like a function call but "
673+
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
674+
[Name, 'Elixir.Macro':to_string(Arg), Name])
665675
end;
666676
_ -> ok
667677
end;
@@ -677,7 +687,7 @@ translate_comprehension(Meta, Kind, Args, S) ->
677687
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
678688
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
679689
_ ->
680-
syntax_error(Meta, S#elixir_scope.file, "keyword argument :do missing for comprehension ~s", [Kind])
690+
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in comprehension ~s", [Kind])
681691
end.
682692

683693
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->

lib/elixir/src/elixir_try.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ normalize_rescue(Meta, Condition, S) ->
9292
{ { atom, _, Atom }, _ } ->
9393
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
9494
_ ->
95-
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should match on an alias, "
96-
"a variable or be in the `var in [alias]` format")
95+
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should "
96+
"match on an alias, a variable or be in the `var in [alias]` format")
9797
end.
9898

9999
%% Convert rescue clauses into guards.

0 commit comments

Comments
 (0)