Skip to content

Commit 5abcc33

Browse files
committed
Improve error message and docs for headless clauses, closes #15104
1 parent 52296e0 commit 5abcc33

3 files changed

Lines changed: 45 additions & 30 deletions

File tree

lib/elixir/pages/getting-started/modules-and-functions.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ defmodule Concat do
151151
# A function head declaring defaults
152152
def join(a, b, sep \\ " ")
153153

154+
# The separator is unused in this case, so we prefix it with underscore
154155
def join(a, b, _sep) when b == "" do
155156
a
156157
end
@@ -165,7 +166,7 @@ IO.puts(Concat.join("Hello", "world")) #=> Hello world
165166
IO.puts(Concat.join("Hello", "world", "_")) #=> Hello_world
166167
```
167168

168-
When a variable is not used by a function or a clause, we add a leading underscore (`_`) to its name to signal this intent. This rule is also covered in our [Naming Conventions](../references/naming-conventions.md#underscore-_foo) document.
169+
Function heads cannot have patterns nor guards. They may only define the argument names and their default values.
169170

170171
## Understanding Aliases
171172

@@ -182,8 +183,7 @@ true
182183

183184
By using the `alias/2` directive, we are changing the atom the alias expands to.
184185

185-
Aliases expand to atoms because in the Erlang Virtual Machine (and consequently Elixir) modules are always represented by atoms. By namespacing
186-
those atoms elixir modules avoid conflicting with existing erlang modules.
186+
Aliases expand to atoms because in the Erlang Virtual Machine (and consequently Elixir) modules are always represented by atoms. By namespacing those atoms, Elixir modules avoid conflicting with existing Erlang modules.
187187

188188
```elixir
189189
iex> List.flatten([1, [2], 3])
@@ -212,9 +212,7 @@ end
212212

213213
The example above will define two modules: `Foo` and `Foo.Bar`. The second can be accessed as `Bar` inside `Foo` as long as they are in the same lexical scope.
214214

215-
If, later, the `Bar` module is moved outside the `Foo` module definition, it must be referenced by its full name (`Foo.Bar`) or an alias must be set using the `alias` directive discussed above.
216-
217-
**Note**: in Elixir, you don't have to define the `Foo` module before being able to define the `Foo.Bar` module, as they are effectively independent. The above could also be written as:
215+
If, later, the `Bar` module is moved outside the `Foo` module definition, it must be referenced by its full name (`Foo.Bar`) or an alias must be set using the `alias` directive discussed above:
218216

219217
```elixir
220218
defmodule Foo.Bar do
@@ -226,7 +224,11 @@ defmodule Foo do
226224
end
227225
```
228226

229-
Aliasing a nested module does not bring parent modules into scope. Consider the following example:
227+
> #### Module names are isolated {: .info}
228+
>
229+
> You don't have to define the `Foo` module before defining the `Foo.Bar` module, as they are effectively independent.
230+
231+
Aliasing a module only alias a single module, it doesn't alias any of its parents. Consider the following example:
230232

231233
```elixir
232234
defmodule Foo do
@@ -241,4 +243,4 @@ alias Foo.Bar.Baz
241243
# However, the module `Foo.Bar` is *not* available as `Bar`
242244
```
243245

244-
As we will see in later chapters, aliases also play a crucial role in macros, to guarantee they are hygienic.
246+
As we will see in later chapters, aliases also work well with macros, to guarantee they are hygienic.

lib/elixir/src/elixir_def.erl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,9 @@ run_with_location_change(File, #{file := File} = E, Callback) ->
238238
run_with_location_change(File, E, Callback) ->
239239
elixir_lexical:with_file(File, E, Callback).
240240

241-
def_to_clauses(_Kind, Meta, Args, [], nil, E) ->
241+
def_to_clauses(_Kind, Meta, Args, Guards, nil, E) ->
242242
check_args_for_function_head(Meta, Args, E),
243+
(Guards /= []) andalso elixir_errors:module_error(Meta, E, ?MODULE, {invalid_function_head, guards}),
243244
[];
244245
def_to_clauses(_Kind, Meta, Args, Guards, [{do, Body}], _E) ->
245246
[{Meta, Args, Guards, Body}];
@@ -374,7 +375,7 @@ check_valid_defaults(_Meta, _File, _Name, _Arity, _Kind, 0, _StoredMeta, _Stored
374375

375376
check_args_for_function_head(Meta, Args, E) ->
376377
[begin
377-
elixir_errors:module_error(Meta, E, ?MODULE, invalid_args_for_function_head)
378+
elixir_errors:module_error(Meta, E, ?MODULE, {invalid_function_head, patterns})
378379
end || Arg <- Args, invalid_arg(Arg)].
379380

380381
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) -> false;
@@ -486,16 +487,17 @@ format_error({no_alias, Atom}) ->
486487
format_error({invalid_def, Kind, NameAndArgs}) ->
487488
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
488489

489-
format_error(invalid_args_for_function_head) ->
490-
"patterns are not allowed in function head, only variables and default arguments (using \\\\)\n"
490+
format_error({invalid_function_head, Prefix}) ->
491+
atom_to_list(Prefix) ++ (
492+
" are not allowed in function head, only variables and default arguments (using \\\\)\n"
491493
"\n"
492494
"If you did not intend to define a function head, make sure your function "
493495
"definition has the proper syntax by wrapping the arguments in parentheses "
494-
"and using the do instruction accordingly:\n\n"
496+
"and using the do keyword accordingly:\n\n"
495497
" def add(a, b), do: a + b\n\n"
496498
" def add(a, b) do\n"
497499
" a + b\n"
498-
" end\n";
500+
" end\n");
499501

500502
format_error({'__info__', Kind}) ->
501503
io_lib:format("cannot define ~ts __info__/1 as it is automatically defined by Elixir", [Kind]);

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -854,33 +854,44 @@ defmodule Kernel.ErrorsTest do
854854
)
855855
end
856856

857-
test "function head with guard" do
858-
assert_compile_error(["nofile:2:7: ", "missing :do option in \"def\""], ~c"""
859-
defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
860-
def foo(n) when is_number(n)
861-
end
862-
""")
863-
864-
assert_compile_error(["nofile:2:7: ", "missing :do option in \"def\""], ~c"""
865-
defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
866-
def foo(n) when is_number(n), true
867-
end
868-
""")
869-
end
870-
871-
test "invalid args for function head" do
857+
test "invalid function head" do
872858
assert_compile_error(
873859
[
874860
"nofile:2:7: ",
875861
"patterns are not allowed in function head, only variables and default arguments (using \\\\)"
876862
],
877863
~c"""
878-
defmodule Kernel.ErrorsTest.InvalidArgsForBodylessClause do
864+
defmodule Kernel.ErrorsTest.InvalidPatternsInFunctionHead do
879865
def foo(nil)
880866
def foo(_), do: :ok
881867
end
882868
"""
883869
)
870+
871+
assert_compile_error(
872+
[
873+
"nofile:2:7: ",
874+
"guards are not allowed in function head, only variables and default arguments (using \\\\)"
875+
],
876+
~c"""
877+
defmodule Kernel.ErrorsTest.InvalidGuardsInFunctionHead do
878+
def foo(x) when x == nil
879+
def foo(_), do: :ok
880+
end
881+
"""
882+
)
883+
884+
assert_compile_error(["nofile:2:7: ", "missing :do option in \"def\""], ~c"""
885+
defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
886+
def foo(n), true
887+
end
888+
""")
889+
890+
assert_compile_error(["nofile:2:7: ", "missing :do option in \"def\""], ~c"""
891+
defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
892+
def foo(n) when is_number(n), true
893+
end
894+
""")
884895
end
885896

886897
test "bad multi-call" do

0 commit comments

Comments
 (0)