Skip to content

Commit d33463d

Browse files
author
José Valim
committed
Unify precedence of when, :: and |
1 parent 2de2322 commit d33463d

File tree

6 files changed

+77
-135
lines changed

6 files changed

+77
-135
lines changed

lib/elixir/lib/behaviour.ex

Lines changed: 21 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -46,56 +46,49 @@ defmodule Behaviour do
4646
@doc """
4747
Define a function callback according to the given type specification.
4848
"""
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__)
52-
end
53-
54-
defmacro defcallback(fun) do
55-
do_defcallback(fun, quote(do: term), [], __CALLER__)
49+
defmacro defcallback(spec) do
50+
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
5651
end
5752

5853
@doc """
5954
Define a macro callback according to the given type specification.
6055
"""
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__)
56+
defmacro defmacrocallback(spec) do
57+
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
6458
end
6559

66-
defmacro defmacrocallback(fun) do
67-
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
60+
defp split_spec({ :when, _, [{ :::, _, [spec, return] }, guard] }, _default) do
61+
{ spec, return, guard }
6862
end
6963

70-
defp split_return_and_guard({ :when, _, [return, guard] }) do
71-
{ return, guard }
64+
defp split_spec({ :when, _, [spec, guard] }, default) do
65+
{ spec, default, guard }
7266
end
7367

74-
defp split_return_and_guard({ :|, meta, [left, right] }) do
75-
{ return, guard } = split_return_and_guard(right)
76-
{ { :|, meta, [left, return] }, guard }
68+
defp split_spec({ :::, _, [spec, return] }, _default) do
69+
{ spec, return, [] }
7770
end
7871

79-
defp split_return_and_guard(other) do
80-
{ other, [] }
72+
defp split_spec(spec, default) do
73+
{ spec, default, [] }
8174
end
8275

83-
defp do_defcallback(fun, return, guards, caller) do
84-
case Macro.decompose_call(fun) do
76+
defp do_defcallback({ spec, return, guards }, caller) do
77+
case Macro.decompose_call(spec) do
8578
{ name, args } ->
8679
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
8780
_ ->
88-
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
81+
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
8982
end
9083
end
9184

92-
defp do_defmacrocallback(fun, return, guards, caller) do
93-
case Macro.decompose_call(fun) do
85+
defp do_defmacrocallback({ spec, return, guards }, caller) do
86+
case Macro.decompose_call(spec) do
9487
{ name, args } ->
9588
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
9689
name, length(args), args, return, guards, caller)
9790
_ ->
98-
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
91+
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
9992
end
10093
end
10194

@@ -111,10 +104,9 @@ defmodule Behaviour do
111104
end
112105

113106
quote do
114-
docs_args = unquote(Macro.escape(docs_args))
115107
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
116108
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
117-
unquote(docs_name), docs_args, unquote(docs_arity))
109+
unquote(docs_name), unquote(docs_arity))
118110
end
119111
end
120112

@@ -125,61 +117,12 @@ defmodule Behaviour do
125117
defp ensure_not_default(_), do: :ok
126118

127119
@doc false
128-
def store_docs(module, line, kind, name, args, arity) do
120+
def store_docs(module, line, kind, name, arity) do
129121
doc = Module.get_attribute module, :doc
130122
Module.delete_attribute module, :doc
131-
132-
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
133-
{ simplify_signature(arg, ix + 1), [line: line], nil }
134-
end)
135-
136-
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
137-
end
138-
139-
defp simplify_signature({ :::, _, [var, _] }, i) do
140-
simplify_signature(var, i)
141-
end
142-
143-
defp simplify_signature({ :|, _, [first, _] }, i) do
144-
simplify_signature(first, i)
145-
end
146-
147-
defp simplify_signature({ :., _, [_, name] }, _i) do
148-
name
149-
end
150-
151-
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
152-
:"fun#{i}"
123+
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
153124
end
154125

155-
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
156-
:"range#{i}"
157-
end
158-
159-
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
160-
:"bitstring#{i}"
161-
end
162-
163-
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
164-
:"tuple#{i}"
165-
end
166-
167-
defp simplify_signature({ name, _, args }, _i)
168-
when is_atom(name) and (is_atom(args) or is_list(args)) do
169-
name
170-
end
171-
172-
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
173-
simplify_signature(ast, i)
174-
end
175-
176-
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
177-
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
178-
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
179-
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
180-
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
181-
defp simplify_signature(_, i), do: :"arg#{i}"
182-
183126
@doc false
184127
defmacro __using__(_) do
185128
quote do

lib/elixir/lib/kernel/typespec.ex

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -361,13 +361,13 @@ defmodule Kernel.Typespec do
361361
|> Enum.uniq
362362
|> Enum.map(&{ &1, { :var, meta, nil } })
363363

364-
result = if vars == [] do
365-
typespec_to_ast(result)
364+
spec = { :::, meta, [body, typespec_to_ast(result)] }
365+
366+
if vars == [] do
367+
spec
366368
else
367-
{ :when, meta, [typespec_to_ast(result), vars] }
369+
{ :when, meta, [spec, vars] }
368370
end
369-
370-
{ :::, meta, [body, result] }
371371
end
372372

373373
def spec_to_ast(name, { :type, line, :fun, [] }) do
@@ -376,23 +376,23 @@ defmodule Kernel.Typespec do
376376

377377
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
378378
guards =
379-
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} <- constraints do
379+
for { :type, _, :constraint, [{ :atom, _, :is_subtype }, [{ :var, _, var }, type]] } <- constraints do
380380
{ var, typespec_to_ast(type) }
381381
end
382382

383383
meta = [line: line]
384384

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

391391
args = for arg <- args, do: typespec_to_ast(arg)
392392

393-
{ :::, meta, [
394-
{ name, [line: line], args },
395-
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
393+
{ :when, meta, [
394+
{ :::, meta, [{ name, [line: line], args }, typespec_to_ast(result)] },
395+
guards ++ vars
396396
] }
397397
end
398398

@@ -542,9 +542,17 @@ defmodule Kernel.Typespec do
542542
end
543543

544544
@doc false
545-
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
545+
546+
def defspec(type, { :when, _meta, [spec, guard] }, caller) do
547+
defspec(type, spec, guard, caller)
548+
end
549+
550+
def defspec(type, spec, caller) do
551+
defspec(type, spec, [], caller)
552+
end
553+
554+
defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
546555
if is_atom(args), do: args = []
547-
{ return, guard } = split_return_and_guard(return_and_guard)
548556

549557
unless Keyword.keyword?(guard) do
550558
guard = Macro.to_string(guard)
@@ -564,24 +572,11 @@ defmodule Kernel.Typespec do
564572
code
565573
end
566574

567-
def defspec(_type, other, caller) do
568-
spec = Macro.to_string(other)
575+
defp defspec(_type, spec, _guard, caller) do
576+
spec = Macro.to_string(spec)
569577
compile_error caller, "invalid function type specification: #{spec}"
570578
end
571579

572-
defp split_return_and_guard({ :when, _, [return, guard] }) do
573-
{ return, guard }
574-
end
575-
576-
defp split_return_and_guard({ :|, meta, [left, right] }) do
577-
{ return, guard } = split_return_and_guard(right)
578-
{ { :|, meta, [left, return] }, guard }
579-
end
580-
581-
defp split_return_and_guard(other) do
582-
{ other, [] }
583-
end
584-
585580
defp guard_to_constraints(guard, vars, meta, caller) do
586581
line = line(meta)
587582

lib/elixir/src/elixir_parser.yrl

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Nonterminals
55
op_expr matched_op_expr no_parens_op_expr
66
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 pipe_op_eol stab_op_eol
8-
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
8+
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol type_op_eol
99
open_paren close_paren empty_paren
1010
list list_args open_bracket close_bracket
1111
tuple open_curly close_curly
@@ -28,7 +28,7 @@ Terminals
2828
fn 'end' aliases
2929
number signed_number atom atom_string bin_string list_string sigil
3030
dot_call_op op_identifier
31-
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
31+
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
3232
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
3333
'true' 'false' 'nil' 'do' eol ',' '.'
3434
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
@@ -45,11 +45,12 @@ Expect 2.
4545
Left 5 do.
4646
Right 10 stab_op_eol. %% ->
4747
Left 20 ','.
48-
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\, :: (allowed in matches along =)
49-
Right 50 pipe_op_eol. %% |
50-
Right 60 assoc_op_eol. %% =>
51-
Right 70 when_op_eol. %% when
52-
Right 80 match_op_eol. %% =
48+
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\ (allowed in matches along =)
49+
Right 50 when_op_eol. %% when
50+
Right 60 type_op_eol. %% ::
51+
Right 70 pipe_op_eol. %% |
52+
Right 80 assoc_op_eol. %% =>
53+
Right 90 match_op_eol. %% =
5354
Left 130 or_op_eol. %% ||, |||, or, xor
5455
Left 140 and_op_eol. %% &&, &&&, and
5556
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
@@ -138,6 +139,7 @@ op_expr -> and_op_eol expr : { '$1', '$2' }.
138139
op_expr -> or_op_eol expr : { '$1', '$2' }.
139140
op_expr -> in_op_eol expr : { '$1', '$2' }.
140141
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
142+
op_expr -> type_op_eol expr : { '$1', '$2' }.
141143
op_expr -> when_op_eol expr : { '$1', '$2' }.
142144
op_expr -> pipe_op_eol expr : { '$1', '$2' }.
143145
op_expr -> comp_op_eol expr : { '$1', '$2' }.
@@ -152,6 +154,7 @@ no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
152154
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
153155
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
154156
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
157+
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
155158
no_parens_op_expr -> pipe_op_eol no_parens_expr : { '$1', '$2' }.
156159
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
157160
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
@@ -169,6 +172,7 @@ matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
169172
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
170173
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
171174
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
175+
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
172176
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
173177
matched_op_expr -> pipe_op_eol matched_expr : { '$1', '$2' }.
174178
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
@@ -323,6 +327,9 @@ in_op_eol -> in_op eol : '$1'.
323327
in_match_op_eol -> in_match_op : '$1'.
324328
in_match_op_eol -> in_match_op eol : '$1'.
325329

330+
type_op_eol -> type_op : '$1'.
331+
type_op_eol -> type_op eol : '$1'.
332+
326333
when_op_eol -> when_op : '$1'.
327334
when_op_eol -> when_op eol : '$1'.
328335

lib/elixir/src/elixir_tokenizer.erl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@
7373

7474
-define(in_match_op(T1, T2),
7575
T1 == $<, T2 == $-;
76-
T1 == $\\, T2 == $\\;
77-
T1 == $:, T2 == $:).
76+
T1 == $\\, T2 == $\\).
7877

7978
-define(stab_op(T1, T2),
8079
T1 == $-, T2 == $>).
8180

81+
-define(type_op(T1, T2),
82+
T1 == $:, T2 == $:).
83+
8284
-define(pipe_op(T1),
8385
T == $|).
8486

@@ -278,7 +280,7 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
278280
% ## Two Token Operators
279281
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
280282
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
281-
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
283+
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
282284
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
283285

284286
% ## Single Token Operators
@@ -364,6 +366,9 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
364366
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op(T1, T2) ->
365367
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
366368

369+
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
370+
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
371+
367372
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
368373
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
369374

@@ -526,7 +531,7 @@ handle_dot([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
526531
% ## Two Token Operators
527532
handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
528533
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
529-
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
534+
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
530535
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
531536

532537
% ## Single Token Operators

lib/elixir/test/elixir/behaviour_test.exs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,14 @@ defmodule BehaviourTest do
2828
test :docs do
2929
docs = Sample.__behaviour__(:docs)
3030
assert [
31-
{{:first, 1}, 10, :def, _, "I should be first."},
32-
{{:foo, 2}, 13, :def, _, "Foo"},
33-
{{:bar, 2}, 16, :def, _, "Bar"},
34-
{{:guarded, 1}, 18, :def, _, nil},
35-
{{:orr, 1}, 20, :def, _, nil},
36-
{{:literal, 5}, 22, :def, _, nil},
37-
{{:last, 1}, 25, :defmacro, _, "I should be last."}
31+
{{:first, 1}, 10, :def, "I should be first."},
32+
{{:foo, 2}, 13, :def, "Foo"},
33+
{{:bar, 2}, 16, :def, "Bar"},
34+
{{:guarded, 1}, 18, :def, nil},
35+
{{:orr, 1}, 20, :def, nil},
36+
{{:literal, 5}, 22, :def, nil},
37+
{{:last, 1}, 25, :defmacro, "I should be last."}
3838
] = docs
39-
40-
assert [{ :integer, _, nil }] = List.keyfind(docs, { :first, 1 }, 0) |> elem(3)
41-
assert [{ :atom, _, nil }, { :binary, _, nil }] = List.keyfind(docs, { :foo, 2 }, 0) |> elem(3)
42-
assert [{ :hello, _, nil }, { :my_var, _, nil }] = List.keyfind(docs, { :bar, 2 }, 0) |> elem(3)
43-
assert [{ :my_var, _, nil }] = List.keyfind(docs, { :guarded, 1 }, 0) |> elem(3)
44-
assert [{ :atom, _, nil }] = List.keyfind(docs, { :orr, 1 }, 0) |> elem(3)
45-
assert [{ :int1, _, nil }, { :tuple2, _, nil }, { :atom3, _, nil }, { :list4, _, nil }, { :bool5, _, nil} ] = List.keyfind(docs, { :literal, 5 }, 0) |> elem(3)
46-
assert [{ :integer, _, nil }] = List.keyfind(docs, { :last, 1 }, 0) |> elem(3)
4739
end
4840

4941
test :callbacks do

0 commit comments

Comments
 (0)