Skip to content

Commit 53b6888

Browse files
author
José Valim
committed
Invert the precedence of | and when
Although this adds some drawbacks when it comes to typespecs, it prepares us for when we have named patterns.
1 parent ea8e555 commit 53b6888

File tree

7 files changed

+95
-94
lines changed

7 files changed

+95
-94
lines changed

lib/elixir/lib/behaviour.ex

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,9 @@ defmodule Behaviour do
4646
@doc """
4747
Define a function callback according to the given type specification.
4848
"""
49-
defmacro defcallback({ :when, _, [{ :::, _, [fun, return] }, guards] }) do
50-
do_defcallback(fun, return, guards, __CALLER__)
51-
end
52-
53-
defmacro defcallback({ :::, _, [fun, return] }) do
54-
do_defcallback(fun, return, [], __CALLER__)
49+
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
50+
{ return, guard } = split_return_and_guard(return_and_guard)
51+
do_defcallback(fun, return, guard, __CALLER__)
5552
end
5653

5754
defmacro defcallback(fun) do
@@ -61,18 +58,28 @@ defmodule Behaviour do
6158
@doc """
6259
Define a macro callback according to the given type specification.
6360
"""
64-
defmacro defmacrocallback({ :when, _, [{ :::, _, [fun, return] }, guards] }) do
65-
do_defcallback(fun, return, guards, __CALLER__)
66-
end
67-
68-
defmacro defmacrocallback({ :::, _, [fun, return] }) do
69-
do_defmacrocallback(fun, return, [], __CALLER__)
61+
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
62+
{ return, guard } = split_return_and_guard(return_and_guard)
63+
do_defmacrocallback(fun, return, guard, __CALLER__)
7064
end
7165

7266
defmacro defmacrocallback(fun) do
7367
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
7468
end
7569

70+
defp split_return_and_guard({ :when, _, [return, guard] }) do
71+
{ return, guard }
72+
end
73+
74+
defp split_return_and_guard({ :|, meta, [left, right] }) do
75+
{ return, guard } = split_return_and_guard(right)
76+
{ { :|, meta, [left, return] }, guard }
77+
end
78+
79+
defp split_return_and_guard(other) do
80+
{ other, [] }
81+
end
82+
7683
defp do_defcallback(fun, return, guards, caller) do
7784
case Macro.decompose_call(fun) do
7885
{ name, args } ->
@@ -112,7 +119,7 @@ defmodule Behaviour do
112119
end
113120

114121
defp ensure_not_default({ ://, _, [_, _] }) do
115-
raise ArgumentError, message: "default arguments // not supported in defcallback"
122+
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
116123
end
117124

118125
defp ensure_not_default(_), do: :ok

lib/elixir/lib/kernel/typespec.ex

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ defmodule Kernel.Typespec do
1919
2020
Most of the built-in types provided in Erlang (for example, `pid()`) are
2121
expressed the same way: `pid()` or simply `pid`. Parametrized types are also
22-
supported (`list(integer`) and so are remote types (`Enum.t`).
22+
supported (`list(integer)`) and so are remote types (`Enum.t`).
2323
2424
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
2525
`false`). All other types are built of unions of predefined types. Certain
@@ -353,19 +353,21 @@ defmodule Kernel.Typespec do
353353
Converts a spec clause back to Elixir AST.
354354
"""
355355
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
356-
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
357-
ast = { :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }
356+
meta = [line: line]
357+
body = { name, meta, Enum.map(args, &typespec_to_ast/1) }
358358

359359
vars = args ++ [result]
360360
|> Enum.flat_map(&collect_vars/1)
361361
|> Enum.uniq
362-
|> Enum.map(&{ &1, { :var, [line: line], nil } })
362+
|> Enum.map(&{ &1, { :var, meta, nil } })
363363

364-
unless vars == [] do
365-
ast = { :when, [line: line], [ast, vars] }
364+
result = if vars == [] do
365+
typespec_to_ast(result)
366+
else
367+
{ :when, meta, [typespec_to_ast(result), vars] }
366368
end
367369

368-
ast
370+
{ :::, meta, [body, result] }
369371
end
370372

371373
def spec_to_ast(name, { :type, line, :fun, [] }) do
@@ -378,16 +380,20 @@ defmodule Kernel.Typespec do
378380
{ var, typespec_to_ast(type) }
379381
end
380382

381-
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
383+
meta = [line: line]
382384

383385
vars = args ++ [result]
384386
|> Enum.flat_map(&collect_vars/1)
385387
|> Enum.uniq
388+
|> Kernel.--(Keyword.keys(guards))
389+
|> Enum.map(&{ &1, { :var, meta, nil } })
386390

387-
vars = vars -- Keyword.keys(guards)
388-
|> Enum.map(&{ &1, { :var, [line: line], nil } })
391+
args = lc arg inlist args, do: typespec_to_ast(arg)
389392

390-
{ :when, [line: line], [{ :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }, guards ++ vars] }
393+
{ :::, meta, [
394+
{ name, [line: line], args },
395+
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
396+
] }
391397
end
392398

393399
@doc """
@@ -473,7 +479,7 @@ defmodule Kernel.Typespec do
473479
The result is returned as a list of tuples where the first
474480
element is spec name and arity and the second is the spec.
475481
476-
The module must have a corresponding beam file
482+
The module must have a corresponding beam file
477483
which can be located by the runtime system.
478484
"""
479485
@spec beam_callbacks(module | binary) :: [tuple] | nil
@@ -524,7 +530,7 @@ defmodule Kernel.Typespec do
524530

525531
def deftype(_kind, other, caller) do
526532
type_spec = Macro.to_string(other)
527-
compile_error caller, "invalid type specification `#{type_spec}`"
533+
compile_error caller, "invalid type specification: #{type_spec}"
528534
end
529535

530536
defp do_deftype(kind, { name, _, args }, definition, caller) do
@@ -545,16 +551,17 @@ defmodule Kernel.Typespec do
545551
end
546552

547553
@doc false
548-
def defspec(type, { :when, meta2, [{ :::, _, [{ name, meta, args }, return] }, guard] }, caller) do
554+
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
549555
if is_atom(args), do: args = []
556+
{ return, guard } = split_return_and_guard(return_and_guard)
550557

551558
unless Keyword.keyword?(guard) do
552559
guard = Macro.to_string(guard)
553-
compile_error caller, "invalid guard in function type specification `#{guard}`"
560+
compile_error caller, "expected keywords as guard in function type specification, got: #{guard}"
554561
end
555562

556563
vars = Keyword.keys(guard)
557-
constraints = guard_to_constraints(guard, vars, meta2, caller)
564+
constraints = guard_to_constraints(guard, vars, meta, caller)
558565

559566
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
560567
if constraints != [] do
@@ -566,17 +573,22 @@ defmodule Kernel.Typespec do
566573
code
567574
end
568575

569-
def defspec(type, { :::, _, [{ name, meta, args }, return] }, caller) do
570-
if is_atom(args), do: args = []
571-
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
572-
code = { { name, Kernel.length(args) }, spec }
573-
Module.compile_typespec(caller.module, type, code)
574-
code
575-
end
576-
577576
def defspec(_type, other, caller) do
578577
spec = Macro.to_string(other)
579-
compile_error caller, "invalid function type specification `#{spec}`"
578+
compile_error caller, "invalid function type specification: #{spec}"
579+
end
580+
581+
defp split_return_and_guard({ :when, _, [return, guard] }) do
582+
{ return, guard }
583+
end
584+
585+
defp split_return_and_guard({ :|, meta, [left, right] }) do
586+
{ return, guard } = split_return_and_guard(right)
587+
{ { :|, meta, [left, return] }, guard }
588+
end
589+
590+
defp split_return_and_guard(other) do
591+
{ other, [] }
580592
end
581593

582594
defp guard_to_constraints(guard, vars, meta, caller) do
@@ -656,8 +668,7 @@ defmodule Kernel.Typespec do
656668

657669
defp typespec_to_ast({ :type, line, :union, args }) do
658670
args = lc arg inlist args, do: typespec_to_ast(arg)
659-
Enum.reduce tl(args), hd(args),
660-
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
671+
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
661672
end
662673

663674
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
@@ -749,7 +760,7 @@ defmodule Kernel.Typespec do
749760

750761
# Handle unions
751762
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
752-
exprs = Enum.reverse(collect_union(exprs))
763+
exprs = collect_union(exprs)
753764
union = lc e inlist exprs, do: typespec(e, vars, caller)
754765
{ :type, line(meta), :union, union }
755766
end
@@ -886,9 +897,10 @@ defmodule Kernel.Typespec do
886897
typespec({ :nonempty_list, [], [spec] }, vars, caller)
887898
end
888899

889-
defp typespec([h|t] = l, vars, caller) do
890-
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
891-
{ :|, [], [acc, validate_kw(x, l, caller)] }
900+
defp typespec(list, vars, caller) do
901+
[h|t] = Enum.reverse(list)
902+
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
903+
{ :|, [], [validate_kw(x, list, caller), acc] }
892904
end)
893905
typespec({ :list, [], [union] }, vars, caller)
894906
end
@@ -904,12 +916,12 @@ defmodule Kernel.Typespec do
904916
{ :remote_type, line(meta), [ remote, name, arguments ] }
905917
end
906918

907-
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
919+
defp collect_union({ :|, _, [a, b] }), do: [a|collect_union(b)]
908920
defp collect_union(v), do: [v]
909921

910922
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
911923
defp validate_kw(_, original, caller) do
912-
compile_error(caller, "unexpected list `#{Macro.to_string original}` in typespec")
924+
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
913925
end
914926

915927
defp fn_args(meta, args, return, vars, caller) do

lib/elixir/lib/macro.ex

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,9 @@ defmodule Macro do
3030
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
3131
defp binary_op_props(o) do
3232
case o do
33-
:when -> {:right, 30}
3433
::: -> {:right, 40}
35-
o when o in [:inlist, :inbits] -> {:left, 50}
36-
:// -> {:right, 60}
37-
:| -> {:left, 70}
34+
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
35+
:when -> {:right, 70}
3836
:= -> {:right, 80}
3937
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
4038
o when o in [:&&, :&&&, :and] -> {:left, 140}

lib/elixir/src/elixir_parser.yrl

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ Nonterminals
33
expr container_expr block_expr no_parens_expr identifier_expr max_expr
44
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
55
op_expr matched_op_expr no_parens_op_expr
6-
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
6+
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
77
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
8-
arrow_op_eol default_op_eol match_op_eol
9-
when_op_eol in_op_eol inc_op_eol
8+
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
109
open_paren close_paren empty_paren
1110
open_bracket close_bracket
1211
open_curly close_curly
@@ -30,9 +29,8 @@ Terminals
3029
fn 'end' aliases
3130
number signed_number atom atom_string bin_string list_string sigil
3231
dot_call_op op_identifier
33-
comp_op at_op unary_op and_op or_op arrow_op match_op
34-
in_op inc_op when_op default_op tail_op
35-
dual_op add_op mult_op exp_op two_op type_op stab_op
32+
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
33+
dual_op add_op mult_op exp_op two_op type_op stab_op when_op
3634
'true' 'false' 'nil' 'do' eol ',' '.' '&'
3735
'(' ')' '[' ']' '{' '}' '<<' '>>'
3836
.
@@ -46,11 +44,9 @@ Expect 2.
4644
Left 5 do.
4745
Right 10 stab_op_eol. %% ->
4846
Left 20 ','.
49-
Right 30 when_op_eol. %% when
5047
Right 40 type_op_eol. %% ::
51-
Left 50 inc_op_eol. %% inlist, inbits
52-
Right 60 default_op_eol. %% //
53-
Left 70 tail_op_eol. %% |
48+
Right 50 in_match_op_eol. %% inlist, inbits, //, | (allowed in matches, lower precedence than =)
49+
Right 70 when_op_eol. %% when
5450
Right 80 match_op_eol. %% =
5551
Left 130 or_op_eol. %% ||, |||, or, xor
5652
Left 140 and_op_eol. %% &&, &&&, and
@@ -138,11 +134,9 @@ op_expr -> exp_op_eol expr : { '$1', '$2' }.
138134
op_expr -> two_op_eol expr : { '$1', '$2' }.
139135
op_expr -> and_op_eol expr : { '$1', '$2' }.
140136
op_expr -> or_op_eol expr : { '$1', '$2' }.
141-
op_expr -> tail_op_eol expr : { '$1', '$2' }.
142137
op_expr -> in_op_eol expr : { '$1', '$2' }.
143-
op_expr -> inc_op_eol expr : { '$1', '$2' }.
138+
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
144139
op_expr -> when_op_eol expr : { '$1', '$2' }.
145-
op_expr -> default_op_eol expr : { '$1', '$2' }.
146140
op_expr -> type_op_eol expr : { '$1', '$2' }.
147141
op_expr -> comp_op_eol expr : { '$1', '$2' }.
148142
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
@@ -154,10 +148,8 @@ no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
154148
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
155149
no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
156150
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
157-
no_parens_op_expr -> tail_op_eol no_parens_expr : { '$1', '$2' }.
158151
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
159-
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
160-
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
152+
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
161153
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
162154
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
163155
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
@@ -173,11 +165,9 @@ matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
173165
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
174166
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
175167
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
176-
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
177168
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
178-
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
169+
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
179170
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
180-
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
181171
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
182172
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
183173
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
@@ -307,9 +297,6 @@ exp_op_eol -> exp_op eol : '$1'.
307297
two_op_eol -> two_op : '$1'.
308298
two_op_eol -> two_op eol : '$1'.
309299

310-
default_op_eol -> default_op : '$1'.
311-
default_op_eol -> default_op eol : '$1'.
312-
313300
type_op_eol -> type_op : '$1'.
314301
type_op_eol -> type_op eol : '$1'.
315302

@@ -327,14 +314,11 @@ and_op_eol -> and_op eol : '$1'.
327314
or_op_eol -> or_op : '$1'.
328315
or_op_eol -> or_op eol : '$1'.
329316

330-
tail_op_eol -> tail_op : '$1'.
331-
tail_op_eol -> tail_op eol : '$1'.
332-
333317
in_op_eol -> in_op : '$1'.
334318
in_op_eol -> in_op eol : '$1'.
335319

336-
inc_op_eol -> inc_op : '$1'.
337-
inc_op_eol -> inc_op eol : '$1'.
320+
in_match_op_eol -> in_match_op : '$1'.
321+
in_match_op_eol -> in_match_op eol : '$1'.
338322

339323
when_op_eol -> when_op : '$1'.
340324
when_op_eol -> when_op eol : '$1'.

0 commit comments

Comments
 (0)