aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Bolinder <[email protected]>2015-06-12 12:12:24 +0200
committerHans Bolinder <[email protected]>2015-06-12 12:12:24 +0200
commit8e49f765b5a467cf47d64f873e5feb226e3f7cae (patch)
tree3faae68b18a2f8c8f2be33b2153313ad2e9b86ce
parent6347ee34c81a136055182367905647588a3947bf (diff)
parent417f9960371607e6d618d9dda108787558a9cef5 (diff)
downloadotp-8e49f765b5a467cf47d64f873e5feb226e3f7cae.tar.gz
otp-8e49f765b5a467cf47d64f873e5feb226e3f7cae.tar.bz2
otp-8e49f765b5a467cf47d64f873e5feb226e3f7cae.zip
Merge branch 'hb/stdlib/types_related/OTP-12492'
* hb/stdlib/types_related/OTP-12492: Update the documentation of the abstract format Document abstract format of type-related trees Update primary bootstrap edoc: Add parentheses to Erlang types and specs when needed stdlib: Introduce precedence for operators in types
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_parse.beambin84396 -> 308128 bytes
-rw-r--r--erts/doc/src/absform.xml160
-rw-r--r--lib/edoc/src/edoc_layout.erl2
-rw-r--r--lib/edoc/src/edoc_specs.erl142
-rw-r--r--lib/stdlib/src/erl_parse.yrl64
-rw-r--r--lib/stdlib/src/erl_pp.erl127
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl2
7 files changed, 353 insertions, 144 deletions
diff --git a/bootstrap/lib/stdlib/ebin/erl_parse.beam b/bootstrap/lib/stdlib/ebin/erl_parse.beam
index adf1cfb43e..0522f5c05e 100644
--- a/bootstrap/lib/stdlib/ebin/erl_parse.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_parse.beam
Binary files differ
diff --git a/erts/doc/src/absform.xml b/erts/doc/src/absform.xml
index 835a4fc692..e1a8c2e517 100644
--- a/erts/doc/src/absform.xml
+++ b/erts/doc/src/absform.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2001</year><year>2013</year>
+ <year>2001</year><year>2015</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -80,6 +80,28 @@
<item>If F is a record declaration <c><![CDATA[-record(Name,{V_1, ..., V_k})]]></c>, then
Rep(F) =
<c><![CDATA[{attribute,LINE,record,{Name,[Rep(V_1), ..., Rep(V_k)]}}]]></c>. For Rep(V), see below.</item>
+ <item>If F is a type attribute (i.e. <c><![CDATA[opaque]]></c> or
+ <c><![CDATA[type]]></c>)
+ <c><![CDATA[-Attr Name(A_1, ..., A_k) :: T]]></c> where each
+ <c><![CDATA[A_i]]></c> is a variable, then Rep(F) =
+ <c><![CDATA[{attribute,LINE,Attr,{Name,Rep(T),[Rep(A_1), ..., Rep(A_k)]}}]]></c>.
+ For Rep(T), see below.</item>
+ <item>If F is a type spec (i.e. <c><![CDATA[callback]]></c> or
+ <c><![CDATA[spec]]></c>)
+ <c><![CDATA[-Attr F Tc_1; ...; Tc_k]]></c>,
+ where each <c><![CDATA[Tc_i]]></c> is a fun type clause with an
+ argument sequence of the same length <c><![CDATA[Arity]]></c>, then
+ Rep(F) =
+ <c><![CDATA[{Attr,LINE,{{F,Arity},[Rep(Tc_1), ..., Rep(Tc_k)]}}]]></c>.
+ For Rep(Tc_i), see below.</item>
+ <item>If F is a type spec (i.e. <c><![CDATA[callback]]></c> or
+ <c><![CDATA[spec]]></c>)
+ <c><![CDATA[-Attr Mod:F Tc_1; ...; Tc_k]]></c>,
+ where each <c><![CDATA[Tc_i]]></c> is a fun type clause with an
+ argument sequence of the same length <c><![CDATA[Arity]]></c>, then
+ Rep(F) =
+ <c><![CDATA[{Attr,LINE,{{Mod,F,Arity},[Rep(Tc_1), ..., Rep(Tc_k)]}}]]></c>.
+ For Rep(Tc_i), see below.</item>
<item>If F is a wild attribute <c><![CDATA[-A(T)]]></c>, then
Rep(F) = <c><![CDATA[{attribute,LINE,A,T}]]></c>.
<br></br></item>
@@ -90,6 +112,127 @@
</list>
<section>
+ <title>Type clauses</title>
+ <list type="bulleted">
+ <item>If T is a fun type clause
+ <c><![CDATA[(A_1, ..., A_n) -> Ret]]></c>, where each
+ <c><![CDATA[A_i]]></c> and <c><![CDATA[Ret]]></c> are types, then
+ Rep(T) =
+ <c><![CDATA[{type,LINE,'fun',[{type,LINE,product,[Rep(A_1), ..., Rep(A_n)]},Rep(Ret)]}]]></c>.
+ </item>
+ <item>If T is a bounded fun type clause <c><![CDATA[Tc when Tg]]></c>,
+ where <c><![CDATA[Tc]]></c> is an unbounded fun type clause and
+ <c><![CDATA[Tg]]></c> is a type guard sequence, then Rep(T) =
+ <c><![CDATA[{type,LINE,bounded_fun,[Rep(Tc),Rep(Tg)]}]]></c>.</item>
+ </list>
+ </section>
+
+ <section>
+ <title>Type guards</title>
+ <list type="bulleted">
+ <item>If G is a constraint <c><![CDATA[F(A_1, ..., A_k)]]></c>, where
+ <c><![CDATA[F]]></c> is an atom and each <c><![CDATA[A_i]]></c> is a
+ type, then Rep(G) =
+ <c><![CDATA[{type,LINE,constraint,[Rep(F),[Rep(A_1), ..., Rep(A_k)]]}]]></c>.
+ </item>
+ <item>If G is a type definition <c><![CDATA[Name :: Type]]></c>,
+ where <c><![CDATA[Name]]></c> is a variable and
+ <c><![CDATA[Type]]></c> is a type, then Rep(G) =
+ <c><![CDATA[{type,LINE,constraint,[{atom,LINE,is_subtype},[Rep(Name),Rep(Type)]]}]]></c>.</item>
+ </list>
+ </section>
+
+ <section>
+ <title>Types</title>
+ <list type="bulleted">
+ <item>If T is a type definition <c><![CDATA[Name :: Type]]></c>,
+ where <c><![CDATA[Name]]></c> is a variable and
+ <c><![CDATA[Type]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{ann_type,LINE,[Rep(Name),Rep(Type)]}]]></c>.</item>
+ <item>If T is a type union <c><![CDATA[A_1 | ... | A_k]]></c>,
+ where each <c><![CDATA[A_i]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{type,LINE,union,[Rep(A_1), ..., Rep(A_k)]}]]></c>.</item>
+ <item>If T is a type range <c><![CDATA[L .. R]]></c>,
+ where <c><![CDATA[L]]></c> and <c><![CDATA[R]]></c> are types, then
+ Rep(T) = <c><![CDATA[{type,LINE,range,[Rep(L), Rep(R)]}]]></c>.</item>
+ <item>If T is a binary operation <c><![CDATA[L Op R]]></c>,
+ where <c><![CDATA[Op]]></c> is an arithmetic or bitwise binary operator
+ and <c><![CDATA[L]]></c> and <c><![CDATA[R]]></c> are types, then
+ Rep(T) = <c><![CDATA[{op,LINE,Op,Rep(L),Rep(R)}]]></c>.</item>
+ <item>If T is <c><![CDATA[Op A]]></c>, where <c><![CDATA[Op]]></c> is an
+ arithmetic or bitwise unary operator and <c><![CDATA[A]]></c> is a
+ type, then Rep(T) = <c><![CDATA[{op,LINE,Op,Rep(A)}]]></c>.</item>
+ <item>If T is a fun type <c><![CDATA[fun()]]></c>, then Rep(T) =
+ <c><![CDATA[{type,LINE,'fun',[]}]]></c>.</item>
+ <item>If T is a variable <c><![CDATA[V]]></c>, then Rep(T) =
+ <c><![CDATA[{var,LINE,A}]]></c>, where <c><![CDATA[A]]></c> is an atom
+ with a printname consisting of the same characters as
+ <c><![CDATA[V]]></c>.</item>
+ <item>If T is an atomic literal L and L is not a string literal, then
+ Rep(T) = Rep(L).</item>
+ <item>If T is a tuple or map type <c><![CDATA[F()]]></c> (i.e.
+ <c><![CDATA[tuple]]></c> or <c><![CDATA[map]]></c>), then Rep(T) =
+ <c><![CDATA[{type,LINE,F,any}]]></c>.</item>
+ <item>If T is a type <c><![CDATA[F(A_1, ..., A_k)]]></c>, where each
+ <c><![CDATA[A_i]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{user_type,LINE,F,[Rep(A_1), ..., Rep(A_k)]}]]></c>.</item>
+ <item>If T is a remote type <c><![CDATA[M:F(A_1, ..., A_k)]]></c>, where
+ each <c><![CDATA[A_i]]></c> is a type and <c><![CDATA[M]]></c> and
+ <c><![CDATA[F]]></c>, then Rep(T) =
+ <c><![CDATA[{remote_type,LINE,[Rep(M),Rep(F),[Rep(A_1), ..., Rep(A_k)]]}]]></c>.
+ </item>
+ <item>If T is the nil type <c><![CDATA[[]]]></c>, then Rep(T) =
+ <c><![CDATA[{type,LINE,nil,[]}]]></c>.</item>
+ <item>If T is a list type <c><![CDATA[[A]]]></c>, where
+ <c><![CDATA[A]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{type,LINE,list,[Rep(A)]}]]></c>.</item>
+ <item>If T is a non-empty list type <c><![CDATA[[A, ...]]]></c>, where
+ <c><![CDATA[A]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{type,LINE,nonempty_list,[Rep(A)]}]]></c>.</item>
+ <item>If T is a map type <c><![CDATA[#{P_1, ..., P_k}]]></c>, where each
+ <c><![CDATA[P_i]]></c> is a map pair type, then Rep(T) =
+ <c><![CDATA[{type,LINE,map,[Rep(P_1), ..., Rep(P_k)]}]]></c>.</item>
+ <item>If T is a map pair type <c><![CDATA[K => V]]></c>, where
+ <c><![CDATA[K]]></c> and <c><![CDATA[V]]></c> are types,
+ then Rep(T) =
+ <c><![CDATA[{type,LINE,map_field_assoc,[Rep(K),Rep(V)]}]]></c>.</item>
+ <item>If T is a tuple type <c><![CDATA[{A_1, ..., A_k}]]></c>, where
+ each <c><![CDATA[A_i]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{type,LINE,tuple,[Rep(A_1), ..., Rep(A_k)]}]]></c>.</item>
+ <item>If T is a record type <c><![CDATA[#Name{}]]></c>, where
+ <c><![CDATA[Name]]></c> is an atom, then Rep(T) =
+ <c><![CDATA[{type,LINE,record,[Rep(Name)]}]]></c>.</item>
+ <item>If T is a record type <c><![CDATA[#Name{F_1, ..., F_k}]]></c>,
+ where <c><![CDATA[Name]]></c> is an atom, then Rep(T) =
+ <c><![CDATA[{type,LINE,record,[Rep(Name),[Rep(F_1), ..., Rep(F_k)]]}]]></c>.
+ </item>
+ <item>If T is a record field type <c><![CDATA[Name :: Type]]></c>,
+ where <c><![CDATA[Name]]></c> is an atom, then Rep(T) =
+ <c><![CDATA[{type,LINE,field_type,[Rep(Name),Rep(Type)]}]]></c>.</item>
+ <item>If T is a record field type <c><![CDATA[<<>>]]></c>, then Rep(T) =
+ <c><![CDATA[{type,LINE,binary,[{integer,LINE,0},{integer,LINE,0}]}]]></c>.
+ </item>
+ <item>If T is a binary type <c><![CDATA[<< _ : B >>]]></c>, where
+ <c><![CDATA[B]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{type,LINE,binary,[Rep(B),{integer,LINE,0}]}]]></c>.</item>
+ <item>If T is a binary type <c><![CDATA[<< _ : _ * U >>]]></c>,
+ where <c><![CDATA[U]]></c> is a type, then Rep(T) =
+ <c><![CDATA[{type,LINE,binary,[{integer,LINE,0},Rep(U)]}]]></c>.</item>
+ <item>If T is a binary type <c><![CDATA[<< _ : B , _ : _ * U >>]]></c>,
+ where <c><![CDATA[B]]></c> and <c><![CDATA[U]]></c> is a type, then
+ Rep(T) =
+ <c><![CDATA[{type,LINE,binary,[Rep(B),Rep(U)]}]]></c>.</item>
+
+ <item>If T is a fun type <c><![CDATA[fun((...) -> Ret)]]></c>, then
+ Rep(T) = <c><![CDATA[{type,LINE,'fun',[{type,LINE,product,[]},Rep(Ret)]}]]></c>.
+ </item>
+ <item>If T is a fun type <c><![CDATA[fun(Tc)]]></c>, where
+ <c><![CDATA[Tc]]></c> is an unbounded fun type clause,
+ then Rep(T) = <c><![CDATA[Rep(Tc)]]></c>.</item>
+ </list>
+ </section>
+
+ <section>
<title>Record fields</title>
<p>Each field in a record declaration may have an optional
explicit default initializer expression</p>
@@ -98,6 +241,21 @@
Rep(V) = <c><![CDATA[{record_field,LINE,Rep(A)}]]></c>.</item>
<item>If V is <c><![CDATA[A = E]]></c>, then
Rep(V) = <c><![CDATA[{record_field,LINE,Rep(A),Rep(E)}]]></c>.</item>
+ <item>If V is <c><![CDATA[A :: T]]></c>, where <c><![CDATA[A]]></c> is
+ an atom and <c><![CDATA[T]]></c> is a type and it does not contain
+ <c><![CDATA[undefined]]></c> syntactically, then Rep(V) =
+ <c><![CDATA[{typed_record_field,{record_field,LINE,Rep(A)},Rep(undefined | T)}]]></c>.
+ Note that if <![CDATA[T]]> is an annotated type, it will be wrapped in
+ parentheses.</item>
+ <item>If V is <c><![CDATA[A :: T]]></c>, where <c><![CDATA[A]]></c> is
+ an atom and <c><![CDATA[T]]></c> is a type, then Rep(V) =
+ <c><![CDATA[{typed_record_field,{record_field,LINE,Rep(A)},Rep(T)}]]></c>.
+ </item>
+ <item>If V is <c><![CDATA[A = E :: T]]></c>, where <c><![CDATA[A]]></c>
+ is an atom, <c><![CDATA[E]]></c> is an expression and
+ <c><![CDATA[T]]></c> is a type, then Rep(V) =
+ <c><![CDATA[{typed_record_field,{record_field,LINE,Rep(A),Rep(E)},Rep(T)}]]></c>.
+ </item>
</list>
</section>
diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl
index 62d5eb9a18..b67ec31ae3 100644
--- a/lib/edoc/src/edoc_layout.erl
+++ b/lib/edoc/src/edoc_layout.erl
@@ -520,7 +520,7 @@ format_spec(Name, Type, Defs, #opts{pretty_printer = erl_pp}=Opts) ->
{R, ".\n"} = etypef(L, O),
[{pre, R}]
catch _:_ ->
- %% Example: "@spec ... -> record(a)"
+ %% Should not happen.
format_spec(Name, Type, Defs, Opts#opts{pretty_printer=''})
end;
format_spec(Sep, Type, Defs, _Opts) ->
diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl
index 59f6cb8ddf..eb69058148 100644
--- a/lib/edoc/src/edoc_specs.erl
+++ b/lib/edoc/src/edoc_specs.erl
@@ -295,47 +295,54 @@ arg_name([A | As], Default) ->
is_name(A) ->
is_atom(A).
-d2e({ann_type,_,[V, T0]}) ->
+d2e(T) ->
+ d2e(T, 0).
+
+d2e({ann_type,_,[V, T0]}, Prec) ->
%% Note: the -spec/-type syntax allows annotations everywhere, but
%% EDoc does not. The fact that the annotation is added to the
%% type here does not necessarily mean that it will be used by the
%% layout module.
- T = d2e(T0),
- ?add_t_ann(T, element(3, V));
-d2e({remote_type,_,[{atom,_,M},{atom,_,F},Ts0]}) ->
+ {_L,P,R} = erl_parse:type_inop_prec('::'),
+ T1 = d2e(T0, R),
+ T = ?add_t_ann(T1, element(3, V)),
+ maybe_paren(P, Prec, T); % the only necessary call to maybe_paren()
+d2e({remote_type,_,[{atom,_,M},{atom,_,F},Ts0]}, _Prec) ->
Ts = d2e(Ts0),
typevar_anno(#t_type{name = #t_name{module = M, name = F}, args = Ts}, Ts);
-d2e({type,_,'fun',[{type,_,product,As0},Ran0]}) ->
+d2e({type,_,'fun',[{type,_,product,As0},Ran0]}, _Prec) ->
Ts = [Ran|As] = d2e([Ran0|As0]),
%% Assume that the linter has checked type variables.
typevar_anno(#t_fun{args = As, range = Ran}, Ts);
-d2e({type,_,'fun',[A0={type,_,any},Ran0]}) ->
+d2e({type,_,'fun',[A0={type,_,any},Ran0]}, _Prec) ->
Ts = [A, Ran] = d2e([A0, Ran0]),
typevar_anno(#t_fun{args = [A], range = Ran}, Ts);
-d2e({type,_,'fun',[]}) ->
+d2e({type,_,'fun',[]}, _Prec) ->
#t_type{name = #t_name{name = function}, args = []};
-d2e({type,_,any}) ->
+d2e({type,_,any}, _Prec) ->
#t_var{name = '...'}; % Kludge... not a type variable!
-d2e({type,_,nil,[]}) ->
+d2e({type,_,nil,[]}, _Prec) ->
#t_nil{};
-d2e({paren_type,_,[T]}) ->
- #t_paren{type = d2e(T)};
-d2e({type,_,list,[T0]}) ->
+d2e({paren_type,_,[T]}, Prec) ->
+ d2e(T, Prec);
+d2e({type,_,list,[T0]}, _Prec) ->
T = d2e(T0),
typevar_anno(#t_list{type = T}, [T]);
-d2e({type,_,nonempty_list,[T0]}) ->
+d2e({type,_,nonempty_list,[T0]}, _Prec) ->
T = d2e(T0),
typevar_anno(#t_nonempty_list{type = T}, [T]);
-d2e({type,_,bounded_fun,[T,Gs]}) ->
+d2e({type,_,bounded_fun,[T,Gs]}, _Prec) ->
[F0|Defs] = d2e([T|Gs]),
F = ?set_t_ann(F0, lists:keydelete(type_variables, 1, ?t_ann(F0))),
%% Assume that the linter has checked type variables.
#t_spec{type = typevar_anno(F, [F0]), defs = Defs};
-d2e({type,_,range,[V1,V2]}) ->
+d2e({type,_,range,[V1,V2]}, Prec) ->
+ {_L,P,_R} = erl_parse:type_inop_prec('..'),
{integer,_,I1} = erl_eval:partial_eval(V1),
{integer,_,I2} = erl_eval:partial_eval(V2),
- #t_integer_range{from = I1, to = I2};
-d2e({type,_,constraint,[Sub,Ts0]}) ->
+ T0 = #t_integer_range{from = I1, to = I2},
+ maybe_paren(P, Prec, T0);
+d2e({type,_,constraint,[Sub,Ts0]}, _Prec) ->
case {Sub,Ts0} of
{{atom,_,is_subtype},[{var,_,N},T0]} ->
Ts = [T] = d2e([T0]),
@@ -347,50 +354,60 @@ d2e({type,_,constraint,[Sub,Ts0]}) ->
_ ->
throw_error(get_line(element(2, Sub)), "cannot handle guard", [])
end;
-d2e({type,_,union,Ts0}) ->
- Ts = d2e(Ts0),
- typevar_anno(#t_union{types = Ts}, Ts);
-d2e({type,_,tuple,any}) ->
+d2e({type,_,union,Ts0}, Prec) ->
+ {_L,P,R} = erl_parse:type_inop_prec('|'),
+ Ts = d2e(Ts0, R),
+ T = maybe_paren(P, Prec, #t_union{types = Ts}),
+ typevar_anno(T, Ts);
+d2e({type,_,tuple,any}, _Prec) ->
#t_type{name = #t_name{name = tuple}, args = []};
-d2e({type,_,binary,[Base,Unit]}) ->
- #t_binary{base_size = element(3, Base),
- unit_size = element(3, Unit)};
-d2e({type,_,map,any}) ->
- #t_map{ types = []};
-d2e({type,_,map,Es}) ->
- #t_map{ types = d2e(Es) };
-d2e({type,_,map_field_assoc,[K,V]}) ->
- #t_map_field{ k_type = d2e(K), v_type=d2e(V) };
-d2e({type,_,map_field_exact,K,V}) ->
- #t_map_field{ k_type = d2e(K), v_type=d2e(V) };
-d2e({type,_,tuple,Ts0}) ->
+d2e({type,_,binary,[Base,Unit]}, _Prec) ->
+ {integer,_,B} = erl_eval:partial_eval(Base),
+ {integer,_,U} = erl_eval:partial_eval(Unit),
+ #t_binary{base_size = B, unit_size = U};
+d2e({type,_,map,any}, _Prec) ->
+ #t_map{types = []};
+d2e({type,_,map,Es}, _Prec) ->
+ #t_map{types = d2e(Es) };
+d2e({type,_,map_field_assoc,[K,V]}, Prec) ->
+ T = #t_map_field{k_type = d2e(K), v_type=d2e(V) },
+ {P,_R} = erl_parse:type_preop_prec('#'),
+ maybe_paren(P, Prec, T);
+d2e({type,_,map_field_exact,K,V}, Prec) ->
+ T = #t_map_field{k_type = d2e(K), v_type=d2e(V) },
+ {P,_R} = erl_parse:type_preop_prec('#'),
+ maybe_paren(P, Prec, T);
+d2e({type,_,tuple,Ts0}, _Prec) ->
Ts = d2e(Ts0),
typevar_anno(#t_tuple{types = Ts}, Ts);
-d2e({type,_,record,[Name|Fs0]}) ->
+d2e({type,_,record,[Name|Fs0]}, Prec) ->
Atom = #t_atom{val = element(3, Name)},
Fs = d2e(Fs0),
- typevar_anno(#t_record{name = Atom, fields = Fs}, Fs);
-d2e({type,_,field_type,[Name,Type0]}) ->
- Type = d2e(Type0),
- typevar_anno(#t_field{name = #t_atom{val = element(3, Name)}, type = Type},
- [Type]);
-d2e({typed_record_field,{record_field,L,Name},Type}) ->
- d2e({type,L,field_type,[Name,Type]});
-d2e({typed_record_field,{record_field,L,Name,_E},Type}) ->
- d2e({type,L,field_type,[Name,Type]});
-d2e({record_field,L,_Name,_E}=F) ->
- d2e({typed_record_field,F,{type,L,any,[]}}); % Maybe skip...
-d2e({record_field,L,_Name}=F) ->
- d2e({typed_record_field,F,{type,L,any,[]}}); % Maybe skip...
-d2e({type,_,Name,Types0}) ->
+ {P,_R} = erl_parse:type_preop_prec('#'),
+ T = maybe_paren(P, Prec, #t_record{name = Atom, fields = Fs}),
+ typevar_anno(T, Fs);
+d2e({type,_,field_type,[Name,Type0]}, Prec) ->
+ {_L,P,R} = erl_parse:type_inop_prec('::'),
+ Type = maybe_paren(P, Prec, d2e(Type0, R)),
+ T = #t_field{name = #t_atom{val = element(3, Name)}, type = Type},
+ typevar_anno(T, [Type]);
+d2e({typed_record_field,{record_field,L,Name},Type}, Prec) ->
+ d2e({type,L,field_type,[Name,Type]}, Prec);
+d2e({typed_record_field,{record_field,L,Name,_E},Type}, Prec) ->
+ d2e({type,L,field_type,[Name,Type]}, Prec);
+d2e({record_field,L,_Name,_E}=F, Prec) ->
+ d2e({typed_record_field,F,{type,L,any,[]}}, Prec); % Maybe skip...
+d2e({record_field,L,_Name}=F, Prec) ->
+ d2e({typed_record_field,F,{type,L,any,[]}}, Prec); % Maybe skip...
+d2e({type,_,Name,Types0}, _Prec) ->
Types = d2e(Types0),
typevar_anno(#t_type{name = #t_name{name = Name}, args = Types}, Types);
-d2e({user_type,_,Name,Types0}) ->
+d2e({user_type,_,Name,Types0}, _Prec) ->
Types = d2e(Types0),
typevar_anno(#t_type{name = #t_name{name = Name}, args = Types}, Types);
-d2e({var,_,'_'}) ->
+d2e({var,_,'_'}, _Prec) ->
#t_type{name = #t_name{name = ?TOP_TYPE}};
-d2e({var,_,TypeName}) ->
+d2e({var,_,TypeName}, _Prec) ->
TypeVar = ordsets:from_list([TypeName]),
T = #t_var{name = TypeName},
%% Annotate type variables with the name of the variable.
@@ -398,13 +415,13 @@ d2e({var,_,TypeName}) ->
%% from using the argument name from the source or to invent a new name.
T1 = ?add_t_ann(T, {type_variables, TypeVar}),
?add_t_ann(T1, TypeName);
-d2e(L) when is_list(L) ->
- [d2e(T) || T <- L];
-d2e({atom,_,A}) ->
+d2e(L, Prec) when is_list(L) ->
+ [d2e(T, Prec) || T <- L];
+d2e({atom,_,A}, _Prec) ->
#t_atom{val = A};
-d2e(undefined = U) -> % opaque
+d2e(undefined = U, _Prec) -> % opaque
U;
-d2e(Expr) ->
+d2e(Expr, _Prec) ->
{integer,_,I} = erl_eval:partial_eval(Expr),
#t_integer{val = I}.
@@ -422,6 +439,11 @@ typevars(Ts) ->
get_typevars(Ts) ->
[Vs || T <- Ts, T =/= undefined, {type_variables, Vs} <- ?t_ann(T)].
+maybe_paren(P, Prec, T) when P < Prec ->
+ #t_paren{type = T};
+maybe_paren(_P, _Prec, T) ->
+ T.
+
-record(parms, {tab, warn, file, line}).
%% Expands record references. Explicitly given record fields are kept,
@@ -484,11 +506,11 @@ xrecs(#t_fun{args = Args0, range = Range0}=T, P) ->
Args = xrecs(Args0, P),
Range = xrecs(Range0, P),
T#t_fun{args = Args, range = Range};
-xrecs(#t_map{ types = Ts0 }=T,P) ->
+xrecs(#t_map{types = Ts0 }=T,P) ->
Ts = xrecs(Ts0, P),
- T#t_map{ types = Ts };
-xrecs(#t_map_field{ k_type=Kt, v_type=Vt}=T, P) ->
- T#t_map_field{ k_type=xrecs(Kt,P), v_type=xrecs(Vt,P)};
+ T#t_map{types = Ts };
+xrecs(#t_map_field{k_type=Kt, v_type=Vt}=T, P) ->
+ T#t_map_field{k_type=xrecs(Kt,P), v_type=xrecs(Vt,P)};
xrecs(#t_tuple{types = Types0}=T, P) ->
Types = xrecs(Types0, P),
T#t_tuple{types = Types};
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index e328e065e3..274bb2a782 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -125,22 +125,19 @@ top_type_100 -> type_200 : '$1'.
top_type_100 -> type_200 '|' top_type_100 : lift_unions('$1','$3').
type_200 -> type_300 '..' type_300 : {type, ?anno('$1'), range,
- [skip_paren('$1'),
- skip_paren('$3')]}.
+ ['$1', '$3']}.
type_200 -> type_300 : '$1'.
-type_300 -> type_300 add_op type_400 : ?mkop2(skip_paren('$1'),
- '$2', skip_paren('$3')).
+type_300 -> type_300 add_op type_400 : ?mkop2('$1', '$2', '$3').
type_300 -> type_400 : '$1'.
-type_400 -> type_400 mult_op type_500 : ?mkop2(skip_paren('$1'),
- '$2', skip_paren('$3')).
+type_400 -> type_400 mult_op type_500 : ?mkop2('$1', '$2', '$3').
type_400 -> type_500 : '$1'.
-type_500 -> prefix_op type : ?mkop1('$1', skip_paren('$2')).
+type_500 -> prefix_op type : ?mkop1('$1', '$2').
type_500 -> type : '$1'.
-type -> '(' top_type ')' : {paren_type, ?anno('$2'), ['$2']}.
+type -> '(' top_type ')' : '$2'.
type -> var : '$1'.
type -> atom : '$1'.
type -> atom '(' ')' : build_gen_type('$1').
@@ -524,6 +521,7 @@ Erlang code.
-export([normalise/1,abstract/1,tokens/1,tokens/2]).
-export([abstract/2]).
-export([inop_prec/1,preop_prec/1,func_prec/0,max_prec/0]).
+-export([type_inop_prec/1,type_preop_prec/1]).
-export([map_anno/2, fold_anno/3, mapfold_anno/3,
new_anno/1, anno_to_term/1, anno_from_term/1]).
-export([set_line/2,get_attribute/2,get_attributes/1]).
@@ -671,11 +669,6 @@ lift_unions(T1, {type, _Aa, union, List}) ->
lift_unions(T1, T2) ->
{type, ?anno(T1), union, [T1, T2]}.
-skip_paren({paren_type,_A,[Type]}) ->
- skip_paren(Type);
-skip_paren(Type) ->
- Type.
-
build_gen_type({atom, Aa, tuple}) ->
{type, Aa, tuple, any};
build_gen_type({atom, Aa, map}) ->
@@ -687,7 +680,7 @@ build_gen_type({atom, Aa, Name}) ->
build_bin_type([{var, _, '_'}|Left], Int) ->
build_bin_type(Left, Int);
build_bin_type([], Int) ->
- skip_paren(Int);
+ Int;
build_bin_type([{var, Aa, _}|_], _) ->
ret_err(Aa, "Bad binary type").
@@ -807,8 +800,7 @@ record_fields([{typed,Expr,TypeInfo}|Fields]) ->
{atom, Aa, _} ->
case has_undefined(TypeInfo) of
false ->
- TypeInfo2 = maybe_add_paren(TypeInfo),
- lift_unions(abstract2(undefined, Aa), TypeInfo2);
+ lift_unions(abstract2(undefined, Aa), TypeInfo);
true ->
TypeInfo
end
@@ -822,18 +814,11 @@ has_undefined({atom,_,undefined}) ->
true;
has_undefined({ann_type,_,[_,T]}) ->
has_undefined(T);
-has_undefined({paren_type,_,[T]}) ->
- has_undefined(T);
has_undefined({type,_,union,Ts}) ->
lists:any(fun has_undefined/1, Ts);
has_undefined(_) ->
false.
-maybe_add_paren({ann_type,A,T}) ->
- {paren_type,A,[{ann_type,A,T}]};
-maybe_add_paren(T) ->
- T.
-
term(Expr) ->
try normalise(Expr)
catch _:_R -> ret_err(?anno(Expr), "bad attribute")
@@ -1099,6 +1084,39 @@ func_prec() -> {800,700}.
max_prec() -> 900.
+-type prec() :: non_neg_integer().
+
+-type type_inop() :: '::' | '|' | '..' | '+' | '-' | 'bor' | 'bxor'
+ | 'bsl' | 'bsr' | '*' | '/' | 'div' | 'rem' | 'band'.
+
+-type type_preop() :: '+' | '-' | 'bnot' | '#'.
+
+-spec type_inop_prec(type_inop()) -> {prec(), prec(), prec()}.
+
+type_inop_prec('=') -> {150,100,100};
+type_inop_prec('::') -> {160,150,150};
+type_inop_prec('|') -> {180,170,170};
+type_inop_prec('..') -> {300,200,300};
+type_inop_prec('+') -> {400,400,500};
+type_inop_prec('-') -> {400,400,500};
+type_inop_prec('bor') -> {400,400,500};
+type_inop_prec('bxor') -> {400,400,500};
+type_inop_prec('bsl') -> {400,400,500};
+type_inop_prec('bsr') -> {400,400,500};
+type_inop_prec('*') -> {500,500,600};
+type_inop_prec('/') -> {500,500,600};
+type_inop_prec('div') -> {500,500,600};
+type_inop_prec('rem') -> {500,500,600};
+type_inop_prec('band') -> {500,500,600};
+type_inop_prec('#') -> {800,700,800}.
+
+-spec type_preop_prec(type_preop()) -> {prec(), prec()}.
+
+type_preop_prec('+') -> {600,700};
+type_preop_prec('-') -> {600,700};
+type_preop_prec('bnot') -> {600,700};
+type_preop_prec('#') -> {700,800}.
+
%%% [Experimental]. The parser just copies the attributes of the
%%% scanner tokens to the abstract format. This design decision has
%%% been hidden to some extent: use set_line() and get_attribute() to
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 623a29f923..6da585b72e 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -27,7 +27,8 @@
-import(lists, [append/1,foldr/3,mapfoldl/3,reverse/1,reverse/2]).
-import(io_lib, [write/1,format/2]).
--import(erl_parse, [inop_prec/1,preop_prec/1,func_prec/0,max_prec/0]).
+-import(erl_parse, [inop_prec/1,preop_prec/1,func_prec/0,max_prec/0,
+ type_inop_prec/1, type_preop_prec/1]).
-define(MAXLINE, 72).
@@ -271,49 +272,64 @@ typeattr(Tag, {TypeName,Type,Args}, _Opts) ->
{first,leaf("-"++atom_to_list(Tag)++" "),
typed(call({atom,a0(),TypeName}, Args, 0, options(none)), Type)}.
-ltype({ann_type,_Line,[V,T]}) ->
- typed(lexpr(V, options(none)), T);
-ltype({paren_type,_Line,[T]}) ->
- [$(,ltype(T),$)];
-ltype({type,_Line,union,Ts}) ->
- {seq,[],[],[' |'],ltypes(Ts)};
-ltype({type,_Line,list,[T]}) ->
+ltype(T) ->
+ ltype(T, 0).
+
+ltype({ann_type,_Line,[V,T]}, Prec) ->
+ {_L,P,_R} = type_inop_prec('::'),
+ E = typed(lexpr(V, options(none)), T),
+ maybe_paren(P, Prec, E);
+ltype({paren_type,_Line,[T]}, P) ->
+ %% Generated before Erlang/OTP 18.
+ ltype(T, P);
+ltype({type,_Line,union,Ts}, Prec) ->
+ {_L,P,R} = type_inop_prec('|'),
+ E = {seq,[],[],[' |'],ltypes(Ts, R)},
+ maybe_paren(P, Prec, E);
+ltype({type,_Line,list,[T]}, _) ->
{seq,$[,$],$,,[ltype(T)]};
-ltype({type,_Line,nonempty_list,[T]}) ->
+ltype({type,_Line,nonempty_list,[T]}, _) ->
{seq,$[,$],[$,],[ltype(T),leaf("...")]};
-ltype({type,Line,nil,[]}) ->
- lexpr({nil,Line}, 0, options(none));
-ltype({type,Line,map,any}) ->
+ltype({type,Line,nil,[]}, _) ->
+ lexpr({nil,Line}, options(none));
+ltype({type,Line,map,any}, _) ->
simple_type({atom,Line,map}, []);
-ltype({type,_Line,map,Pairs}) ->
- map_type(Pairs);
-ltype({type,Line,tuple,any}) ->
+ltype({type,_Line,map,Pairs}, Prec) ->
+ {P,_R} = type_preop_prec('#'),
+ E = map_type(Pairs),
+ maybe_paren(P, Prec, E);
+ltype({type,Line,tuple,any}, _) ->
simple_type({atom,Line,tuple}, []);
-ltype({type,_Line,tuple,Ts}) ->
- tuple_type(Ts, fun ltype/1);
-ltype({type,_Line,record,[{atom,_,N}|Fs]}) ->
- record_type(N, Fs);
-ltype({type,_Line,range,[_I1,_I2]=Es}) ->
- expr_list(Es, '..', fun lexpr/2, options(none));
-ltype({type,_Line,binary,[I1,I2]}) ->
+ltype({type,_Line,tuple,Ts}, _) ->
+ tuple_type(Ts, fun ltype/2);
+ltype({type,_Line,record,[{atom,_,N}|Fs]}, Prec) ->
+ {P,_R} = type_preop_prec('#'),
+ E = record_type(N, Fs),
+ maybe_paren(P, Prec, E);
+ltype({type,_Line,range,[_I1,_I2]=Es}, Prec) ->
+ {_L,P,R} = type_inop_prec('..'),
+ F = fun(E, Opts) -> lexpr(E, R, Opts) end,
+ E = expr_list(Es, '..', F, options(none)),
+ maybe_paren(P, Prec, E);
+ltype({type,_Line,binary,[I1,I2]}, _) ->
binary_type(I1, I2); % except binary()
-ltype({type,_Line,'fun',[]}) ->
+ltype({type,_Line,'fun',[]}, _) ->
leaf("fun()");
-ltype({type,_,'fun',[{type,_,any},_]}=FunType) ->
+ltype({type,_,'fun',[{type,_,any},_]}=FunType, _) ->
[fun_type(['fun',$(], FunType),$)];
-ltype({type,_Line,'fun',[{type,_,product,_},_]}=FunType) ->
+ltype({type,_Line,'fun',[{type,_,product,_},_]}=FunType, _) ->
[fun_type(['fun',$(], FunType),$)];
-ltype({type,Line,T,Ts}) ->
+ltype({type,Line,T,Ts}, _) ->
%% Compatibility. Before 18.0.
simple_type({atom,Line,T}, Ts);
-ltype({user_type,Line,T,Ts}) ->
+ltype({user_type,Line,T,Ts}, _) ->
simple_type({atom,Line,T}, Ts);
-ltype({remote_type,Line,[M,F,Ts]}) ->
+ltype({remote_type,Line,[M,F,Ts]}, _) ->
simple_type({remote,Line,M,F}, Ts);
-ltype({atom,_,T}) ->
+ltype({atom,_,T}, _) ->
leaf(write(T));
-ltype(E) ->
- lexpr(E, 0, options(none)).
+ltype(E, P) ->
+ lexpr(E, P, options(none)).
binary_type(I1, I2) ->
B = [[] || {integer,_,0} <- [I1]] =:= [],
@@ -327,42 +343,37 @@ map_type(Fs) ->
{first,[$#],map_pair_types(Fs)}.
map_pair_types(Fs) ->
- tuple_type(Fs, fun map_pair_type/1).
+ tuple_type(Fs, fun map_pair_type/2).
-map_pair_type({type,_Line,map_field_assoc,[Ktype,Vtype]}) ->
- map_assoc_typed(ltype(Ktype), Vtype).
+map_pair_type({type,_Line,map_field_assoc,[Ktype,Vtype]}, Prec) ->
+ map_assoc_typed(ltype(Ktype), Vtype, Prec).
-map_assoc_typed(B, {type,_,union,Ts}) ->
- {first,[B,$\s],{seq,[],[],[],map_assoc_union_type(Ts)}};
-map_assoc_typed(B, Type) ->
- {list,[{cstep,[B," =>"],ltype(Type)}]}.
+map_assoc_typed(B, {type,_,union,Ts}, Prec) ->
+ {first,[B,$\s],{seq,[],[],[],map_assoc_union_type(Ts, Prec)}};
+map_assoc_typed(B, Type, Prec) ->
+ {list,[{cstep,[B," =>"],ltype(Type, Prec)}]}.
-map_assoc_union_type([T|Ts]) ->
- [[leaf("=> "),ltype(T)] | ltypes(Ts, fun union_elem/1)].
+map_assoc_union_type([T|Ts], Prec) ->
+ [[leaf("=> "),ltype(T)] | ltypes(Ts, fun union_elem/2, Prec)].
record_type(Name, Fields) ->
{first,[record_name(Name)],field_types(Fields)}.
field_types(Fs) ->
- tuple_type(Fs, fun field_type/1).
+ tuple_type(Fs, fun field_type/2).
-field_type({type,_Line,field_type,[Name,Type]}) ->
+field_type({type,_Line,field_type,[Name,Type]}, _Prec) ->
typed(lexpr(Name, options(none)), Type).
-typed(B, {type,_,union,Ts}) ->
- %% Special layout for :: followed by union.
- {first,[B,$\s],{seq,[],[],[],union_type(Ts)}};
typed(B, Type) ->
- {list,[{cstep,[B,' ::'],ltype(Type)}]}.
+ {_L,_P,R} = type_inop_prec('::'),
+ {list,[{cstep,[B,' ::'],ltype(Type, R)}]}.
-union_type([T|Ts]) ->
- [[leaf(":: "),ltype(T)] | ltypes(Ts, fun union_elem/1)].
-
-union_elem(T) ->
- [leaf(" | "),ltype(T)].
+union_elem(T, Prec) ->
+ [leaf(" | "),ltype(T, Prec)].
tuple_type(Ts, F) ->
- {seq,${,$},[$,],ltypes(Ts, F)}.
+ {seq,${,$},[$,],ltypes(Ts, F, 0)}.
specattr(SpecKind, {FuncSpec,TypeSpecs}) ->
Func = case FuncSpec of
@@ -399,16 +410,16 @@ type_args({type,_line,product,Ts}) ->
targs(Ts).
simple_type(Tag, Types) ->
- {first,lexpr(Tag, 0, options(none)),targs(Types)}.
+ {first,lexpr(Tag, options(none)),targs(Types)}.
targs(Ts) ->
- {seq,$(,$),[$,],ltypes(Ts)}.
+ {seq,$(,$),[$,],ltypes(Ts, 0)}.
-ltypes(Ts) ->
- ltypes(Ts, fun ltype/1).
+ltypes(Ts, Prec) ->
+ ltypes(Ts, fun ltype/2, Prec).
-ltypes(Ts, F) ->
- [F(T) || T <- Ts].
+ltypes(Ts, F, Prec) ->
+ [F(T, Prec) || T <- Ts].
attr(Name, Args) ->
call({var,a0(),format("-~s", [Name])}, Args, 0, options(none)).
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index 1d63c8e17e..afeeb5bfd4 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -1149,7 +1149,7 @@ otp_11100(Config) when is_list(Config) ->
{a,{type,A1,range,[{integer,A1,1},{foo,bar}]},[]}}),
"-type foo(INVALID-FORM:{foo,bar}:) :: A.\n" =
pf({attribute,A1,type,{foo,{var,A1,'A'},[{foo,bar}]}}),
- "-type foo() :: (INVALID-FORM:{foo,bar}: :: []).\n" =
+ "-type foo() :: INVALID-FORM:{foo,bar}: :: [].\n" =
pf({attribute,A1,type,
{foo,{paren_type,A1,
[{ann_type,A1,[{foo,bar},{type,A1,nil,[]}]}]},