aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/common_test/doc/src/ct_hooks.xml4
-rw-r--r--lib/common_test/src/ct_hooks.erl8
-rw-r--r--lib/dialyzer/RELEASE_NOTES12
-rw-r--r--lib/dialyzer/src/dialyzer.erl10
-rw-r--r--lib/dialyzer/src/dialyzer.hrl5
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl40
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl142
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl1
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl22
-rw-r--r--lib/dialyzer/src/dialyzer_succ_typings.erl26
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl24
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/edoc/doc/overview.edoc103
-rw-r--r--lib/edoc/doc/src/Makefile2
-rw-r--r--lib/edoc/doc/src/ref_man.xml2
-rw-r--r--lib/edoc/src/Makefile3
-rw-r--r--lib/edoc/src/edoc.app.src1
-rw-r--r--lib/edoc/src/edoc.erl34
-rw-r--r--lib/edoc/src/edoc.hrl11
-rw-r--r--lib/edoc/src/edoc_data.erl16
-rw-r--r--lib/edoc/src/edoc_doclet.erl4
-rw-r--r--lib/edoc/src/edoc_extract.erl154
-rw-r--r--lib/edoc/src/edoc_layout.erl395
-rw-r--r--lib/edoc/src/edoc_lib.erl1
-rw-r--r--lib/edoc/src/edoc_parser.yrl106
-rw-r--r--lib/edoc/src/edoc_refs.erl2
-rw-r--r--lib/edoc/src/edoc_scanner.erl22
-rw-r--r--lib/edoc/src/edoc_specs.erl603
-rw-r--r--lib/edoc/src/edoc_tags.erl133
-rw-r--r--lib/edoc/src/edoc_types.erl107
-rw-r--r--lib/edoc/src/edoc_types.hrl45
-rw-r--r--lib/kernel/src/application.erl6
-rw-r--r--lib/kernel/src/disk_log_1.erl8
-rw-r--r--lib/kernel/src/erl_ddll.erl33
-rw-r--r--lib/kernel/src/error_handler.erl6
-rw-r--r--lib/kernel/src/global.erl30
-rw-r--r--lib/kernel/src/os.erl2
-rw-r--r--lib/stdlib/doc/src/filelib.xml2
-rw-r--r--lib/stdlib/doc/src/io.xml8
-rw-r--r--lib/stdlib/src/io_lib_format.erl44
-rw-r--r--lib/stdlib/test/io_SUITE.erl84
41 files changed, 1910 insertions, 353 deletions
diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml
index 0d59ce3b22..b52eb737ad 100644
--- a/lib/common_test/doc/src/ct_hooks.xml
+++ b/lib/common_test/doc/src/ct_hooks.xml
@@ -409,7 +409,7 @@
end_per_suite</seealso> if it exists. It behaves the same way as
<seealso marker="ct_hooks#Module:pre_init_per_suite-3">
pre_init_per_suite</seealso>, but for the
- <seealso marker="common_test#Module:end_per_suite-2">
+ <seealso marker="common_test#Module:end_per_suite-1">
end_per_suite</seealso> function instead.</p>
</desc>
</func>
@@ -438,7 +438,7 @@
end_per_suite</seealso> if it exists. It behaves the same way as
<seealso marker="ct_hooks#Module:post_init_per_suite-4">
post_init_per_suite</seealso>, but for the
- <seealso marker="common_test#Module:end_per_suite-2">
+ <seealso marker="common_test#Module:end_per_suite-1">
end_per_suite</seealso> function instead.</p>
</desc>
</func>
diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl
index 77b7566d9e..6f315d4b82 100644
--- a/lib/common_test/src/ct_hooks.erl
+++ b/lib/common_test/src/ct_hooks.erl
@@ -66,11 +66,13 @@ terminate(Hooks) ->
init_tc(ct_framework, _Func, Args) ->
Args;
init_tc(Mod, init_per_suite, Config) ->
- Info = case catch proplists:get_value(ct_hooks, Mod:suite()) of
+ Info = try proplists:get_value(ct_hooks, Mod:suite(),[]) of
List when is_list(List) ->
[{ct_hooks,List}];
- _Else ->
- []
+ CTHook when is_atom(CTHook) ->
+ [{ct_hooks,[CTHook]}]
+ catch error:undef ->
+ [{ct_hooks,[]}]
end,
call(fun call_generic/3, Config ++ Info, [pre_init_per_suite, Mod]);
init_tc(Mod, end_per_suite, Config) ->
diff --git a/lib/dialyzer/RELEASE_NOTES b/lib/dialyzer/RELEASE_NOTES
index 3fd5e9cc7d..4e311bb543 100644
--- a/lib/dialyzer/RELEASE_NOTES
+++ b/lib/dialyzer/RELEASE_NOTES
@@ -3,6 +3,18 @@
(in reversed chronological order)
==============================================================================
+Version 2.4.2 (in Erlang/OTP R14B02)
+------------------------------------
+ - Added --fullpath option to display files with warnings with their full
+ file names (thanks to Magnus Henoch for the original patch).
+ - Better handling of 'and'/'or'/'not' guards that generate warnings
+ (thanks to Stavros Aronis).
+ - Better blame assignment for cases when a function's spec is erroneous
+ (thanks to Stavros Aronis).
+ - More descriptive warnings when a tuple/record pattern contains subterms
+ that violate the declared types of record fields (thanks to Matthias Lang
+ for the test case and for Stavros Aronis for the actual fix).
+
Version 2.4.0 (in Erlang/OTP R14B01)
------------------------------------
- Added ability to supply multiple PLTs for the analysis (option --plts).
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index dde0c17c39..5014a4244c 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -334,6 +334,9 @@ message_to_string({guard_fail, []}) ->
"Clause guard cannot succeed.\n";
message_to_string({guard_fail, [Arg1, Infix, Arg2]}) ->
io_lib:format("Guard test ~s ~s ~s can never succeed\n", [Arg1, Infix, Arg2]);
+message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}) ->
+ io_lib:format("Guard test not(~s ~s ~s) can never succeed\n",
+ [Arg1, Infix, Arg2]);
message_to_string({guard_fail, [Guard, Args]}) ->
io_lib:format("Guard test ~w~s can never succeed\n", [Guard, Args]);
message_to_string({neg_guard_fail, [Guard, Args]}) ->
@@ -365,6 +368,9 @@ message_to_string({record_constr, [Name, Field, Type]}) ->
message_to_string({record_matching, [String, Name]}) ->
io_lib:format("The ~s violates the"
" declared type for #~w{}\n", [String, Name]);
+message_to_string({record_match, [Pat, Type]}) ->
+ io_lib:format("Matching of ~s tagged with a record name violates the declared"
+ " type of ~s\n", [Pat, Type]);
message_to_string({pattern_match, [Pat, Type]}) ->
io_lib:format("The ~s can never match the type ~s\n", [Pat, Type]);
message_to_string({pattern_match_cov, [Pat, Type]}) ->
@@ -391,6 +397,10 @@ message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}) ->
io_lib:format("Type specification ~w:~w~s"
" is a supertype of the success typing: ~w:~w~s\n",
[M, F, Contract, M, F, Sig]);
+message_to_string({contract_range, [Contract, M, F, ArgStrings, Line, CRet]}) ->
+ io_lib:format("The contract ~w:~w~s cannot be right because the inferred"
+ " return for ~w~s on line ~w is ~s\n",
+ [M, F, Contract, F, ArgStrings, Line, CRet]);
message_to_string({invalid_contract, [M, F, A, Sig]}) ->
io_lib:format("Invalid type specification for function ~w:~w/~w."
" The success typing is ~s\n", [M, F, A, Sig]);
diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl
index aa3f703af2..9d2e554981 100644
--- a/lib/dialyzer/src/dialyzer.hrl
+++ b/lib/dialyzer/src/dialyzer.hrl
@@ -52,10 +52,11 @@
-define(WARN_CONTRACT_NOT_EQUAL, warn_contract_not_equal).
-define(WARN_CONTRACT_SUBTYPE, warn_contract_subtype).
-define(WARN_CONTRACT_SUPERTYPE, warn_contract_supertype).
+-define(WARN_CONTRACT_RANGE, warn_contract_range).
-define(WARN_CALLGRAPH, warn_callgraph).
-define(WARN_UNMATCHED_RETURN, warn_umatched_return).
-define(WARN_RACE_CONDITION, warn_race_condition).
--define(WARN_BEHAVIOUR,warn_behaviour).
+-define(WARN_BEHAVIOUR, warn_behaviour).
%%
%% The following type has double role:
@@ -70,7 +71,7 @@
| ?WARN_CONTRACT_NOT_EQUAL | ?WARN_CONTRACT_SUBTYPE
| ?WARN_CONTRACT_SUPERTYPE | ?WARN_CALLGRAPH
| ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION
- | ?WARN_BEHAVIOUR.
+ | ?WARN_BEHAVIOUR | ?WARN_CONTRACT_RANGE.
%%
%% This is the representation of each warning as they will be returned
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index 6ecec421a2..f80eb81ac6 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -20,10 +20,8 @@
-module(dialyzer_cl_parse).
-%% Avoid warning for local function error/1 clashing with autoimported BIF.
--compile({no_auto_import,[error/1]}).
-export([start/0, get_lib_dir/1]).
--export([collect_args/1]). % used also by typer_options.erl
+-export([collect_args/1]). % used also by typer
-include("dialyzer.hrl").
@@ -32,9 +30,11 @@
-type dial_cl_parse_ret() :: {'check_init', #options{}}
| {'plt_info', #options{}}
| {'cl', #options{}}
- | {{'gui', 'gs' | 'wx'}, #options{}}
+ | {{'gui', 'gs' | 'wx'}, #options{}}
| {'error', string()}.
+-type deep_string() :: string() | [deep_string()].
+
%%-----------------------------------------------------------------------
-spec start() -> dial_cl_parse_ret().
@@ -82,7 +82,7 @@ cl(["--get_warnings"|T]) ->
put(dialyzer_options_get_warnings, true),
cl(T);
cl(["-D"|_]) ->
- error("No defines specified after -D");
+ cl_error("No defines specified after -D");
cl(["-D"++Define|T]) ->
Def = re:split(Define, "=", [{return, list}]),
append_defines(Def),
@@ -92,7 +92,7 @@ cl(["-h"|_]) ->
cl(["--help"|_]) ->
help_message();
cl(["-I"]) ->
- error("no include directory specified after -I");
+ cl_error("no include directory specified after -I");
cl(["-I", Dir|T]) ->
append_include(Dir),
cl(T);
@@ -113,14 +113,14 @@ cl(["--com"++_|T]) ->
NewTail = command_line(T),
cl(NewTail);
cl(["--output"]) ->
- error("No outfile specified");
+ cl_error("No outfile specified");
cl(["-o"]) ->
- error("No outfile specified");
+ cl_error("No outfile specified");
cl(["--output",Output|T]) ->
put(dialyzer_output, Output),
cl(T);
cl(["--output_plt"]) ->
- error("No outfile specified for --output_plt");
+ cl_error("No outfile specified for --output_plt");
cl(["--output_plt",Output|T]) ->
put(dialyzer_output_plt, Output),
cl(T);
@@ -139,7 +139,7 @@ cl(["--fullpath"|T]) ->
cl(["-pa", Path|T]) ->
case code:add_patha(Path) of
true -> cl(T);
- {error, _} -> error("Bad directory for -pa: "++Path)
+ {error, _} -> cl_error("Bad directory for -pa: " ++ Path)
end;
cl(["--plt"]) ->
error("No plt specified for --plt");
@@ -174,14 +174,14 @@ cl(["--verbose"|T]) ->
put(dialyzer_options_report_mode, verbose),
cl(T);
cl(["-W"|_]) ->
- error("-W given without warning");
+ cl_error("-W given without warning");
cl(["-Whelp"|_]) ->
help_warnings();
cl(["-W"++Warn|T]) ->
append_var(dialyzer_warnings, [list_to_atom(Warn)]),
cl(T);
cl(["--dump_callgraph"]) ->
- error("No outfile specified for --dump_callgraph");
+ cl_error("No outfile specified for --dump_callgraph");
cl(["--dump_callgraph", File|T]) ->
put(dialyzer_callgraph_file, File),
cl(T);
@@ -197,7 +197,7 @@ cl([H|_] = L) ->
NewTail = command_line(L),
cl(NewTail);
false ->
- error("Unknown option: " ++ H)
+ cl_error("Unknown option: " ++ H)
end;
cl([]) ->
{RetTag, Opts} =
@@ -216,7 +216,7 @@ cl([]) ->
end
end,
case dialyzer_options:build(Opts) of
- {error, Msg} -> error(Msg);
+ {error, Msg} -> cl_error(Msg);
OptsRecord -> {RetTag, OptsRecord}
end.
@@ -232,7 +232,9 @@ command_line(T0) ->
end,
T.
-error(Str) ->
+-spec cl_error(deep_string()) -> no_return().
+
+cl_error(Str) ->
Msg = lists:flatten(Str),
throw({dialyzer_cl_parse_error, Msg}).
@@ -332,11 +334,15 @@ get_plts([], Acc) -> {lists:reverse(Acc), []}.
%%-----------------------------------------------------------------------
+-spec help_warnings() -> no_return().
+
help_warnings() ->
S = warning_options_msg(),
io:put_chars(S),
erlang:halt(?RET_NOTHING_SUSPICIOUS).
+-spec help_message() -> no_return().
+
help_message() ->
S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose]
[-pa dir]* [--plt plt] [--plts plt*] [-Ddefine]*
@@ -496,13 +502,13 @@ warning_options_msg() ->
Include warnings about behaviour callbacks which drift from the published
recommended interfaces.
-Wunderspecs ***
- Warn about underspecified functions
+ Warn about underspecified functions
(those whose -spec is strictly more allowing than the success typing).
The following options are also available but their use is not recommended:
(they are mostly for Dialyzer developers and internal debugging)
-Woverspecs ***
- Warn about overspecified functions
+ Warn about overspecified functions
(those whose -spec is strictly less allowing than the success typing).
-Wspecdiffs ***
Warn when the -spec is different than the success typing.
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 2ffbcd3e82..7137dbc036 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -2,7 +2,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -657,7 +657,8 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
true -> opaque;
false -> structured
end,
- RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, RetMode), SigRange, RetMode),
+ RetWithoutContr = t_inf(SigRange, BifRet, RetMode),
+ RetWithoutLocal = t_inf(ContrRet, RetWithoutContr, RetMode),
?debug("--------------------------------------------------------\n", []),
?debug("Fun: ~p\n", [Fun]),
?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]),
@@ -666,6 +667,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
[erl_types:t_to_string(t_product(NewArgsContract))]),
?debug("NewArgsBif: ~s\n", [erl_types:t_to_string(t_product(NewArgsBif))]),
?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]),
+ ?debug("RetWithoutContr: ~s\n",[erl_types:t_to_string(RetWithoutContr)]),
?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]),
?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]),
?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(TmpArgTypes))]),
@@ -700,22 +702,39 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
State2 =
case FailedConj andalso not (IsFailBif orelse IsFailSig) of
true ->
- FailedSig = any_none(NewArgsSig),
- FailedContract = any_none([CRange(TmpArgsContract)|NewArgsContract]),
- FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]),
- InfSig = t_inf(t_fun(SigArgs, SigRange),
- t_fun(BifArgs, BifRange(BifArgs))),
- FailReason = apply_fail_reason(FailedSig, FailedBif, FailedContract),
- Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig,
- Contr, CArgs, State1, FailReason),
- WarnType = case Msg of
- {call, _} -> ?WARN_FAILING_CALL;
- {apply, _} -> ?WARN_FAILING_CALL;
- {call_with_opaque, _} -> ?WARN_OPAQUE;
- {call_without_opaque, _} -> ?WARN_OPAQUE;
- {opaque_type_test, _} -> ?WARN_OPAQUE
- end,
- state__add_warning(State1, WarnType, Tree, Msg);
+ case t_is_none(RetWithoutLocal) andalso
+ not t_is_none(RetWithoutContr) andalso
+ not any_none(NewArgTypes) of
+ true ->
+ {value, C1} = Contr,
+ Contract = dialyzer_contracts:contract_to_string(C1),
+ {M1, F1, A1} = state__lookup_name(Fun, State),
+ ArgStrings = format_args(Args, ArgTypes, State),
+ CRet = erl_types:t_to_string(RetWithoutContr),
+ %% This Msg will be post_processed by dialyzer_succ_typings
+ Msg =
+ {contract_range, [Contract, M1, F1, A1, ArgStrings, CRet]},
+ state__add_warning(State1, ?WARN_CONTRACT_RANGE, Tree, Msg);
+ false ->
+ FailedSig = any_none(NewArgsSig),
+ FailedContract =
+ any_none([CRange(TmpArgsContract)|NewArgsContract]),
+ FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]),
+ InfSig = t_inf(t_fun(SigArgs, SigRange),
+ t_fun(BifArgs, BifRange(BifArgs))),
+ FailReason =
+ apply_fail_reason(FailedSig, FailedBif, FailedContract),
+ Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig,
+ Contr, CArgs, State1, FailReason),
+ WarnType = case Msg of
+ {call, _} -> ?WARN_FAILING_CALL;
+ {apply, _} -> ?WARN_FAILING_CALL;
+ {call_with_opaque, _} -> ?WARN_OPAQUE;
+ {call_without_opaque, _} -> ?WARN_OPAQUE;
+ {opaque_type_test, _} -> ?WARN_OPAQUE
+ end,
+ state__add_warning(State1, WarnType, Tree, Msg)
+ end;
false -> State1
end,
State3 =
@@ -1350,7 +1369,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
bind_pat_vars(Pats, ArgTypes, [], Map1, State1)
end,
case BindRes of
- {error, BindOrOpaque, NewPats, Type, OpaqueTerm} ->
+ {error, ErrorType, NewPats, Type, OpaqueTerm} ->
?debug("Failed binding pattern: ~s\nto ~s\n",
[cerl_prettypr:format(C), format_type(ArgType0, State1)]),
case state__warning_mode(State1) of
@@ -1358,8 +1377,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
{State1, Map, t_none(), ArgType0};
true ->
PatString =
- case BindOrOpaque of
+ case ErrorType of
bind -> format_patterns(Pats);
+ record -> format_patterns(Pats);
opaque -> format_patterns(NewPats)
end,
{Msg, Force} =
@@ -1399,13 +1419,15 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
false ->
true
end,
- PatTypes = case BindOrOpaque of
+ PatTypes = case ErrorType of
bind -> [PatString, format_type(ArgType0, State1)];
+ record -> [PatString, format_type(Type, State1)];
opaque -> [PatString, format_type(Type, State1),
format_type(OpaqueTerm, State1)]
- end,
- FailedMsg = case BindOrOpaque of
+ end,
+ FailedMsg = case ErrorType of
bind -> {pattern_match, PatTypes};
+ record -> {record_match, PatTypes};
opaque -> {opaque_match, PatTypes}
end,
{FailedMsg, Force0}
@@ -1413,6 +1435,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
WarnType = case Msg of
{opaque_match, _} -> ?WARN_OPAQUE;
{pattern_match, _} -> ?WARN_MATCHING;
+ {record_match, _} -> ?WARN_MATCHING;
{pattern_match_cov, _} -> ?WARN_MATCHING
end,
{state__add_warning(State1, WarnType, C, Msg, Force),
@@ -1506,14 +1529,18 @@ bind_pat_vars(Pats, Types, Acc, Map, State) ->
try
bind_pat_vars(Pats, Types, Acc, Map, State, false)
catch
- throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType}
+ throw:Error ->
+ %% Error = {error, bind | opaque | record, ErrorPats, ErrorType}
+ Error
end.
bind_pat_vars_reverse(Pats, Types, Acc, Map, State) ->
try
bind_pat_vars(Pats, Types, Acc, Map, State, true)
catch
- throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType}
+ throw:Error ->
+ %% Error = {error, bind | opaque | record, ErrorPats, ErrorType}
+ Error
end.
bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
@@ -1568,18 +1595,21 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
end;
tuple ->
Es = cerl:tuple_es(Pat),
- Prototype =
+ {TypedRecord, Prototype} =
case Es of
- [] -> t_tuple([]);
+ [] -> {false, t_tuple([])};
[Tag|Left] ->
case cerl:is_c_atom(Tag) of
true ->
TagAtom = cerl:atom_val(Tag),
case state__lookup_record(TagAtom, length(Left), State) of
- error -> t_tuple(length(Es));
- {ok, Record} -> Record
+ error -> {false, t_tuple(length(Es))};
+ {ok, Record} ->
+ [_Head|AnyTail] = [t_any() || _ <- Es],
+ UntypedRecord = t_tuple([t_atom(TagAtom)|AnyTail]),
+ {not erl_types:t_is_equal(Record, UntypedRecord), Record}
end;
- false -> t_tuple(length(Es))
+ false -> {false, t_tuple(length(Es))}
end
end,
Tuple = t_inf(Prototype, Type),
@@ -1604,7 +1634,11 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
bind_error([Pat], Tuple, Opaque, opaque);
false ->
case [M || {M, _} <- Results, M =/= error] of
- [] -> bind_error([Pat], Tuple, t_none(), bind);
+ [] ->
+ case TypedRecord of
+ true -> bind_error([Pat], Tuple, Prototype, record);
+ false -> bind_error([Pat], Tuple, t_none(), bind)
+ end;
Maps ->
Map1 = join_maps(Maps, Map),
TupleType = t_sup([t_tuple(EsTypes)
@@ -2087,7 +2121,10 @@ handle_guard_eq(Guard, Map, Env, Eval, State) ->
true ->
if
Eval =:= pos -> {Map, t_atom(true)};
- Eval =:= neg -> throw({fail, none});
+ Eval =:= neg ->
+ ArgTypes = [t_from_term(cerl:concrete(Arg1)),
+ t_from_term(cerl:concrete(Arg2))],
+ signal_guard_fail(Eval, Guard, ArgTypes, State);
Eval =:= dont_know -> {Map, t_atom(true)}
end;
false ->
@@ -2142,7 +2179,10 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) ->
{literal, literal} ->
case cerl:concrete(Arg1) =:= cerl:concrete(Arg2) of
true ->
- if Eval =:= neg -> throw({fail, none});
+ if Eval =:= neg ->
+ ArgTypes = [t_from_term(cerl:concrete(Arg1)),
+ t_from_term(cerl:concrete(Arg2))],
+ signal_guard_fail(Eval, Guard, ArgTypes, State);
Eval =:= pos -> {Map, t_atom(true)};
Eval =:= dont_know -> {Map, t_atom(true)}
end;
@@ -2217,7 +2257,7 @@ bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
true -> {Map1, t_atom(true)};
false ->
{_, Type0} = bind_guard(Arg2, Map, Env, Eval, State),
- signal_guard_fail(Eval, Guard, [Type0, t_atom(true)], State)
+ signal_guard_fail(Eval, Guard, [Type0, t_atom(false)], State)
end;
Term ->
LitType = t_from_term(Term),
@@ -2238,11 +2278,11 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
pos ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, Eval, State),
case t_is_atom(true, Type1) of
- false -> throw({fail, none});
+ false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State);
true ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, Eval, State),
case t_is_atom(true, Type2) of
- false -> throw({fail, none});
+ false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State);
true -> {Map2, t_atom(true)}
end
end;
@@ -2257,7 +2297,7 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
end,
case t_is_atom(false, Type1) orelse t_is_atom(false, Type2) of
true -> {join_maps([Map1, Map2], Map), t_atom(false)};
- false -> throw({fail, none})
+ false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State)
end;
dont_know ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State),
@@ -2297,16 +2337,16 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
orelse
(t_is_atom(true, Bool2) andalso t_is_boolean(Bool1))) of
true -> {join_maps([Map1, Map2], Map), t_atom(true)};
- false -> throw({fail, none})
+ false -> signal_guard_fail(Eval, Guard, [Bool1, Bool2], State)
end;
neg ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, neg, State),
case t_is_atom(false, Type1) of
- false -> throw({fail, none});
+ false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State);
true ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, neg, State),
case t_is_atom(false, Type2) of
- false -> throw({fail, none});
+ false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State);
true -> {Map2, t_atom(false)}
end
end;
@@ -2337,13 +2377,17 @@ handle_guard_not(Guard, Map, Env, Eval, State) ->
{Map1, Type} = bind_guard(Arg, Map, Env, pos, State),
case t_is_atom(true, Type) of
true -> {Map1, t_atom(false)};
- false -> throw({fail, none})
+ false ->
+ {_, Type0} = bind_guard(Arg, Map, Env, Eval, State),
+ signal_guard_fail(Eval, Guard, [Type0], State)
end;
pos ->
{Map1, Type} = bind_guard(Arg, Map, Env, neg, State),
case t_is_atom(false, Type) of
true -> {Map1, t_atom(true)};
- false -> throw({fail, none})
+ false ->
+ {_, Type0} = bind_guard(Arg, Map, Env, Eval, State),
+ signal_guard_fail(Eval, Guard, [Type0], State)
end;
dont_know ->
{Map1, Type} = bind_guard(Arg, Map, Env, dont_know, State),
@@ -2382,9 +2426,15 @@ signal_guard_fail(Eval, Guard, ArgTypes, State) ->
true ->
[ArgType1, ArgType2] = ArgTypes,
[Arg1, Arg2] = Args,
- {guard_fail, [format_args_1([Arg1], [ArgType1], State),
- atom_to_list(F),
- format_args_1([Arg2], [ArgType2], State)]};
+ Kind =
+ case Eval of
+ neg -> neg_guard_fail;
+ pos -> guard_fail;
+ dont_know -> guard_fail
+ end,
+ {Kind, [format_args_1([Arg1], [ArgType1], State),
+ atom_to_list(F),
+ format_args_1([Arg2], [ArgType2], State)]};
false ->
mk_guard_msg(Eval, F, Args, ArgTypes, State)
end,
@@ -2767,8 +2817,6 @@ state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) ->
FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques),
Work = init_work([get_label(Tree)]),
Env = dict:store(top, map__new(), dict:new()),
- Opaques = erl_types:module_builtin_opaques(Module) ++
- erl_types:t_opaque_from_records(Records),
#state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques,
plt = Plt, races = dialyzer_races:new(), records = Records,
warning_mode = false, warnings = [], work = Work, tree_map = TreeMap,
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index 29e164628a..b2a67de8bd 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -47,6 +47,7 @@ build(Opts) ->
?WARN_FAILING_CALL,
?WARN_BIN_CONSTRUCTION,
?WARN_CALLGRAPH,
+ ?WARN_CONTRACT_RANGE,
?WARN_CONTRACT_TYPES,
?WARN_CONTRACT_SYNTAX],
DefaultWarns1 = ordsets:from_list(DefaultWarns),
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
index a7ba270c41..807c9af44f 100644
--- a/lib/dialyzer/src/dialyzer_plt.erl
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -28,8 +28,6 @@
%%%-------------------------------------------------------------------
-module(dialyzer_plt).
-%% Avoid warning for local function error/1 clashing with autoimported BIF.
--compile({no_auto_import,[error/1]}).
-export([check_plt/3,
compute_md5_from_files/1,
contains_mfa/2,
@@ -56,8 +54,7 @@
plt_and_info_from_file/1,
get_specs/1,
get_specs/4,
- to_file/4
- ]).
+ to_file/4]).
%% Debug utilities
-export([pp_non_returning/0, pp_mod/1]).
@@ -68,6 +65,8 @@
-type mod_deps() :: dict().
+-type deep_string() :: string() | [deep_string()].
+
%% The following are used for searching the PLT when using the GUI
%% (e.g. in show or search PLT contents). The user might be searching
%% with a partial specification, in which case the missing items
@@ -203,8 +202,8 @@ get_default_plt() ->
false ->
case os:getenv("HOME") of
false ->
- error("The HOME environment variable needs to be set " ++
- "so that Dialyzer knows where to find the default PLT");
+ plt_error("The HOME environment variable needs to be set " ++
+ "so that Dialyzer knows where to find the default PLT");
HomeDir -> filename:join(HomeDir, ".dialyzer_plt")
end;
UserSpecPlt -> UserSpecPlt
@@ -226,7 +225,7 @@ from_file(FileName, ReturnInfo) ->
case check_version(Rec) of
error ->
Msg = io_lib:format("Old PLT file ~s\n", [FileName]),
- error(Msg);
+ plt_error(Msg);
ok ->
Plt = #plt{info = Rec#file_plt.info,
types = Rec#file_plt.types,
@@ -241,8 +240,9 @@ from_file(FileName, ReturnInfo) ->
end
end;
{error, Reason} ->
- error(io_lib:format("Could not read PLT file ~s: ~p\n",
- [FileName, Reason]))
+ Msg = io_lib:format("Could not read PLT file ~s: ~p\n",
+ [FileName, Reason]),
+ plt_error(Msg)
end.
-type err_rsn() :: 'not_valid' | 'no_such_file' | 'read_error'.
@@ -518,7 +518,9 @@ expand_args([ArgType|Left]) ->
end ++
","|expand_args(Left)].
-error(Msg) ->
+-spec plt_error(deep_string()) -> no_return().
+
+plt_error(Msg) ->
throw({dialyzer_error, lists:flatten(Msg)}).
%%---------------------------------------------------------------------------
diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl
index 8bfc66fc39..daf68d24f0 100644
--- a/lib/dialyzer/src/dialyzer_succ_typings.erl
+++ b/lib/dialyzer/src/dialyzer_succ_typings.erl
@@ -131,8 +131,9 @@ get_warnings_from_modules([M|Ms], State, DocPlt,
%% Check if there are contracts for functions that do not exist
Warnings1 =
dialyzer_contracts:contracts_without_fun(Contracts, AllFuns, Callgraph),
- {Warnings2, FunTypes, RaceCode, PublicTables, NamedTables} =
+ {RawWarnings2, FunTypes, RaceCode, PublicTables, NamedTables} =
dialyzer_dataflow:get_warnings(ModCode, Plt, Callgraph, Records, NoWarnUnused),
+ {NewAcc, Warnings2} = postprocess_dataflow_warns(RawWarnings2, State, Acc),
Attrs = cerl:module_attrs(ModCode),
Warnings3 = if BehavioursChk ->
dialyzer_behaviours:check_callbacks(M, Attrs,
@@ -145,10 +146,31 @@ get_warnings_from_modules([M|Ms], State, DocPlt,
NamedTables),
State1 = st__renew_state_calls(NewCallgraph, State),
get_warnings_from_modules(Ms, State1, NewDocPlt, BehavioursChk,
- [Warnings1, Warnings2, Warnings3|Acc]);
+ [Warnings1, Warnings2, Warnings3|NewAcc]);
get_warnings_from_modules([], #st{plt = Plt}, DocPlt, _, Acc) ->
{lists:flatten(Acc), Plt, DocPlt}.
+postprocess_dataflow_warns(RawWarnings, State, WarnAcc) ->
+ postprocess_dataflow_warns(RawWarnings, State, WarnAcc, []).
+
+postprocess_dataflow_warns([], _State, WAcc, Acc) ->
+ {WAcc, lists:reverse(Acc)};
+postprocess_dataflow_warns([{?WARN_CONTRACT_RANGE, {File, CallL}, Msg}|Rest],
+ #st{codeserver = Codeserver} = State, WAcc, Acc) ->
+ {contract_range, [Contract, M, F, A, ArgStrings, CRet]} = Msg,
+ {ok, {{File, _ContrL} = FileLine, _C}} =
+ dialyzer_codeserver:lookup_mfa_contract({M,F,A}, Codeserver),
+ NewMsg =
+ {contract_range, [Contract, M, F, ArgStrings, CallL, CRet]},
+ W = {?WARN_CONTRACT_RANGE, FileLine, NewMsg},
+ Filter =
+ fun({?WARN_CONTRACT_TYPES, FL, _}) when FL =:= FileLine -> false;
+ (_) -> true
+ end,
+ postprocess_dataflow_warns(Rest, State, lists:filter(Filter, WAcc), [W|Acc]);
+postprocess_dataflow_warns([W|Rest], State, Wacc, Acc) ->
+ postprocess_dataflow_warns(Rest, State, Wacc, [W|Acc]).
+
refine_succ_typings(ModulePostorder, State) ->
?debug("Module postorder: ~p\n", [ModulePostorder]),
refine_succ_typings(ModulePostorder, State, []).
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index 248fdf6835..12f8dec67e 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -214,14 +214,13 @@ get_record_and_type_info([], _Module, Records, RecDict) ->
?debug(_NewRecDict),
Ok;
{error, Name, Error} ->
- {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n",
- [Name, Error]))}
+ {error, flat_format(" Error while parsing #~w{}: ~s\n", [Name, Error])}
end.
add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) ->
case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of
true ->
- throw({error, io_lib:format("Type already defined: ~w\n", [Name])});
+ throw({error, flat_format("Type ~s already defined\n", [Name])});
false ->
ArgTypes = [erl_types:t_from_form(X) || X <- ArgForms],
case lists:all(fun erl_types:t_is_var/1, ArgTypes) of
@@ -229,8 +228,8 @@ add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) ->
ArgNames = [erl_types:t_var_name(X) || X <- ArgTypes],
dict:store({TypeOrOpaque, Name}, {Module, TypeForm, ArgNames}, RecDict);
false ->
- throw({error, io_lib:format("Type declaration for ~w does not "
- "have variables as parameters", [Name])})
+ throw({error, flat_format("Type declaration for ~w does not "
+ "have variables as parameters", [Name])})
end
end.
@@ -338,14 +337,14 @@ get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left],
get_spec_info(Left, NewSpecDict, RecordsDict, ModName, File);
{ok, {{OtherFile, L},_C}} ->
{Mod, Fun, Arity} = MFA,
- Msg = io_lib:format(" Contract for function ~w:~w/~w "
- "already defined in ~s:~w\n",
- [Mod, Fun, Arity, OtherFile, L]),
+ Msg = flat_format(" Contract for function ~w:~w/~w "
+ "already defined in ~s:~w\n",
+ [Mod, Fun, Arity, OtherFile, L]),
throw({error, Msg})
catch
throw:{error, Error} ->
- {error, lists:flatten(io_lib:format(" Error while parsing contract "
- "in line ~w: ~s\n", [Ln, Error]))}
+ {error, flat_format(" Error while parsing contract in line ~w: ~s\n",
+ [Ln, Error])}
end;
get_spec_info([{attribute, _, file, {IncludeFile, _}}|Left],
SpecDict, RecordsDict, ModName, _File) ->
@@ -419,6 +418,9 @@ format_sig(Type, RecDict) ->
")" ++ RevSig = lists:reverse(Sig),
lists:reverse(RevSig).
+flat_format(Fmt, Lst) ->
+ lists:flatten(io_lib:format(Fmt, Lst)).
+
%%-------------------------------------------------------------------
%% Author : Per Gustafsson <[email protected]>
%% Description : Provides better printing of binaries.
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index b2902e95ed..53b6f8c553 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 2.4.0
+DIALYZER_VSN = 2.4.2
diff --git a/lib/edoc/doc/overview.edoc b/lib/edoc/doc/overview.edoc
index 9b25c17b1f..bd603b7a13 100644
--- a/lib/edoc/doc/overview.edoc
+++ b/lib/edoc/doc/overview.edoc
@@ -205,8 +205,12 @@ The following tags can be used anywhere within a module:
the text. See {@section Type specifications} for syntax and
examples.
All data type descriptions are placed in a separate section of
- the documentation, regardless of where the tags occur.</dd>
+ the documentation, regardless of where the tags occur.
+ Instead of specifying the complete type alias in an EDoc
+ documentation comment, type definitions from the actual
+ Erlang code can be re-used for documentation.
+ See {@section Type specifications} for examples.</dd>
</dl>
@@ -405,7 +409,12 @@ The following tags can be used before a function definition:
included in the specification, it must match the name in the
actual code. When parameter names are not given in the
specification, suitable names will be taken from the source
- code if possible, and otherwise synthesized.</dd>
+ code if possible, and otherwise synthesized.
+
+ Instead of specifying the complete function type in an EDoc
+ documentation comment, specifications from the actual
+ Erlang code can be re-used for documentation.
+ See {@section Type specifications} for examples.</dd>
<dt><a name="ftag-throws">`@throws'</a></dt>
<dd>Specifies which types of terms may be thrown by the
@@ -763,6 +772,17 @@ following escape sequences may be used: <dl>
=== Function specifications ===
+<note>Although the syntax described in the following can still be used
+for specifying functions we recommend that Erlang specifications as
+described in <seealso marker="doc/reference_manual:typespec"> Types
+and Function Specification</seealso> should be added to the source
+code instead. This way the analyses of <seealso
+marker="dialyzer:dialyzer">Dialyzer</seealso>'s can be utilized in the
+process of keeping the documentation consistent and up-to-date.
+Erlang specifications will be used unless there is also a function
+specification (a `@spec' tag followed by a type) with the same name.
+</note>
+
The following grammar describes the form of the specifications following
a `@spec' tag. A '`?'' suffix implies that the element is optional.
Function types have higher precedence than union types; e.g., "`(atom())
@@ -818,16 +838,51 @@ not as `(atom()) -> (atom() | integer())'.
<br/>| Atom
<br/>| Integer
<br/>| Float
+ <br/>| Integer ".." Integer
<br/>| FunType
+ <br/>| "fun(" FunType ")"
+ <br/>| "fun(...)"
<br/>| "{" UnionTypes? "}"
+ <br/>| "#" Atom "{" Fields? "}"
<br/>| "[" "]"
<br/>| "[" UnionType "]"
+ <br/>| "[" UnionType "," "..." "]"
<br/>| "(" UnionType ")"
+ <br/>| BinType
<br/>| TypeName "(" UnionTypes? ")"
<br/>| ModuleName ":" TypeName "(" UnionTypes? ")"
<br/>| "//" AppName "/" ModuleName ":" TypeName "(" UnionTypes? ")"</code></td>
</tr>
<tr>
+ <td><code>Fields</code></td>
+ <td>::=</td>
+ <td><code>Field
+ <br/>| Fields "," Fields</code></td>
+ </tr>
+ <tr>
+ <td><code>Field</code></td>
+ <td>::=</td>
+ <td><code>Atom "=" UnionList</code></td>
+ </tr>
+ <tr>
+ <td><code>BinType</code></td>
+ <td>::=</td>
+ <td><code>"&lt;&lt;&gt;&gt;"
+ <br/>| "&lt;&lt;" BaseType "&gt;&gt;"
+ <br/>| "&lt;&lt;" UnitType "&gt;&gt;"
+ <br/>| "&lt;&lt;" BaseType "," UnitType "&gt;&gt;"</code></td>
+ </tr>
+ <tr>
+ <td><code>BaseType</code></td>
+ <td>::=</td>
+ <td><code>"_" ":" Integer</code></td>
+ </tr>
+ <tr>
+ <td><code>UnitType</code></td>
+ <td>::=</td>
+ <td><code>"_" ":" "_" "*" Integer</code></td>
+ </tr>
+ <tr>
<td><code>TypeVariable</code></td>
<td>::=</td>
<td><code>Variable</code></td>
@@ -858,7 +913,7 @@ not as `(atom()) -> (atom() | integer())'.
<tr>
<td><code>Def</code></td>
<td>::=</td>
- <td><code>TypeVariable "=" UnionType
+ <td><code>TypeVariable "=" UnionList
<br/>| TypeName "(" TypeVariables? ")" "=" UnionType</code></td>
</tr>
<tr>
@@ -873,6 +928,9 @@ not as `(atom()) -> (atom() | integer())'.
Examples:
```
+ -spec my_function(X :: integer()) -> integer().
+ %% @doc Creates ...'''
+```
%% @spec my_function(X::integer()) -> integer()'''
```
%% @spec (X::integer()) -> integer()'''
@@ -895,6 +953,8 @@ Examples:
```
%% @spec close(graphics:window()) -> ok'''
+The first example shows the recommended way of specifying functions.
+
In the above examples, `X', `A', `B',
and `File' are parameter names, used for referring to the
parameters from the documentation text. The <em>type variables</em>
@@ -930,6 +990,13 @@ contain any annotations at all.
=== Type definitions ===
+<note>Although the syntax described in the following can still be used
+for specifying types we recommend that Erlang types as described in
+<seealso marker="doc/reference_manual:typespec"> Types and Function
+Specification</seealso> should be added to the source code instead.
+Erlang types will be used unless there is a type alias with the same
+name.</note>
+
The following grammar (see above for auxiliary definitions) describes
the form of the definitions that may follow a `@type' tag:
@@ -939,7 +1006,7 @@ the form of the definitions that may follow a `@type' tag:
<td><code>Typedef</code></td>
<td>::=</td>
<td><code>TypeName "(" TypeVariables? ")" DefList?
- <br/>| TypeName "(" TypeVariables? ")" "=" UnionType DefList?</code></td>
+ <br/>| TypeName "(" TypeVariables? ")" "=" UnionList DefList?</code></td>
</tr>
</tbody>
</table>
@@ -947,6 +1014,11 @@ the form of the definitions that may follow a `@type' tag:
(For a truly abstract data type, no equivalence is specified.) The main
definition may be followed by additional local definitions. Examples:
```
+ -type my_list(X) :: [X]. %% A special kind of lists ...'''
+```
+ -opaque another_list(X) :: [X].
+ %% another_list() is a kind of list...'''
+```
%% @type myList(X). A special kind of lists ...'''
```
%% @type filename() = string(). Atoms not allowed!'''
@@ -955,6 +1027,7 @@ definition may be followed by additional local definitions. Examples:
%% A = term().
%% A kind of wrapper type thingy.'''
+The first two examples show the recommended way of specifying types.
=== Pre-defined data types ===
@@ -962,24 +1035,42 @@ The following data types are predefined by EDoc, and may not be
redefined:
```
any()
+ arity()
atom()
binary()
- bool()
+ bitstring()
+ bool() (allowed, but use boolean() instead)
+ boolean()
+ byte()
char()
cons()
deep_string()
float()
function()
integer()
+ iodata()
+ iolist()
list()
+ maybe_improper_list()
+ mfa()
+ module()
nil()
+ neg_integer()
+ node()
+ non_neg_integer()
+ nonempty_improper_list()
+ nonempty_list()
+ nonempty_maybe_improper_list()
+ nonempty_string()
none()
number()
pid()
port()
+ pos_integer()
reference()
string()
term()
+ timeout()
tuple()
'''
Details:
@@ -991,7 +1082,7 @@ Details:
`integer()', `pid()', `port()'
and `reference()' are primitive data types of
the Erlang programming language.</li>
- <li>`bool()' is the subset of `atom()' consisting
+ <li>`boolean()' is the subset of `atom()' consisting
of the atoms `true' and `false'.</li>
<li>`char()' is a subset of
`integer()' representing character codes.</li>
diff --git a/lib/edoc/doc/src/Makefile b/lib/edoc/doc/src/Makefile
index 748691d173..5ee0096f0f 100644
--- a/lib/edoc/doc/src/Makefile
+++ b/lib/edoc/doc/src/Makefile
@@ -105,7 +105,7 @@ man: $(MAN3_FILES)
$(XML_REF3_FILES):
escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(EDOC_VSN) -i $(ERL_TOP)/lib/edoc/include $(SRC_DIR)/$(@:%.xml=%.erl)
-$(XML_CHAPTER_FILES):
+$(XML_CHAPTER_FILES): ../overview.edoc
escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(EDOC_VSN) -chapter ../overview.edoc
gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
diff --git a/lib/edoc/doc/src/ref_man.xml b/lib/edoc/doc/src/ref_man.xml
index 619fbaa7ca..a9af8740b9 100644
--- a/lib/edoc/doc/src/ref_man.xml
+++ b/lib/edoc/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2006</year><year>2009</year>
+ <year>2006</year><year>2011</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/edoc/src/Makefile b/lib/edoc/src/Makefile
index ca95c4cdad..9c5a9d30d1 100644
--- a/lib/edoc/src/Makefile
+++ b/lib/edoc/src/Makefile
@@ -29,7 +29,8 @@ SOURCES= \
edoc.erl edoc_data.erl edoc_doclet.erl edoc_extract.erl \
edoc_layout.erl edoc_lib.erl edoc_macros.erl edoc_parser.erl \
edoc_refs.erl edoc_report.erl edoc_run.erl edoc_scanner.erl \
- edoc_tags.erl edoc_types.erl edoc_wiki.erl otpsgml_layout.erl
+ edoc_specs.erl edoc_tags.erl edoc_types.erl edoc_wiki.erl \
+ otpsgml_layout.erl
OBJECTS=$(SOURCES:%.erl=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
diff --git a/lib/edoc/src/edoc.app.src b/lib/edoc/src/edoc.app.src
index 2177533441..0c8d5b85f8 100644
--- a/lib/edoc/src/edoc.app.src
+++ b/lib/edoc/src/edoc.app.src
@@ -15,6 +15,7 @@
edoc_report,
edoc_run,
edoc_scanner,
+ edoc_specs,
edoc_tags,
edoc_types,
edoc_wiki,
diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl
index 75b3bb451a..360f2dbc9e 100644
--- a/lib/edoc/src/edoc.erl
+++ b/lib/edoc/src/edoc.erl
@@ -258,6 +258,7 @@ opt_defaults() ->
opt_negations() ->
[{no_preprocess, preprocess},
{no_subpackages, subpackages},
+ {no_report_missing_types, report_missing_types},
{no_packages, packages}].
%% @spec run(Packages::[package()],
@@ -310,13 +311,13 @@ opt_negations() ->
%% <dd>Specifies the suffix used for output files. The default value is
%% `".html"'. Note that this also affects generated references.
%% </dd>
-%% <dt>{@type {new, bool()@}}
+%% <dt>{@type {new, boolean()@}}
%% </dt>
%% <dd>If the value is `true', any existing `edoc-info' file in the
%% target directory will be ignored and overwritten. The default
%% value is `false'.
%% </dd>
-%% <dt>{@type {packages, bool()@}}
+%% <dt>{@type {packages, boolean()@}}
%% </dt>
%% <dd>If the value is `true', it it assumed that packages (module
%% namespaces) are being used, and that the source code directory
@@ -342,7 +343,7 @@ opt_negations() ->
%% <dd>Specifies the expected suffix of input files. The default
%% value is `".erl"'.
%% </dd>
-%% <dt>{@type {subpackages, bool()@}}
+%% <dt>{@type {subpackages, boolean()@}}
%% </dt>
%% <dd>If the value is `true', all subpackages of specified packages
%% will also be included in the documentation. The default value is
@@ -578,6 +579,12 @@ layout(Doc, Opts) ->
%% @spec (File) -> [comment()]
+%% @type comment() = {Line, Column, Indentation, Text}
+%% where
+%% Line = integer(),
+%% Column = integer(),
+%% Indentation = integer(),
+%% Text = [string()]
%% @equiv read_comments(File, [])
read_comments(File) ->
@@ -585,12 +592,6 @@ read_comments(File) ->
%% @spec read_comments(File::filename(), Options::proplist()) ->
%% [comment()]
-%% where
-%% comment() = {Line, Column, Indentation, Text},
-%% Line = integer(),
-%% Column = integer(),
-%% Indentation = integer(),
-%% Text = [string()]
%%
%% @doc Extracts comments from an Erlang source code file. See the
%% module {@link //syntax_tools/erl_comment_scan} for details on the
@@ -616,7 +617,7 @@ read_source(Name) ->
%%
%% Options:
%% <dl>
-%% <dt>{@type {preprocess, bool()@}}
+%% <dt>{@type {preprocess, boolean()@}}
%% </dt>
%% <dd>If the value is `true', the source file will be read via the
%% Erlang preprocessor (`epp'). The default value is `false'.
@@ -642,6 +643,13 @@ read_source(Name) ->
%% macro definitions, used if the `preprocess' option is turned on.
%% The default value is the empty list.</dd>
%% </dl>
+%% <dt>{@type {report_missing_types, boolean()@}}
+%% </dt>
+%% <dd>If the value is `true', warnings are issued for missing types.
+%% The default value is `false'.
+%% `no_report_missing_types' is an alias for
+%% `{report_missing_types, false}'.
+%% </dd>
%%
%% @see get_doc/2
%% @see //syntax_tools/erl_syntax
@@ -724,17 +732,17 @@ get_doc(File) ->
%% <a href="overview-summary.html#Macro_expansion">Inline macro expansion</a>
%% for details.
%% </dd>
-%% <dt>{@type {hidden, bool()@}}
+%% <dt>{@type {hidden, boolean()@}}
%% </dt>
%% <dd>If the value is `true', documentation of hidden functions will
%% also be included. The default value is `false'.
%% </dd>
-%% <dt>{@type {private, bool()@}}
+%% <dt>{@type {private, boolean()@}}
%% </dt>
%% <dd>If the value is `true', documentation of private functions will
%% also be included. The default value is `false'.
%% </dd>
-%% <dt>{@type {todo, bool()@}}
+%% <dt>{@type {todo, boolean()@}}
%% </dt>
%% <dd>If the value is `true', To-Do notes written using `@todo' or
%% `@TODO' tags will be included in the documentation. The default
diff --git a/lib/edoc/src/edoc.hrl b/lib/edoc/src/edoc.hrl
index 71cc1a52b9..43657b3b8f 100644
--- a/lib/edoc/src/edoc.hrl
+++ b/lib/edoc/src/edoc.hrl
@@ -37,6 +37,7 @@
-define(SOURCE_DIR, "src").
-define(EBIN_DIR, "ebin").
-define(EDOC_DIR, "doc").
+-define(REPORT_MISSING_TYPE, false).
-include("edoc_doclet.hrl").
@@ -83,10 +84,11 @@
%% Module Entries (one per function, plus module header and footer)
-%% @type entry() = #entry{name = atom(),
-%% args = [string()],
+%% @type entry() = #entry{{atom(), integer()} % function
+%% | name = atom(), % other
+%% args = [atom()],
%% line = integer(),
-%% export = bool(),
+%% export = boolean(),
%% data = term()}
-record(entry, {name, args = [], line = 0, export, data}).
@@ -95,6 +97,7 @@
%% @type tag() = #tag{name = atom(),
%% line = integer(),
+%% origin = comment | code,
%% data = term()}
--record(tag, {name, line = 0, data}).
+-record(tag, {name, line = 0, origin = comment, data}).
diff --git a/lib/edoc/src/edoc_data.erl b/lib/edoc/src/edoc_data.erl
index 124f8eb9a1..27f43dca5a 100644
--- a/lib/edoc/src/edoc_data.erl
+++ b/lib/edoc/src/edoc_data.erl
@@ -20,7 +20,7 @@
%% @copyright 2003 Richard Carlsson
%% @author Richard Carlsson <[email protected]>
%% @see edoc
-%% @end
+%% @end
%% =====================================================================
%% @doc Building the EDoc external data structure. See the file
@@ -30,9 +30,10 @@
-export([module/4, package/4, overview/4, type/2]).
+-export([hidden_filter/2, get_all_tags/1]).
+
-include("edoc.hrl").
-%% TODO: report multiple definitions of the same type in the same module.
%% TODO: check that variables in @equiv are found in the signature
%% TODO: copy types from target (if missing) when using @equiv
@@ -139,6 +140,15 @@ functions(Es, Env, Opts) ->
|| #entry{name = {_,_}=N, args = As, export = Export, data = Ts}
<- Es].
+hidden_filter(Es, Opts) ->
+ Private = proplists:get_bool(private, Opts),
+ Hidden = proplists:get_bool(hidden, Opts),
+ [E || E <- Es,
+ case E#entry.name of
+ {_, _} -> function_filter(E, Private, Hidden);
+ _ -> true
+ end].
+
function_filter(Es, Opts) ->
Private = proplists:get_bool(private, Opts),
Hidden = proplists:get_bool(hidden, Opts),
@@ -298,7 +308,7 @@ get_deprecated(Ts, F, A, Env) ->
case otp_internal:obsolete(M, F, A) of
{Tag, Text} when Tag =:= deprecated; Tag =:= removed ->
deprecated([Text]);
- {Tag, Repl, _Rel} when Tag =:= deprecated; Tag =:= removed ->
+ {Tag, Repl, _Rel} when Tag =:= deprecated; Tag =:= removed ->
deprecated(Repl, Env);
_ ->
[]
diff --git a/lib/edoc/src/edoc_doclet.erl b/lib/edoc/src/edoc_doclet.erl
index f1d876d593..30eef3e63a 100644
--- a/lib/edoc/src/edoc_doclet.erl
+++ b/lib/edoc/src/edoc_doclet.erl
@@ -76,7 +76,7 @@
%% <dd>Specifies the suffix used for output files. The default value is
%% `".html"'.
%% </dd>
-%% <dt>{@type {hidden, bool()@}}
+%% <dt>{@type {hidden, boolean()@}}
%% </dt>
%% <dd>If the value is `true', documentation of hidden modules and
%% functions will also be included. The default value is `false'.
@@ -86,7 +86,7 @@
%% <dd>Specifies the name of the overview-file. By default, this doclet
%% looks for a file `"overview.edoc"' in the target directory.
%% </dd>
-%% <dt>{@type {private, bool()@}}
+%% <dt>{@type {private, boolean()@}}
%% </dt>
%% <dd>If the value is `true', documentation of private modules and
%% functions will also be included. The default value is `false'.
diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl
index ea2755f7aa..5e28762c53 100644
--- a/lib/edoc/src/edoc_extract.erl
+++ b/lib/edoc/src/edoc_extract.erl
@@ -14,7 +14,7 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
+%% $Id: $
%%
%% @copyright 2001-2003 Richard Carlsson
%% @author Richard Carlsson <[email protected]>
@@ -34,10 +34,12 @@
%% %% @headerfile "edoc.hrl" (disabled until it can be made private)
-include("edoc.hrl").
-%% @type filename() = file:filename()
+%% @type filename() = file:filename().
+%% @type proplist() = proplists:property().
+%% @type syntaxTree() = erl_syntax:syntaxTree().
%% @spec source(File::filename(), Env::edoc_env(), Options::proplist())
-%% -> {ModuleName, edoc_module()}
+%% -> {ModuleName, edoc:edoc_module()}
%% ModuleName = atom()
%% proplist() = [term()]
%%
@@ -53,16 +55,11 @@ source(File, Env, Opts) ->
Comments = edoc:read_comments(File, Opts),
source(Forms, Comments, File, Env, Opts).
-%% @spec source(Forms, Comments::[comment()], File::filename(),
+%% @spec source(Forms, Comments::[edoc:comment()], File::filename(),
%% Env::edoc_env(), Options::proplist()) ->
-%% {ModuleName, edoc_module()}
+%% {ModuleName, edoc:edoc_module()}
%%
%% Forms = syntaxTree() | [syntaxTree()]
-%% comment() = {Line, Column, Indentation, Text}
-%% Line = integer()
-%% Column = integer()
-%% Indentation = integer()
-%% Text = [string()]
%% ModuleName = atom()
%%
%% @doc Like {@link source/4}, but first inserts the given comments in
@@ -80,15 +77,15 @@ source(Forms, Comments, File, Env, Opts) when is_list(Forms) ->
source(Forms1, Comments, File, Env, Opts);
source(Forms, Comments, File, Env, Opts) ->
Tree = erl_recomment:quick_recomment_forms(Forms, Comments),
- source(Tree, File, Env, Opts).
+ TypeDocs = find_type_docs(Forms, Comments),
+ source1(Tree, File, Env, Opts, TypeDocs).
%% @spec source(Forms, File::filename(), Env::edoc_env(),
%% Options::proplist()) ->
-%% {ModuleName, edoc_module()}
+%% {ModuleName, edoc:edoc_module()}
%%
%% Forms = syntaxTree() | [syntaxTree()]
%% ModuleName = atom()
-%% edoc_module() = edoc:edoc_module()
%% @type edoc_env() = edoc_lib:edoc_env()
%%
%% @doc Extracts EDoc documentation from commented source code syntax
@@ -116,6 +113,11 @@ source(Forms, Comments, File, Env, Opts) ->
source(Forms, File, Env, Opts) when is_list(Forms) ->
source(erl_syntax:form_list(Forms), File, Env, Opts);
source(Tree, File0, Env, Opts) ->
+ TypeDocs = find_type_docs(Tree, []),
+ source1(Tree, File0, Env, Opts, TypeDocs).
+
+%% Forms0 and Comments is used for extracting Erlang type documentation.
+source1(Tree, File0, Env, Opts, TypeDocs) ->
Forms = preprocess_forms(Tree),
File = edoc_lib:filename(File0),
Module = get_module_info(Tree, File),
@@ -126,11 +128,12 @@ source(Tree, File0, Env, Opts) ->
package = Package,
root = edoc_refs:relative_package_path('', Package)},
Env2 = add_macro_defs(module_macros(Env1), Opts, Env1),
- Entries1 = get_tags([Header, Footer | Entries], Env2, File),
- Data = edoc_data:module(Module, Entries1, Env2, Opts),
+ Entries1 = get_tags([Header, Footer | Entries], Env2, File, TypeDocs),
+ Entries2 = edoc_specs:add_data(Entries1, Opts, File, Module),
+ edoc_tags:check_types(Entries2, Opts, File),
+ Data = edoc_data:module(Module, Entries2, Env2, Opts),
{Name, Data}.
-
%% @spec header(File::filename(), Env::edoc_env(), Options::proplist())
%% -> {ok, Tags} | {error, Reason}
%% Tags = [term()]
@@ -148,7 +151,7 @@ header(File, Env, Opts) ->
Comments = edoc:read_comments(File),
header(Forms, Comments, File, Env, Opts).
-%% @spec header(Forms, Comments::[comment()], File::filename(),
+%% @spec header(Forms, Comments::[edoc:comment()], File::filename(),
%% Env::edoc_env(), Options::proplist()) ->
%% {ok, Tags} | {error, Reason}
%% Forms = syntaxTree() | [syntaxTree()]
@@ -196,7 +199,7 @@ header(Tree, File0, Env, _Opts) ->
%% kill all the information above it up to that point. Then we call
%% this the 'header' to make error reports make better sense.
{Header, Footer, Entries} = collect(Forms, Module),
- if Header#entry.data /= [] ->
+ if Header#entry.data /= {[],[],[]} ->
warning(File, "documentation before module declaration is ignored by @headerfile", []);
true -> ok
end,
@@ -215,7 +218,6 @@ add_macro_defs(Defs0, Opts, Env) ->
edoc_macros:check_defs(Defs),
Env#env{macros = Defs ++ Defs0 ++ Env#env.macros}.
-
%% @spec file(File::filename(), Context, Env::edoc_env(),
%% Options::proplist()) -> {ok, Tags} | {error, Reason}
%% Context = overview | package
@@ -276,7 +278,7 @@ text(Text, Context, Env, Opts, Where) ->
end.
-%% @spec (Forms::[syntaxTree()], File::filename()) -> moduleInfo()
+%% @spec (Forms::[syntaxTree()], File::filename()) -> module()
%% @doc Initialises a module-info record with data about the module
%% represented by the list of forms. Exports are guaranteed to exist in
%% the set of defined names.
@@ -351,6 +353,13 @@ preprocess_forms_2(F, Fs) ->
[F | preprocess_forms_1(Fs)];
text ->
[F | preprocess_forms_1(Fs)];
+ {attribute, {N, _}} ->
+ case edoc_specs:is_tag(N) of
+ true ->
+ [F | preprocess_forms_1(Fs)];
+ false ->
+ preprocess_forms_1(Fs)
+ end;
_ ->
preprocess_forms_1(Fs)
end.
@@ -362,42 +371,55 @@ preprocess_forms_2(F, Fs) ->
%% in the list.
collect(Fs, Mod) ->
- collect(Fs, [], [], undefined, Mod).
+ collect(Fs, [], [], [], [], undefined, Mod).
-collect([F | Fs], Cs, As, Header, Mod) ->
+collect([F | Fs], Cs, Ss, Ts, As, Header, Mod) ->
case erl_syntax_lib:analyze_form(F) of
comment ->
- collect(Fs, [F | Cs], As, Header, Mod);
+ collect(Fs, [F | Cs], Ss, Ts, As, Header, Mod);
{function, Name} ->
L = erl_syntax:get_pos(F),
Export = ordsets:is_element(Name, Mod#module.exports),
Args = parameters(erl_syntax:function_clauses(F)),
- collect(Fs, [], [#entry{name = Name, args = Args, line = L,
- export = Export,
- data = comment_text(Cs)} | As],
+ collect(Fs, [], [], [],
+ [#entry{name = Name, args = Args, line = L,
+ export = Export,
+ data = {comment_text(Cs),Ss,Ts}} | As],
Header, Mod);
{rule, Name} ->
L = erl_syntax:get_pos(F),
Export = ordsets:is_element(Name, Mod#module.exports),
Args = parameters(erl_syntax:rule_clauses(F)),
- collect(Fs, [], [#entry{name = Name, args = Args, line = L,
- export = Export,
- data = comment_text(Cs)} | As],
+ collect(Fs, [], [], [],
+ [#entry{name = Name, args = Args, line = L,
+ export = Export,
+ data = {comment_text(Cs),Ss,Ts}} | As],
Header, Mod);
{attribute, {module, _}} when Header =:= undefined ->
L = erl_syntax:get_pos(F),
- collect(Fs, [], As, #entry{name = module, line = L,
- data = comment_text(Cs)},
+ collect(Fs, [], [], [], As,
+ #entry{name = module, line = L,
+ data = {comment_text(Cs),Ss,Ts}},
Mod);
+ {attribute, {N, _}} ->
+ case edoc_specs:tag(N) of
+ spec ->
+ collect(Fs, Cs, [F | Ss], Ts, As, Header, Mod);
+ type ->
+ collect(Fs, Cs, Ss, [F | Ts], As, Header, Mod);
+ unknown ->
+ %% Drop current seen comments.
+ collect(Fs, [], [], [], As, Header, Mod)
+ end;
_ ->
%% Drop current seen comments.
- collect(Fs, [], As, Header, Mod)
+ collect(Fs, [], [], [], As, Header, Mod)
end;
-collect([], Cs, As, Header, _Mod) ->
- Footer = #entry{name = footer, data = comment_text(Cs)},
+collect([], Cs, Ss, Ts, As, Header, _Mod) ->
+ Footer = #entry{name = footer, data = {comment_text(Cs),Ss,Ts}},
As1 = lists:reverse(As),
if Header =:= undefined ->
- {#entry{name = module, data = []}, Footer, As1};
+ {#entry{name = module, data = {[],[],[]}}, Footer, As1};
true ->
{Header, Footer, As1}
end.
@@ -475,7 +497,7 @@ select_names([Ns | Ls], As, S) ->
select_names([], As, _) ->
lists:reverse(As).
-select_name([A | Ns], S) ->
+select_name([A | Ns], S) ->
case sets:is_element(A, S) of
true ->
select_name(Ns, S);
@@ -522,6 +544,9 @@ capitalize(Cs) -> Cs.
-record(tags, {names,single,module,function,footer}).
get_tags(Es, Env, File) ->
+ get_tags(Es, Env, File, dict:new()).
+
+get_tags(Es, Env, File, TypeDocs) ->
%% Cache this stuff for quick lookups.
Tags = #tags{names = sets:from_list(edoc_tags:tag_names()),
single = sets:from_list(edoc_tags:tags(single)),
@@ -529,17 +554,20 @@ get_tags(Es, Env, File) ->
footer = sets:from_list(edoc_tags:tags(footer)),
function = sets:from_list(edoc_tags:tags(function))},
How = dict:from_list(edoc_tags:tag_parsers()),
- get_tags(Es, Tags, Env, How, File).
+ get_tags(Es, Tags, Env, How, File, TypeDocs).
-get_tags([#entry{name = Name, data = Cs} = E | Es], Tags, Env,
- How, File) ->
+get_tags([#entry{name = Name, data = {Cs,Specs,Types}} = E | Es], Tags, Env,
+ How, File, TypeDocs) ->
Where = {File, Name},
Ts0 = scan_tags(Cs),
- Ts1 = check_tags(Ts0, Tags, Where),
- Ts2 = edoc_macros:expand_tags(Ts1, Env, Where),
- Ts = edoc_tags:parse_tags(Ts2, How, Env, Where),
- [E#entry{data = Ts} | get_tags(Es, Tags, Env, How, File)];
-get_tags([], _, _, _, _) ->
+ {Ts1,Specs1} = select_spec(Ts0, Where, Specs),
+ Ts2 = check_tags(Ts1, Tags, Where),
+ Ts3 = edoc_macros:expand_tags(Ts2, Env, Where),
+ Ts4 = edoc_tags:parse_tags(Ts3, How, Env, Where),
+ Ts = selected_specs(Specs1, Ts4),
+ ETypes = [edoc_specs:type(Type, TypeDocs) || Type <- Types],
+ [E#entry{data = Ts++ETypes} | get_tags(Es, Tags, Env, How, File, TypeDocs)];
+get_tags([], _, _, _, _, _) ->
[].
%% Scanning a list of separate comments for tags.
@@ -572,6 +600,22 @@ check_tags_1(Ts, Tags, Where) ->
Single = Tags#tags.single,
edoc_tags:check_tags(Ts, Allow, Single, Where).
+select_spec(Ts, {_, {_F, _A}}, Specs) ->
+ case edoc_tags:filter_tags(Ts, sets:from_list([spec])) of
+ [] ->
+ %% Just a dummy to get us through check_tags()
+ {[edoc_specs:dummy_spec(S) || S <- Specs] ++ Ts, Specs};
+ _ ->
+ {Ts,[]}
+ end;
+select_spec(Ts, _Where, _Specs) ->
+ {Ts,[]}.
+
+selected_specs([], Ts) ->
+ Ts;
+selected_specs([F], [_ | Ts]) ->
+ [edoc_specs:spec(F, _Clause=1) | Ts].
+
%% Macros for modules
module_macros(Env) ->
@@ -582,3 +626,25 @@ module_macros(Env) ->
file_macros(_Context, Env) ->
edoc_macros:std_macros(Env).
+
+%% @doc Extracts what will be documentation of Erlang types.
+%% Returns a dict of {Name, Doc} where Name is {TypeName, Arity}.
+%%
+%% The idea is to mimic how the @type tag works.
+%% Using @type:
+%% @type t() = t1(). Some docs of t/0;
+%% Further docs of t/0.
+%% The same thing using -type:
+%% -type t() :: t1(). % Some docs of t/0;
+%% Further docs of t/0.
+find_type_docs(Forms0, Comments) ->
+ Tree = erl_recomment:recomment_forms(Forms0, Comments),
+ Forms = preprocess_forms(Tree),
+ edoc_specs:docs(Forms, fun find_fun/2).
+
+find_fun(C0, Line) ->
+ C1 = comment_text(C0),
+ Text = lists:append([C#comment.text || C <- C1]),
+ Comm = #comment{line = Line, text = Text},
+ [Tag | _] = scan_tags([Comm]),
+ Tag.
diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl
index 6cc2f5cd9b..3ec87b7060 100644
--- a/lib/edoc/src/edoc_layout.erl
+++ b/lib/edoc/src/edoc_layout.erl
@@ -14,7 +14,7 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
+%% $Id: $
%%
%% @author Richard Carlsson <[email protected]>
%% @copyright 2001-2006 Richard Carlsson
@@ -49,7 +49,6 @@
-define(FUNCTIONS_TITLE, "Function Details").
-define(FUNCTIONS_LABEL, "functions").
-
%% @doc The layout function.
%%
%% Options to the standard layout:
@@ -59,13 +58,20 @@
%% <dd>Specifies the number of column pairs used for the function
%% index tables. The default value is 1.
%% </dd>
+%% <dt>{@type {pretty_printer, atom()@}}
+%% </dt>
+%% <dd>Specifies how types and specifications are pretty printed.
+%% If the value `erl_pp' is specified the Erlang pretty printer
+%% (the module `erl_pp') will be used. The default is to do
+%% no pretty printing which implies that lines can be very long.
+%% </dd>
%% <dt>{@type {stylesheet, string()@}}
%% </dt>
%% <dd>Specifies the URI used for referencing the stylesheet. The
%% default value is `"stylesheet.css"'. If an empty string is
%% specified, no stylesheet reference will be generated.
%% </dd>
-%% <dt>{@type {sort_functions, bool()@}}
+%% <dt>{@type {sort_functions, boolean()@}}
%% </dt>
%% <dd>If `true', the detailed function descriptions are listed by
%% name, otherwise they are listed in the order of occurrence in
@@ -96,14 +102,20 @@ module(Element, Options) ->
%% % stylesheet = string(),
%% % index_columns = integer()}
--record(opts, {root, stylesheet, index_columns, sort_functions}).
+-record(opts, {root,
+ stylesheet,
+ index_columns,
+ sort_functions,
+ pretty_printer}).
init_opts(Element, Options) ->
R = #opts{root = get_attrval(root, Element),
index_columns = proplists:get_value(index_columns,
Options, 1),
sort_functions = proplists:get_value(sort_functions,
- Options, true)
+ Options, true),
+ pretty_printer = proplists:get_value(pretty_printer,
+ Options, '')
},
case proplists:get_value(stylesheet, Options) of
undefined ->
@@ -112,7 +124,7 @@ init_opts(Element, Options) ->
"" ->
R; % don't use any stylesheet
S when is_list(S) ->
- R#opts{stylesheet = S};
+ R#opts{stylesheet = S};
_ ->
report("bad value for option `stylesheet'.", []),
exit(error)
@@ -192,10 +204,10 @@ layout_module(#xmlElement{name = module, content = Es}=E, Opts) ->
["Description"]}]}
| FullDesc]
end
- ++ types(lists:sort(Types))
+ ++ types(lists:sort(Types), Opts)
++ function_index(SortedFs, Opts#opts.index_columns)
- ++ if Opts#opts.sort_functions -> functions(SortedFs);
- true -> functions(Functions)
+ ++ if Opts#opts.sort_functions -> functions(SortedFs, Opts);
+ true -> functions(Functions, Opts)
end
++ [hr, ?NL]
++ navigation("bottom")
@@ -218,7 +230,7 @@ timestamp() ->
edoc_lib:timestr(time())])
]}]},
?NL].
-
+
stylesheet(Opts) ->
case Opts#opts.stylesheet of
undefined ->
@@ -335,8 +347,8 @@ label_href(Content, F) ->
%% <!ELEMENT equiv (expr, see?)>
%% <!ELEMENT expr (#PCDATA)>
-functions(Fs) ->
- Es = lists:flatmap(fun ({Name, E}) -> function(Name, E) end, Fs),
+functions(Fs, Opts) ->
+ Es = lists:flatmap(fun ({Name, E}) -> function(Name, E, Opts) end, Fs),
if Es == [] -> [];
true ->
[?NL,
@@ -344,7 +356,7 @@ functions(Fs) ->
?NL | Es]
end.
-function(Name, E=#xmlElement{content = Es}) ->
+function(Name, E=#xmlElement{content = Es}, Opts) ->
([?NL,
{h3, [{class, "function"}],
label_anchor(function_header(Name, E, " *"), E)},
@@ -352,7 +364,7 @@ function(Name, E=#xmlElement{content = Es}) ->
++ [{'div', [{class, "spec"}],
[?NL,
{p,
- case typespec(get_content(typespec, Es)) of
+ case typespec(get_content(typespec, Es), Opts) of
[] ->
signature(get_content(args, Es),
get_attrval(name, E));
@@ -367,7 +379,7 @@ function(Name, E=#xmlElement{content = Es}) ->
[] -> [];
Rs -> [{p, Rs}, ?NL]
end}]
- ++ throws(Es)
+ ++ throws(Es, Opts)
++ equiv_p(Es)
++ deprecated(Es, "function")
++ fulldesc(Es)
@@ -402,7 +414,7 @@ label_anchor(Content, E) ->
%% This is currently only done for functions without type spec.
-signature(Es, Name) ->
+signature(Es, Name) ->
[{tt, [Name, "("] ++ seq(fun arg/1, Es) ++ [") -> any()"]}].
arg(#xmlElement{content = Es}) ->
@@ -432,66 +444,168 @@ returns(Es) ->
%% <!ELEMENT throws (type, localdef*)>
-throws(Es) ->
+throws(Es, Opts) ->
case get_content(throws, Es) of
[] -> [];
Es1 ->
+ %% Doesn't use format_type; keep it short!
[{p, (["throws ", {tt, t_utype(get_elem(type, Es1))}]
- ++ local_defs(get_elem(localdef, Es1)))},
+ ++ local_defs(get_elem(localdef, Es1), Opts))},
?NL]
end.
%% <!ELEMENT typespec (erlangName, type, localdef*)>
-typespec([]) -> [];
-typespec(Es) ->
- [{tt, ([t_name(get_elem(erlangName, Es))]
- ++ t_utype(get_elem(type, Es)))}]
- ++ local_defs(get_elem(localdef, Es)).
+typespec([], _Opts) -> [];
+typespec(Es, Opts) ->
+ Name = t_name(get_elem(erlangName, Es)),
+ Defs = get_elem(localdef, Es),
+ [Type] = get_elem(type, Es),
+ format_spec(Name, Type, Defs, Opts) ++ local_defs(Defs, Opts).
%% <!ELEMENT typedecl (typedef, description?)>
%% <!ELEMENT typedef (erlangName, argtypes, type?, localdef*)>
-types([]) -> [];
-types(Ts) ->
- Es = lists:flatmap(fun ({Name, E}) -> typedecl(Name, E) end, Ts),
+types([], _Opts) -> [];
+types(Ts, Opts) ->
+ Es = lists:flatmap(fun ({Name, E}) -> typedecl(Name, E, Opts) end, Ts),
[?NL,
{h2, [{a, [{name, ?DATA_TYPES_LABEL}],
[?DATA_TYPES_TITLE]}]},
?NL | Es].
-typedecl(Name, E=#xmlElement{content = Es}) ->
+typedecl(Name, E=#xmlElement{content = Es}, Opts) ->
([?NL, {h3, [{class, "typedecl"}], label_anchor([Name, "()"], E)}, ?NL]
- ++ [{p, typedef(get_content(typedef, Es))}, ?NL]
+ ++ [{p, typedef(get_content(typedef, Es), Opts)}, ?NL]
++ fulldesc(Es)).
type_name(#xmlElement{content = Es}) ->
t_name(get_elem(erlangName, get_content(typedef, Es))).
-typedef(Es) ->
+typedef(Es, Opts) ->
Name = ([t_name(get_elem(erlangName, Es)), "("]
- ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])),
+ ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])),
(case get_elem(type, Es) of
[] -> [{b, ["abstract datatype"]}, ": ", {tt, Name}];
- Type ->
- [{tt, Name ++ [" = "] ++ t_utype(Type)}]
+ Type -> format_type(Name, Name, Type, [], Opts)
end
- ++ local_defs(get_elem(localdef, Es))).
+ ++ local_defs(get_elem(localdef, Es), Opts)).
-local_defs([]) -> [];
-local_defs(Es) ->
+local_defs(Es, Opts) ->
+ local_defs(Es, [], Opts).
+
+local_defs([], _, _Opts) -> [];
+local_defs(Es0, Last, Opts) ->
+ [E | Es] = lists:reverse(Es0),
[?NL,
{ul, [{class, "definitions"}],
- lists:append([[{li, [{tt, localdef(E)}]}, ?NL] || E <- Es])}].
-
-localdef(E = #xmlElement{content = Es}) ->
- (case get_elem(typevar, Es) of
- [] ->
- label_anchor(t_abstype(get_content(abstype, Es)), E);
- [V] ->
- t_var(V)
- end
- ++ [" = "] ++ t_utype(get_elem(type, Es))).
+ lists:reverse(lists:append([localdef(E1, [], Opts) || E1 <- Es]),
+ localdef(E, Last, Opts))}].
+
+localdef(E = #xmlElement{content = Es}, Last, Opts) ->
+ Name = case get_elem(typevar, Es) of
+ [] ->
+ label_anchor(N0 = t_abstype(get_content(abstype, Es)), E);
+ [V] ->
+ N0 = t_var(V)
+ end,
+ [{li, format_type(Name, N0, get_elem(type, Es), Last, Opts)}].
+
+%% Use the default formatting of EDoc, which creates references, and
+%% then insert newlines and indentation according to erl_pp (the
+%% (fast) Erlang pretty printer).
+format_spec(Name, Type, Defs, #opts{pretty_printer = erl_pp}=Opts) ->
+ try
+ L = t_clause(Name, Type),
+ O = pp_clause(Name, Type),
+ {R, ".\n"} = etypef(L, O),
+ [{pre, R}]
+ catch _:_ ->
+ %% Example: "@spec ... -> record(a)"
+ format_spec(Name, Type, Defs, Opts#opts{pretty_printer=''})
+ end;
+format_spec(Sep, Type, Defs, _Opts) ->
+ %% Very limited formatting.
+ Br = if Defs =:= [] -> br; true -> [] end,
+ [{tt, t_clause(Sep, Type)}, Br].
+
+t_clause(Name, Type) ->
+ #xmlElement{content = [#xmlElement{name = 'fun', content = C}]} = Type,
+ [Name] ++ t_fun(C).
+
+pp_clause(Pre, Type) ->
+ Types = ot_utype([Type]),
+ Atom = lists:duplicate(iolist_size(Pre), $a),
+ L1 = erl_pp:attribute({attribute,0,spec,{{list_to_atom(Atom),0},[Types]}}),
+ "-spec " ++ L2 = lists:flatten(L1),
+ L3 = Pre ++ lists:nthtail(length(Atom), L2),
+ re:replace(L3, "\n ", "\n", [{return,list},global]).
+
+format_type(Prefix, Name, Type, Last, #opts{pretty_printer = erl_pp}=Opts) ->
+ try
+ L = t_utype(Type),
+ O = pp_type(Name, Type),
+ {R, ".\n"} = etypef(L, O),
+ [{pre, Prefix ++ [" = "] ++ R ++ Last}]
+ catch _:_ ->
+ %% Example: "t() = record(a)."
+ format_type(Prefix, Name, Type, Last, Opts#opts{pretty_printer =''})
+ end;
+format_type(Prefix, _Name, Type, Last, _Opts) ->
+ [{tt, Prefix ++ [" = "] ++ t_utype(Type) ++ Last}].
+
+pp_type(Prefix, Type) ->
+ Atom = list_to_atom(lists:duplicate(iolist_size(Prefix), $a)),
+ L1 = erl_pp:attribute({attribute,0,type,{Atom,ot_utype(Type),[]}}),
+ {L2,N} = case lists:dropwhile(fun(C) -> C =/= $: end, lists:flatten(L1)) of
+ ":: " ++ L3 -> {L3,9}; % compensation for extra "()" and ":"
+ "::\n" ++ L3 -> {"\n"++L3,6}
+ end,
+ Ss = lists:duplicate(N, $\s),
+ re:replace(L2, "\n"++Ss, "\n", [{return,list},global]).
+
+etypef(L, O0) ->
+ {R, O} = etypef(L, [], O0, []),
+ {lists:reverse(R), O}.
+
+etypef([C | L], St, [C | O], R) ->
+ etypef(L, St, O, [[C] | R]);
+etypef(" "++L, St, O, R) ->
+ etypef(L, St, O, R);
+etypef("", [Cs | St], O, R) ->
+ etypef(Cs, St, O, R);
+etypef("", [], O, R) ->
+ {R, O};
+etypef(L, St, " "++O, R) ->
+ etypef(L, St, O, [" " | R]);
+etypef(L, St, "\n"++O, R) ->
+ Ss = lists:takewhile(fun(C) -> C =:= $\s end, O),
+ etypef(L, St, lists:nthtail(length(Ss), O), ["\n"++Ss | R]);
+etypef([{a, HRef, S0} | L], St, O0, R) ->
+ {S, O} = etypef(S0, app_fix(O0)),
+ etypef(L, St, O, [{a, HRef, S} | R]);
+etypef("="++L, St, "::"++O, R) ->
+ %% EDoc uses "=" for record field types; Erlang types use "::".
+ %% Maybe there should be an option for this, possibly affecting
+ %% other similar discrepancies.
+ etypef(L, St, O, ["=" | R]);
+etypef([Cs | L], St, O, R) ->
+ etypef(Cs, [L | St], O, R).
+
+app_fix(L) ->
+ try
+ {"//" ++ R1,L2} = app_fix(L, 1),
+ [App, Mod] = string:tokens(R1, "/"),
+ "//" ++ atom(App) ++ "/" ++ atom(Mod) ++ L2
+ catch _:_ -> L
+ end.
+
+app_fix(L, I) -> % a bit slow
+ {L1, L2} = lists:split(I, L),
+ case erl_scan:tokens([], L1 ++ ". ", 1) of
+ {done, {ok,[{atom,_,Atom}|_],_}, _} -> {atom_to_list(Atom), L2};
+ _ -> app_fix(L, I+1)
+ end.
fulldesc(Es) ->
case get_content(fullDescription, get_content(description, Es)) of
@@ -702,21 +816,28 @@ t_type([E=#xmlElement{name = atom}]) ->
t_atom(E);
t_type([E=#xmlElement{name = integer}]) ->
t_integer(E);
+t_type([E=#xmlElement{name = range}]) ->
+ t_range(E);
+t_type([E=#xmlElement{name = binary}]) ->
+ t_binary(E);
t_type([E=#xmlElement{name = float}]) ->
t_float(E);
t_type([#xmlElement{name = nil}]) ->
t_nil();
+t_type([#xmlElement{name = paren, content = Es}]) ->
+ t_paren(Es);
t_type([#xmlElement{name = list, content = Es}]) ->
t_list(Es);
+t_type([#xmlElement{name = nonempty_list, content = Es}]) ->
+ t_nonempty_list(Es);
t_type([#xmlElement{name = tuple, content = Es}]) ->
t_tuple(Es);
t_type([#xmlElement{name = 'fun', content = Es}]) ->
- t_fun(Es);
-t_type([#xmlElement{name = record, content = Es}]) ->
- t_record(Es);
+ ["fun("] ++ t_fun(Es) ++ [")"];
+t_type([E = #xmlElement{name = record, content = Es}]) ->
+ t_record(E, Es);
t_type([E = #xmlElement{name = abstype, content = Es}]) ->
- T = t_abstype(Es),
- see(E, T);
+ t_abstype(E, Es);
t_type([#xmlElement{name = union, content = Es}]) ->
t_union(Es).
@@ -729,15 +850,27 @@ t_atom(E) ->
t_integer(E) ->
[get_attrval(value, E)].
+t_range(E) ->
+ [get_attrval(value, E)].
+
+t_binary(E) ->
+ [get_attrval(value, E)].
+
t_float(E) ->
[get_attrval(value, E)].
t_nil() ->
["[]"].
+t_paren(Es) ->
+ ["("] ++ t_utype(get_elem(type, Es)) ++ [")"].
+
t_list(Es) ->
["["] ++ t_utype(get_elem(type, Es)) ++ ["]"].
+t_nonempty_list(Es) ->
+ ["["] ++ t_utype(get_elem(type, Es)) ++ [", ...]"].
+
t_tuple(Es) ->
["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]).
@@ -745,13 +878,27 @@ t_fun(Es) ->
["("] ++ seq(fun t_utype_elem/1, get_content(argtypes, Es),
[") -> "] ++ t_utype(get_elem(type, Es))).
-t_record(Es) ->
- ["#"] ++ t_type(get_elem(atom, Es)) ++ ["{"]
- ++ seq(fun t_field/1, get_elem(field, Es), ["}"]).
+t_record(E, Es) ->
+ Name = ["#"] ++ t_type(get_elem(atom, Es)),
+ case get_elem(field, Es) of
+ [] ->
+ see(E, [Name, "{}"]);
+ Fs ->
+ see(E, Name) ++ ["{"] ++ seq(fun t_field/1, Fs, ["}"])
+ end.
t_field(#xmlElement{content = Es}) ->
t_type(get_elem(atom, Es)) ++ [" = "] ++ t_utype(get_elem(type, Es)).
+t_abstype(E, Es) ->
+ Name = t_name(get_elem(erlangName, Es)),
+ case get_elem(type, Es) of
+ [] ->
+ see(E, [Name, "()"]);
+ Ts ->
+ see(E, [Name]) ++ ["("] ++ seq(fun t_utype_elem/1, Ts, [")"])
+ end.
+
t_abstype(Es) ->
([t_name(get_elem(erlangName, Es)), "("]
++ seq(fun t_utype_elem/1, get_elem(type, Es), [")"])).
@@ -827,7 +974,8 @@ type(E) ->
type(E, []).
type(E, Ds) ->
- xmerl:export_simple_content(t_utype_elem(E) ++ local_defs(Ds),
+ Opts = [],
+ xmerl:export_simple_content(t_utype_elem(E) ++ local_defs(Ds, Opts),
?HTML_EXPORT).
package(E=#xmlElement{name = package, content = Es}, Options) ->
@@ -873,3 +1021,142 @@ overview(E=#xmlElement{name = overview, content = Es}, Options) ->
++ timestamp()),
XML = xhtml(Title, stylesheet(Opts), Body),
xmerl:export_simple(XML, ?HTML_EXPORT, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% NYTT
+
+ot_utype([E]) ->
+ ot_utype_elem(E).
+
+ot_utype_elem(E=#xmlElement{content = Es}) ->
+ case get_attrval(name, E) of
+ "" -> ot_type(Es);
+ N ->
+ Name = {var,0,list_to_atom(N)},
+ T = ot_type(Es),
+ case T of
+ Name -> T;
+ T -> {ann_type,0,[Name, T]}
+ end
+ end.
+
+ot_type([E=#xmlElement{name = typevar}]) ->
+ ot_var(E);
+ot_type([E=#xmlElement{name = atom}]) ->
+ ot_atom(E);
+ot_type([E=#xmlElement{name = integer}]) ->
+ ot_integer(E);
+ot_type([E=#xmlElement{name = range}]) ->
+ ot_range(E);
+ot_type([E=#xmlElement{name = binary}]) ->
+ ot_binary(E);
+ot_type([E=#xmlElement{name = float}]) ->
+ ot_float(E);
+ot_type([#xmlElement{name = nil}]) ->
+ ot_nil();
+ot_type([#xmlElement{name = paren, content = Es}]) ->
+ ot_paren(Es);
+ot_type([#xmlElement{name = list, content = Es}]) ->
+ ot_list(Es);
+ot_type([#xmlElement{name = nonempty_list, content = Es}]) ->
+ ot_nonempty_list(Es);
+ot_type([#xmlElement{name = tuple, content = Es}]) ->
+ ot_tuple(Es);
+ot_type([#xmlElement{name = 'fun', content = Es}]) ->
+ ot_fun(Es);
+ot_type([#xmlElement{name = record, content = Es}]) ->
+ ot_record(Es);
+ot_type([#xmlElement{name = abstype, content = Es}]) ->
+ ot_abstype(Es);
+ot_type([#xmlElement{name = union, content = Es}]) ->
+ ot_union(Es).
+
+ot_var(E) ->
+ {var,0,list_to_atom(get_attrval(name, E))}.
+
+ot_atom(E) ->
+ {ok, [Atom], _} = erl_scan:string(get_attrval(value, E), 0),
+ Atom.
+
+ot_integer(E) ->
+ {integer,0,list_to_integer(get_attrval(value, E))}.
+
+ot_range(E) ->
+ [I1, I2] = string:tokens(get_attrval(value, E), "."),
+ {type,0,range,[{integer,0,list_to_integer(I1)},
+ {integer,0,list_to_integer(I2)}]}.
+
+ot_binary(E) ->
+ {Base, Unit} =
+ case string:tokens(get_attrval(value, E), ",:*><") of
+ [] ->
+ {0, 0};
+ ["_",B] ->
+ {list_to_integer(B), 0};
+ ["_","_",U] ->
+ {0, list_to_integer(U)};
+ ["_",B,_,"_",U] ->
+ {list_to_integer(B), list_to_integer(U)}
+ end,
+ {type,0,binary,[{integer,0,Base},{integer,0,Unit}]}.
+
+ot_float(E) ->
+ {float,0,list_to_float(get_attrval(value, E))}.
+
+ot_nil() ->
+ {nil,0}.
+
+ot_paren(Es) ->
+ {paren_type,0,[ot_utype(get_elem(type, Es))]}.
+
+ot_list(Es) ->
+ {type,0,list,[ot_utype(get_elem(type, Es))]}.
+
+ot_nonempty_list(Es) ->
+ {type,0,nonempty_list,[ot_utype(get_elem(type, Es))]}.
+
+ot_tuple(Es) ->
+ {type,0,tuple,[ot_utype_elem(E) || E <- Es]}.
+
+ot_fun(Es) ->
+ Range = ot_utype(get_elem(type, Es)),
+ Args = [ot_utype_elem(A) || A <- get_content(argtypes, Es)],
+ {type,0,'fun',[{type,0,product,Args},Range]}.
+
+ot_record(Es) ->
+ {type,0,record,[ot_type(get_elem(atom, Es)) |
+ [ot_field(F) || F <- get_elem(field, Es)]]}.
+
+ot_field(#xmlElement{content = Es}) ->
+ {type,0,field_type,
+ [ot_type(get_elem(atom, Es)), ot_utype(get_elem(type, Es))]}.
+
+ot_abstype(Es) ->
+ ot_name(get_elem(erlangName, Es),
+ [ot_utype_elem(Elem) || Elem <- get_elem(type, Es)]).
+
+ot_union(Es) ->
+ {type,0,union,[ot_utype_elem(E) || E <- Es]}.
+
+ot_name(Es, T) ->
+ case ot_name(Es) of
+ [Mod, ":", Atom] ->
+ {remote_type,0,[{atom,0,list_to_atom(Mod)},
+ {atom,0,list_to_atom(Atom)},T]};
+ "tuple" when T =:= [] ->
+ {type,0,tuple,any};
+ Atom ->
+ {type,0,list_to_atom(Atom),T}
+ end.
+
+ot_name([E]) ->
+ Atom = get_attrval(name, E),
+ case get_attrval(module, E) of
+ "" -> Atom;
+ M ->
+ case get_attrval(app, E) of
+ "" ->
+ [M, ":", Atom];
+ A ->
+ ["//"++A++"/" ++ M, ":", Atom] % EDoc only!
+ end
+ end.
diff --git a/lib/edoc/src/edoc_lib.erl b/lib/edoc/src/edoc_lib.erl
index 6705ccd356..585e30a2d2 100644
--- a/lib/edoc/src/edoc_lib.erl
+++ b/lib/edoc/src/edoc_lib.erl
@@ -947,6 +947,7 @@ get_doc_env(Opts) ->
%% Modules = [atom()]
%% proplist() = [term()]
%%
+%% @type proplist() = proplists:property().
%% @type edoc_env(). Environment information needed by EDoc for
%% generating references. The data representation is not documented.
%%
diff --git a/lib/edoc/src/edoc_parser.yrl b/lib/edoc/src/edoc_parser.yrl
index 91ee5a1b2b..6943f1bdb8 100644
--- a/lib/edoc/src/edoc_parser.yrl
+++ b/lib/edoc/src/edoc_parser.yrl
@@ -24,21 +24,22 @@
%%
%% Author contact: [email protected]
%%
-%% $Id$
+%% $Id $
%%
%% =====================================================================
Nonterminals
start spec func_type utype_list utype_tuple utypes utype ptypes ptype
-nutype function_name where_defs defs def typedef etype throws qname ref
-aref mref lref pref var_list vars fields field.
+nutype function_name where_defs defs defs2 def typedef etype
+throws qname ref aref mref lref pref var_list vars fields field
+futype_list bin_base_type bin_unit_type.
Terminals
-atom float integer var string start_spec start_typedef start_throws
+atom float integer var an_var string start_spec start_typedef start_throws
start_ref
'(' ')' ',' '.' '->' '{' '}' '[' ']' '|' '+' ':' '::' '=' '/' '//' '*'
-'#' 'where'.
+'#' 'where' '<<' '>>' '..' '...'.
Rootsymbol start.
@@ -52,9 +53,9 @@ qname -> atom: [tok_val('$1')].
qname -> qname '.' atom: [tok_val('$3') | '$1'].
spec -> func_type where_defs:
- #t_spec{type = '$1', defs = lists:reverse('$2')}.
+ #t_spec{type = '$1', defs = '$2'}.
spec -> function_name func_type where_defs:
- #t_spec{name = '$1', type = '$2', defs = lists:reverse('$3')}.
+ #t_spec{name = '$1', type = '$2', defs = '$3'}.
where_defs -> 'where' defs: '$2'.
where_defs -> defs: '$1'.
@@ -66,13 +67,15 @@ func_type -> utype_list '->' utype:
%% Paired with line number, for later error reporting
-utype_list -> '(' ')' : {[], tok_line('$1')}.
utype_list -> '(' utypes ')' : {lists:reverse('$2'), tok_line('$1')}.
-utype_tuple -> '{' '}' : [].
+futype_list -> utype_list : '$1'.
+futype_list -> '(' '...' ')' : {[#t_var{name = '...'}], tok_line('$1')}.
+
utype_tuple -> '{' utypes '}' : lists:reverse('$2').
%% Produced in reverse order.
+utypes -> '$empty' : [].
utypes -> utype : ['$1'].
utypes -> utypes ',' utype : ['$3' | '$1'].
@@ -90,20 +93,25 @@ ptypes -> ptypes '|' ptype : ['$3' | '$1'].
ptype -> var : #t_var{name = tok_val('$1')}.
ptype -> atom : #t_atom{val = tok_val('$1')}.
ptype -> integer: #t_integer{val = tok_val('$1')}.
+ptype -> integer '..' integer: #t_integer_range{from = tok_val('$1'),
+ to = tok_val('$3')}.
ptype -> float: #t_float{val = tok_val('$1')}.
ptype -> utype_tuple : #t_tuple{types = '$1'}.
ptype -> '[' ']' : #t_nil{}.
ptype -> '[' utype ']' : #t_list{type = '$2'}.
+ptype -> '[' utype ',' '...' ']' : #t_nonempty_list{type = '$2'}.
ptype -> utype_list:
- if length(element(1, '$1')) == 1 ->
+ if length(element(1, '$1')) == 1 ->
%% there must be exactly one utype in the list
hd(element(1, '$1'));
+ %% Replace last line when releasing next major release:
+ %% #t_paren{type = hd(element(1, '$1'))};
length(element(1, '$1')) == 0 ->
return_error(element(2, '$1'), "syntax error before: ')'");
true ->
return_error(element(2, '$1'), "syntax error before: ','")
end.
-ptype -> utype_list '->' ptype:
+ptype -> futype_list '->' ptype:
#t_fun{args = element(1, '$1'), range = '$3'}.
ptype -> '#' atom '{' '}' :
#t_record{name = #t_atom{val = tok_val('$2')}}.
@@ -111,17 +119,45 @@ ptype -> '#' atom '{' fields '}' :
#t_record{name = #t_atom{val = tok_val('$2')},
fields = lists:reverse('$4')}.
ptype -> atom utype_list:
- #t_type{name = #t_name{name = tok_val('$1')},
- args = element(1, '$2')}.
-ptype -> qname ':' atom utype_list :
+ case {tok_val('$1'), element(1, '$2')} of
+ {nil, []} ->
+ %% Prefer '[]' before 'nil(). Due to
+ %% compatibility with Erlang types, which do not
+ %% separate '[]' from 'nil()'.
+ #t_nil{};
+ {list, [T]} ->
+ %% Prefer '[T]' before 'list(T). Due to
+ %% compatibility with Erlang types, which do not
+ %% separate '[T]' from 'list(T)'.
+ #t_list{type = T};
+ {'fun', [#t_fun{}=Fun]} ->
+ %% An incompatible change as compared to EDOc 0.7.6.6.
+ %% Due to compatibility with Erlang types.
+ Fun;
+ {'fun', []} ->
+ #t_type{name = #t_name{name = function}};
+ {Name, Args} ->
+ #t_type{name = #t_name{name = Name},
+ args = Args}
+ end.
+ptype -> qname ':' atom utype_list :
#t_type{name = #t_name{module = qname('$1'),
name = tok_val('$3')},
args = element(1, '$4')}.
-ptype -> '//' atom '/' qname ':' atom utype_list :
+ptype -> '//' atom '/' qname ':' atom utype_list :
#t_type{name = #t_name{app = tok_val('$2'),
module = qname('$4'),
name = tok_val('$6')},
args = element(1, '$7')}.
+ptype -> '<<' '>>' : #t_binary{}.
+ptype -> '<<' bin_base_type '>>' : #t_binary{base_size = '$2'}.
+ptype -> '<<' bin_unit_type '>>' : #t_binary{unit_size = '$2'}.
+ptype -> '<<' bin_base_type ',' bin_unit_type '>>' :
+ #t_binary{base_size = '$2', unit_size = '$4'}.
+
+bin_base_type -> an_var ':' integer: tok_val('$3').
+
+bin_unit_type -> an_var ':' an_var '*' integer : tok_val('$5').
%% Produced in reverse order.
fields -> field : ['$1'].
@@ -130,18 +166,19 @@ fields -> fields ',' field : ['$3' | '$1'].
field -> atom '=' utype :
#t_field{name = #t_atom{val = tok_val('$1')}, type = '$3'}.
-%% Produced in reverse order.
defs -> '$empty' : [].
-defs -> defs def : ['$2' | '$1'].
-defs -> defs ',' def : ['$3' | '$1'].
+defs -> def defs2 : ['$1' | lists:reverse('$2')].
+
+%% Produced in reverse order.
+defs2 -> '$empty' : [].
+defs2 -> defs2 def : ['$2' | '$1'].
+defs2 -> defs2 ',' def : ['$3' | '$1'].
def -> var '=' utype:
#t_def{name = #t_var{name = tok_val('$1')},
type = '$3'}.
-def -> atom var_list '=' utype:
- #t_def{name = #t_type{name = #t_name{name = tok_val('$1')},
- args = '$2'},
- type = '$4'}.
+def -> atom '(' utypes ')' '=' utype:
+ build_def(tok_val('$1'), '$2', '$3', '$6').
var_list -> '(' ')' : [].
var_list -> '(' vars ')' : lists:reverse('$2').
@@ -153,12 +190,12 @@ vars -> vars ',' var : [#t_var{name = tok_val('$3')} | '$1'].
typedef -> atom var_list where_defs:
#t_typedef{name = #t_name{name = tok_val('$1')},
args = '$2',
- defs = lists:reverse('$3')}.
+ defs = '$3'}.
typedef -> atom var_list '=' utype where_defs:
#t_typedef{name = #t_name{name = tok_val('$1')},
args = '$2',
type = '$4',
- defs = lists:reverse('$5')}.
+ defs = '$5'}.
%% References
@@ -195,7 +232,7 @@ etype -> utype: '$1'.
throws -> etype where_defs:
#t_throws{type = '$1',
- defs = lists:reverse('$2')}.
+ defs = '$2'}.
%% (commented out for now)
%% Header
@@ -297,7 +334,22 @@ union(Ts) ->
end.
annotate(T, A) -> ?add_t_ann(T, A).
-
+
+build_def(S, P, As, T) ->
+ case all_vars(As) of
+ true ->
+ #t_def{name = #t_type{name = #t_name{name = S},
+ args = lists:reverse(As)},
+ type = T};
+ false ->
+ return_error(element(2, P), "variable expected after '('")
+ end.
+
+all_vars([#t_var{} | As]) ->
+ all_vars(As);
+all_vars(As) ->
+ As =:= [].
+
%% ---------------------------------------------------------------------
%% @doc EDoc type specification parsing. Parses the content of
@@ -379,7 +431,7 @@ parse_param(S, L) ->
{S1, S2} = edoc_lib:split_at_space(edoc_lib:strip_space(S)),
case edoc_lib:strip_space(S1) of
"" -> throw_error(parse_param, L);
- Name ->
+ Name ->
Text = edoc_lib:strip_space(S2),
{list_to_atom(Name), edoc_wiki:parse_xml(Text, L)}
end.
diff --git a/lib/edoc/src/edoc_refs.erl b/lib/edoc/src/edoc_refs.erl
index edc30674c0..b974cf77c1 100644
--- a/lib/edoc/src/edoc_refs.erl
+++ b/lib/edoc/src/edoc_refs.erl
@@ -19,7 +19,7 @@
%% @author Richard Carlsson <[email protected]>
%% @see edoc
%% @see edoc_parse_ref
-%% @end
+%% @end
%% =====================================================================
%% @doc Representation and handling of EDoc object references. See
diff --git a/lib/edoc/src/edoc_scanner.erl b/lib/edoc/src/edoc_scanner.erl
index d3dff64682..9d2e6f3aed 100644
--- a/lib/edoc/src/edoc_scanner.erl
+++ b/lib/edoc/src/edoc_scanner.erl
@@ -3,24 +3,24 @@
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and
%% limitations under the License.
-%%
+%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings
%% AB. Portions created by Ericsson are Copyright 1999, Ericsson
%% Utvecklings AB. All Rights Reserved.''
%%
-%% $Id$
+%% $Id: $
%%
%% @private
%% @copyright Richard Carlsson 2001-2003. Portions created by Ericsson
%% are Copyright 1999, Ericsson Utvecklings AB. All Rights Reserved.
%% @author Richard Carlsson <[email protected]>
%% @see edoc
-%% @end
+%% @end
%% @doc Tokeniser for EDoc. Based on the Erlang standard library module
%% {@link //stdlib/erl_scan}.
@@ -139,13 +139,21 @@ scan1([$"|Cs0], Toks, Pos) -> % String
scan_error({illegal, string}, Pos)
end;
%% Punctuation characters and operators, first recognise multiples.
+scan1([$<,$<|Cs], Toks, Pos) ->
+ scan1(Cs, [{'<<',Pos}|Toks], Pos);
+scan1([$>,$>|Cs], Toks, Pos) ->
+ scan1(Cs, [{'>>',Pos}|Toks], Pos);
scan1([$-,$>|Cs], Toks, Pos) ->
scan1(Cs, [{'->',Pos}|Toks], Pos);
scan1([$:,$:|Cs], Toks, Pos) ->
scan1(Cs, [{'::',Pos}|Toks], Pos);
scan1([$/,$/|Cs], Toks, Pos) ->
scan1(Cs, [{'//',Pos}|Toks], Pos);
-scan1([C|Cs], Toks, Pos) -> % Punctuation character
+scan1([$.,$.,$.|Cs], Toks, Pos) ->
+ scan1(Cs, [{'...',Pos}|Toks], Pos);
+scan1([$.,$.|Cs], Toks, Pos) ->
+ scan1(Cs, [{'..',Pos}|Toks], Pos);
+scan1([C|Cs], Toks, Pos) -> % Punctuation character
P = list_to_atom([C]),
scan1(Cs, [{P,Pos}|Toks], Pos);
scan1([], Toks0, _Pos) ->
@@ -158,7 +166,7 @@ scan_variable(C, Cs, Toks, Pos) ->
W = [C|reverse(Wcs)],
case W of
"_" ->
- scan_error({illegal,token}, Pos);
+ scan1(Cs1, [{an_var,Pos,'_'}|Toks], Pos);
_ ->
case catch list_to_atom(W) of
A when is_atom(A) ->
@@ -318,7 +326,7 @@ scan_integer(Cs, Stack, Pos) ->
scan_after_int([$.,C|Cs0], Ncs0, Toks, SPos, CPos) when C >= $0, C =< $9 ->
{Ncs,Cs,CPos1} = scan_integer(Cs0, [C,$.|Ncs0], CPos),
- scan_after_fraction(Cs, Ncs, Toks, SPos, CPos1);
+ scan_after_fraction(Cs, Ncs, Toks, SPos, CPos1);
scan_after_int(Cs, Ncs, Toks, SPos, CPos) ->
N = list_to_integer(reverse(Ncs)),
scan1(Cs, [{integer,SPos,N}|Toks], CPos).
diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl
new file mode 100644
index 0000000000..45016ef85a
--- /dev/null
+++ b/lib/edoc/src/edoc_specs.erl
@@ -0,0 +1,603 @@
+%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+%% @doc EDoc interface to Erlang specifications and types.
+
+-module(edoc_specs).
+
+-export([type/2, spec/2, dummy_spec/1, docs/2]).
+
+-export([add_data/4, tag/1, is_tag/1]).
+
+-include("edoc.hrl").
+-include("edoc_types.hrl").
+
+-type proplist() :: [proplists:property()].
+-type syntaxTree() :: erl_syntax:syntaxTree().
+
+-define(TOP_TYPE, term).
+
+%%
+%% Exported functions
+%%
+
+-spec type(Form::syntaxTree(), TypeDocs::dict()) -> #tag{}.
+
+%% @doc Convert an Erlang type to EDoc representation.
+%% TypeDocs is a dict of {Name, Doc}.
+%% Note: #t_typedef.name is set to {record, R} for record types.
+type(Form, TypeDocs) ->
+ {Name, Data0} = erl_syntax_lib:analyze_wild_attribute(Form),
+ type = tag(Name),
+ {TypeName, Type, Args, Doc} =
+ case Data0 of
+ {{record, R}, Fs, []} ->
+ L = erl_syntax:get_pos(Form),
+ {{record, R}, {type, L, record, [{atom,L,R} | Fs]}, [], ""};
+ {N,T,As} ->
+ Doc0 =
+ case dict:find({N, length(As)}, TypeDocs) of
+ {ok, Doc1} ->
+ Doc1;
+ error ->
+ ""
+ end,
+ {#t_name{name = N}, T, As, Doc0}
+ end,
+ #tag{name = type, line = element(2, Type),
+ origin = code,
+ data = {#t_typedef{name = TypeName,
+ args = d2e(Args),
+ type = d2e(opaque2abstr(Name, Type))},
+ Doc}}.
+
+-spec spec(Form::syntaxTree(), ClauseN::pos_integer()) -> #tag{}.
+
+%% @doc Convert an Erlang spec to EDoc representation.
+spec(Form, Clause) ->
+ {Name, _Arity, TypeSpecs} = get_spec(Form),
+ TypeSpec = lists:nth(Clause, TypeSpecs),
+ #tag{name = spec, line = element(2, TypeSpec),
+ origin = code,
+ data = aspec(d2e(TypeSpec), Name)}.
+
+-spec dummy_spec(Form::syntaxTree()) -> #tag{}.
+
+%% @doc Create a #tag{} record where data is a string with the name of
+%% the given Erlang spec and an empty list of arguments.
+dummy_spec(Form) ->
+ {#t_name{name = Name}, Arity, TypeSpecs} = get_spec(Form),
+ As = string:join(lists:duplicate(Arity, "_X"), ","),
+ S = lists:flatten(io_lib:format("~p(~s) -> true\n", [Name, As])),
+ #tag{name = spec, line = element(2, hd(TypeSpecs)),
+ origin = code, data = S}.
+
+-spec docs(Forms::[syntaxTree()], CommentFun) -> dict() when
+ CommentFun :: fun(([syntaxTree()], Line :: term()) -> #tag{}).
+
+%% @doc Find comments after -type/-opaque declarations.
+%% Postcomments "inside" the type are skipped.
+docs(Forms, CommentFun) ->
+ find_type_docs(Forms, [], CommentFun).
+
+-type entry() :: #entry{}.
+-type module_info() :: #module{}.
+-type entries() :: [entry()].
+-spec add_data(Entries::entries(), Options::proplist(),
+ File::file:filename(), Module::module_info()) -> entries().
+
+%% @doc Create tags a la EDoc for Erlang specifications and types.
+%% Exported types and types used (indirectly) by Erlang specs are
+%% added to the entries.
+add_data(Entries, Opts, File, Module) ->
+ TypeDefs0 = espec_types(Entries),
+ TypeTable = ets:new(etypes, [ordered_set]),
+ Es1 = expand_records(Entries, TypeDefs0, TypeTable, Opts, File, Module),
+ Es = [use_tags(E, TypeTable) || E <- Es1],
+ true = ets:delete(TypeTable),
+ Es.
+
+%%
+%% Local functions
+%%
+
+aspec(#t_spec{}=Spec, Name) ->
+ Spec#t_spec{name = Name};
+aspec(Type, Name) ->
+ #t_spec{name = Name, type = Type}.
+
+get_spec(Form) ->
+ {spec, Data0} = erl_syntax_lib:analyze_wild_attribute(Form),
+ case Data0 of
+ {{F,A}, D} ->
+ {#t_name{name = F}, A, D};
+ {{M,F,A}, D} ->
+ {#t_name{module = M, name = F}, A, D}
+ end.
+
+find_type_docs([], Cs, _Fun) ->
+ dict:from_list(Cs);
+find_type_docs([F | Fs], Cs, Fun) ->
+ try get_name_and_last_line(F) of
+ {Name, LastTypeLine} ->
+ C0 = erl_syntax:comment(["% @type f(). "]),
+ C1 = erl_syntax:set_pos(C0, LastTypeLine),
+ %% Postcomments before the dot after the typespec are ignored.
+ C2 = [C1 | [C ||
+ C <- erl_syntax:get_postcomments(F),
+ get_line(erl_syntax:get_pos(C)) >= LastTypeLine]],
+ C3 = collect_comments(Fs, LastTypeLine),
+ #tag{data = Doc0} = Fun(lists:reverse(C2 ++ C3), LastTypeLine),
+ case strip(Doc0) of % Strip away "f(). \n"
+ "" ->
+ find_type_docs(Fs, Cs, Fun);
+ Doc ->
+ W = edoc_wiki:parse_xml(Doc, LastTypeLine),
+ find_type_docs(Fs, [{Name, W}|Cs], Fun)
+ end
+ catch _:_ ->
+ find_type_docs(Fs, Cs, Fun)
+ end.
+
+collect_comments([], _Line) ->
+ [];
+collect_comments([F | Fs], Line) ->
+ L1 = get_line(erl_syntax:get_pos(F)),
+ if
+ L1 =:= Line + 1;
+ L1 =:= Line -> % a separate postcomment
+ case is_comment(F) of
+ true ->
+ [F | collect_comments(Fs, L1)];
+ false ->
+ []
+ end;
+ true ->
+ []
+ end.
+%% Note: there is a creepy bug concerning an include file terminated
+%% by a -type attribute and the include statement is followed by a
+%% comment (which is not meant to be documentation of the type).
+
+is_comment(F) ->
+ erl_syntax_lib:analyze_form(F) =:= comment.
+
+strip("") ->
+ "";
+strip([$\n | S]) ->
+ S;
+strip([_ | S]) ->
+ strip(S).
+
+%% Find the type name and the greatest line number of a type spec.
+%% Should use syntax_tools but this has to do for now.
+get_name_and_last_line(F) ->
+ {Name, Data} = erl_syntax_lib:analyze_wild_attribute(F),
+ type = edoc_specs:tag(Name),
+ Attr = {attribute, erl_syntax:get_pos(F), Name, Data},
+ Ref = make_ref(),
+ Fun = fun(L) -> {Ref, get_line(L)} end,
+ TypeName = case Data of
+ {N, _T, As} when is_atom(N) -> % skip records
+ {N, length(As)}
+ end,
+ Line = gll(erl_lint:modify_line(Attr, Fun), Ref),
+ {TypeName, Line}.
+
+gll({Ref, Line}, Ref) ->
+ Line;
+gll([], _Ref) ->
+ 0;
+gll(List, Ref) when is_list(List) ->
+ lists:max([gll(E, Ref) || E <- List]);
+gll(Tuple, Ref) when is_tuple(Tuple) ->
+ gll(tuple_to_list(Tuple), Ref);
+gll(_, _) ->
+ 0.
+
+get_line(Pos) ->
+ {line, Line} = erl_scan:attributes_info(Pos, line),
+ Line.
+
+%% Collect all Erlang types. Types in comments (@type) shadow Erlang
+%% types (-spec/-opaque).
+espec_types(Entries) ->
+ Tags = get_all_tags(Entries),
+ CommTs = [type_name(T) ||
+ #tag{name = type, origin = comment}=T <- Tags],
+ CT = sets:from_list(CommTs),
+ [T || #tag{name = Name, origin = code}=T <- Tags,
+ tag(Name) =:= type,
+ not sets:is_element(type_name(T), CT)].
+
+get_all_tags(Es) ->
+ lists:flatmap(fun (#entry{data = Ts}) -> Ts end, Es).
+
+%% Turns an opaque type into an abstract datatype.
+%% Note: top level annotation is ignored.
+opaque2abstr(opaque, _T) -> undefined;
+opaque2abstr(type, T) -> T.
+
+%% Replaces the parameters extracted from the source (by
+%% edoc_extract:parameters/1) by annotations and variable names, using
+%% the source parameters as default values
+%% Selects seen types (exported types, types used by specs),
+%% skips records and unused types.
+use_tags(#entry{data = Ts}=E, TypeTable) ->
+ use_tags(Ts, E, TypeTable, []).
+
+use_tags([], E, _TypeTable, NTs) ->
+ E#entry{data = lists:reverse(NTs)};
+use_tags([#tag{origin = code}=T | Ts], E, TypeTable, NTs) ->
+ case tag(T#tag.name) of
+ spec ->
+ Args = params(T, E#entry.args),
+ use_tags(Ts, E#entry{args = Args}, TypeTable, [T | NTs]);
+ type ->
+ TypeName = type_name(T),
+ case ets:lookup(TypeTable, TypeName) of
+ [{{{record,_},_},_,_}] ->
+ use_tags(Ts, E, TypeTable, NTs);
+ [{_,_,not_seen}] ->
+ use_tags(Ts, E, TypeTable, NTs);
+ [] ->
+ use_tags(Ts, E, TypeTable, NTs);
+ [{TypeName, Tag, seen}] ->
+ use_tags(Ts, E, TypeTable, [Tag | NTs])
+ end
+ end;
+use_tags([T | Ts], E, TypeTable, NTs) ->
+ use_tags(Ts, E, TypeTable, [T | NTs]).
+
+params(#tag{name = spec, data=#t_spec{type = #t_fun{args = As}}}, Default) ->
+ parms(As, Default).
+
+parms([], []) ->
+ [];
+parms([A | As], [D | Ds]) ->
+ [param(A, D) | parms(As, Ds)].
+
+param(#t_list{type = Type}, Default) ->
+ param(Type, Default);
+param(#t_paren{type = Type}, Default) ->
+ param(Type, Default);
+param(#t_nonempty_list{type = Type}, Default) ->
+ param(Type, Default);
+param(#t_record{name = #t_atom{val = Name}}, _Default) ->
+ list_to_atom(capitalize(atom_to_list(Name)));
+param(T, Default) ->
+ arg_name(?t_ann(T), Default).
+
+capitalize([C | Cs]) when C >= $a, C =< $z -> [C - 32 | Cs];
+capitalize(Cs) -> Cs.
+
+%% Like edoc_types:arg_name/1
+arg_name([], Default) ->
+ Default;
+arg_name([A | As], Default) ->
+ case is_name(A) of
+ true -> A;
+ false -> arg_name(As, Default)
+ end.
+
+is_name(A) ->
+ is_atom(A).
+
+d2e({ann_type,_,[V, T0]}) ->
+ %% 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({type,_,no_return,[]}) ->
+ #t_type{name = #t_name{name = none}};
+d2e({remote_type,_,[{atom,_,M},{atom,_,F},Ts0]}) ->
+ Ts = d2e(Ts0),
+ typevar_anno(#t_type{name = #t_name{module = M, name = F}, args = Ts}, Ts);
+d2e({type,_,'fun',[{type,_,product,As0},Ran0]}) ->
+ 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]}) ->
+ Ts = [A, Ran] = d2e([A0, Ran0]),
+ typevar_anno(#t_fun{args = [A], range = Ran}, Ts);
+d2e({type,_,'fun',[]}) ->
+ #t_type{name = #t_name{name = function}, args = []};
+d2e({type,_,any}) ->
+ #t_var{name = '...'}; % Kludge... not a type variable!
+d2e({type,_,nil,[]}) ->
+ #t_nil{};
+d2e({paren_type,_,[T]}) ->
+ #t_paren{type = d2e(T)};
+d2e({type,_,list,[T0]}) ->
+ T = d2e(T0),
+ typevar_anno(#t_list{type = T}, [T]);
+d2e({type,_,nonempty_list,[T0]}) ->
+ T = d2e(T0),
+ typevar_anno(#t_nonempty_list{type = T}, [T]);
+d2e({type,_,bounded_fun,[T,Gs]}) ->
+ [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]}) ->
+ {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]}) ->
+ case {Sub,Ts0} of
+ {{atom,_,is_subtype},[{var,_,N},T0]} ->
+ Ts = [T] = d2e([T0]),
+ #t_def{name = #t_var{name = N}, type = typevar_anno(T, Ts)};
+ {{atom,_,is_subtype},[ST0,T0]} ->
+ %% Should not happen.
+ Ts = [ST,T] = d2e([ST0,T0]),
+ #t_def{name = ST, type = typevar_anno(T, Ts)};
+ _ ->
+ throw_error(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}) ->
+ #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,_,tuple,Ts0}) ->
+ Ts = d2e(Ts0),
+ typevar_anno(#t_tuple{types = Ts}, Ts);
+d2e({type,_,record,[Name|Fs0]}) ->
+ 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}) ->
+ Types = d2e(Types0),
+ typevar_anno(#t_type{name = #t_name{name = Name}, args = Types}, Types);
+d2e({var,_,'_'}) ->
+ #t_type{name = #t_name{name = ?TOP_TYPE}};
+d2e({var,_,TypeName}) ->
+ TypeVar = ordsets:from_list([TypeName]),
+ T = #t_var{name = TypeName},
+ %% Annotate type variables with the name of the variable.
+ %% Doing so will stop edoc_layout (and possibly other layout modules)
+ %% 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}) ->
+ #t_atom{val = A};
+d2e(undefined = U) -> % opaque
+ U;
+d2e(Expr) ->
+ {integer,_,I} = erl_eval:partial_eval(Expr),
+ #t_integer{val = I}.
+
+%% A type annotation (a tuple; neither an atom nor a list).
+typevar_anno(Type, Ts) ->
+ Vs = typevars(Ts),
+ case ordsets:to_list(Vs) of
+ [] -> Type;
+ _ -> ?add_t_ann(Type, {type_variables, Vs})
+ end.
+
+typevars(Ts) ->
+ ordsets:union(get_typevars(Ts)).
+
+get_typevars(Ts) ->
+ [Vs || T <- Ts, T =/= undefined, {type_variables, Vs} <- ?t_ann(T)].
+
+-record(parms, {tab, warn, file, line}).
+
+%% Expands record references. Explicitly given record fields are kept,
+%% but otherwise the fields from the record definition are substituted
+%% for the reference. The reason is that there are no record types.
+%% It is recommended to introduce types like "r() :: r{}" and then use
+%% r() everywhere. The right hand side, r{}, is expanded in order to
+%% show all fields.
+%% Returns updated types in the ETS table DT.
+expand_records(Entries, TypeDefs, DT, Opts, File, Module) ->
+ TypeList = [{type_name(T), T, not_seen} || T <- TypeDefs],
+ true = ets:insert(DT, TypeList),
+ Warn = proplists:get_value(report_missing_type, Opts,
+ ?REPORT_MISSING_TYPE) =:= true,
+ P = #parms{tab = DT, warn = Warn, file = File, line = 0},
+ ExportedTypes = [Name ||
+ {export_type,Ts} <- Module#module.attributes,
+ is_list(Ts),
+ {N,I} <- Ts,
+ ets:member(DT, Name = {#t_name{name = N}, I})],
+ _ = lists:foreach(fun({N,A}) -> true = seen_type(N, A, P)
+ end, ExportedTypes),
+ entries(Entries, P, Opts).
+
+entries([E0 | Es], P, Opts) ->
+ E = case edoc_data:hidden_filter([E0], Opts) of
+ [] ->
+ E0;
+ [_] ->
+ E0#entry{data = specs(E0#entry.data, P)}
+ end,
+ [E | entries(Es, P, Opts)];
+entries([], _P, _Opts) ->
+ [].
+
+specs([#tag{line = L, name = spec, origin = code, data = Spec}=Tag0 | Tags],
+ P0) ->
+ #t_spec{type = Type0, defs = Defs0} = Spec,
+ P = P0#parms{line = L},
+ Type = xrecs(Type0, P),
+ Defs = xrecs(Defs0, P),
+ Tag = Tag0#tag{data = Spec#t_spec{type = Type, defs = Defs}},
+ [Tag | specs(Tags, P)];
+specs([Tag | Tags], P) ->
+ [Tag | specs(Tags, P)];
+specs([], _P) ->
+ [].
+
+xrecs(#t_def{type = Type0}=T, P) ->
+ Type = xrecs(Type0, P),
+ T#t_def{type = Type};
+xrecs(#t_type{name = Name, args = Args0}=T, P) ->
+ Args = xrecs(Args0, P),
+ NArgs = length(Args),
+ true = seen_type(Name, NArgs, P),
+ T#t_type{args = Args};
+xrecs(#t_var{}=T, _P) ->
+ T;
+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_tuple{types = Types0}=T, P) ->
+ Types = xrecs(Types0, P),
+ T#t_tuple{types = Types};
+xrecs(#t_list{type = Type0}=T, P) ->
+ Type = xrecs(Type0, P),
+ T#t_list{type = Type};
+xrecs(#t_nil{}=T, _P) ->
+ T;
+xrecs(#t_paren{type = Type0}=T, P) ->
+ Type = xrecs(Type0, P),
+ T#t_paren{type = Type};
+xrecs(#t_nonempty_list{type = Type0}=T, P) ->
+ Type = xrecs(Type0, P),
+ T#t_nonempty_list{type = Type};
+xrecs(#t_atom{}=T, _P) ->
+ T;
+xrecs(#t_integer{}=T, _P) ->
+ T;
+xrecs(#t_integer_range{}=T, _P) ->
+ T;
+xrecs(#t_binary{}=T, _P) ->
+ T;
+xrecs(#t_float{}=T, _P) ->
+ T;
+xrecs(#t_union{types = Types0}=T, P) ->
+ Types = xrecs(Types0, P),
+ T#t_union{types = Types};
+xrecs(#t_record{fields = Fields0}=T, P) ->
+ Fields1 = xrecs(Fields0, P),
+ #t_record{name = #t_atom{val = Name}} = T,
+ RName = {record, Name},
+ true = seen_type(RName, 0, P),
+ Fields = select_fields(Fields1, RName, P#parms.tab),
+ T#t_record{fields = Fields};
+xrecs(#t_field{type = Type0}=T, P) ->
+ Type = xrecs(Type0, P),
+ T#t_field{type = Type};
+xrecs(undefined=T, _P) -> % opaque
+ T;
+xrecs([]=T, _P) ->
+ T;
+xrecs([E0 | Es0], P) ->
+ [xrecs(E0, P) | xrecs(Es0, P)].
+
+seen_type(N, NArgs, P) ->
+ TypeName = {N, NArgs},
+ #parms{tab = DT} = P,
+ case {ets:lookup(DT, TypeName), N} of
+ {[{TypeName, _, seen}], _} ->
+ true;
+ {[{TypeName, TagType, not_seen}], _} when N#t_name.module =:= [] ->
+ expand_datatype(TagType, proper_type, DT, P);
+ {[{TypeName, TagType, not_seen}], {record, _}} ->
+ expand_datatype(TagType, record_type, DT, P);
+ {[], {record, R}} ->
+ #parms{warn = W, line = L, file = File} = P,
+ [edoc_report:warning(L, File, "reference to untyped record ~w",
+ [R]) || W],
+ ets:insert(DT, {TypeName, fake, seen});
+ {[], _} -> % External type or missing type.
+ true
+ end.
+
+expand_datatype(Tag0, Kind, DT, P0) ->
+ #tag{line = L, data = {T0, Doc}} = Tag0,
+ #t_typedef{type = Type0, defs = []} = T0,
+ TypeName = type_name(Tag0),
+ true = ets:update_element(DT, TypeName, {3, seen}),
+ P = P0#parms{line = L},
+ Type = case Kind of
+ record_type ->
+ #t_record{fields = Fields0} = Type0,
+ Fields = xrecs(Fields0, P),
+ Type0#t_record{fields = Fields};
+ proper_type ->
+ xrecs(Type0, P)
+ end,
+ Tag = Tag0#tag{data={T0#t_typedef{type=Type}, Doc}},
+ ets:insert(DT, {TypeName, Tag, seen}).
+
+select_fields(Fields, Name, DT) ->
+ RecordName = {Name, 0},
+ case ets:lookup(DT, RecordName) of
+ [{RecordName, fake, seen}] ->
+ Fields;
+ [{RecordName, #tag{data = {T, _Doc}}, seen}] ->
+ #t_typedef{args = [], type = #t_record{fields = Fs}, defs = []}=T,
+ [find_field(F, Fields) || F <- Fs]
+ end.
+
+find_field(F, Fs) ->
+ case lists:keyfind(F#t_field.name, #t_field.name, Fs) of
+ false -> F;
+ NF -> NF
+ end.
+
+type_name(#tag{name = type,
+ data = {#t_typedef{name = Name, args = As},_}}) ->
+ {Name, length(As)}.
+
+%% @doc Return `true' if `Tag' is one of the specification and type
+%% attribute tags recognized by the Erlang compiler.
+
+-spec is_tag(Tag::atom()) -> boolean().
+
+is_tag(opaque) -> true;
+is_tag(spec) -> true;
+is_tag(type) -> true;
+is_tag(_) -> false.
+
+%% @doc Return the kind of the attribute tag.
+
+-type tag_kind() :: 'type' | 'spec' | 'unknown'.
+-spec tag(Tag::atom()) -> tag_kind().
+
+tag(opaque) -> type;
+tag(spec) -> spec;
+tag(type) -> type;
+tag(_) -> unknown.
+
+throw_error(Line, S, A) ->
+ edoc_report:error(Line, "", io_lib:format(S, A)),
+ throw(error).
diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl
index c0b861e08a..def39ee34c 100644
--- a/lib/edoc/src/edoc_tags.erl
+++ b/lib/edoc/src/edoc_tags.erl
@@ -31,7 +31,8 @@
-module(edoc_tags).
-export([tags/0, tags/1, tag_names/0, tag_parsers/0, scan_lines/2,
- filter_tags/3, check_tags/4, parse_tags/4]).
+ filter_tags/2, filter_tags/3, check_tags/4, parse_tags/4,
+ check_types/3]).
-import(edoc_report, [report/4, warning/4, error/3]).
@@ -201,6 +202,9 @@ append_lines([]) -> [].
%% Filtering out unknown tags.
+filter_tags(Ts, Tags) ->
+ filter_tags(Ts, Tags, no).
+
filter_tags(Ts, Tags, Where) ->
filter_tags(Ts, Tags, Where, []).
@@ -211,7 +215,8 @@ filter_tags([#tag{name = N, line = L} = T | Ts], Tags, Where, Ts1) ->
true ->
filter_tags(Ts, Tags, Where, [T | Ts1]);
false ->
- warning(L, Where, "tag @~s not recognized.", [N]),
+ [warning(L, Where, "tag @~s not recognized.", [N]) ||
+ Where =/= no],
filter_tags(Ts, Tags, Where, Ts1)
end;
filter_tags([], _, _, Ts) ->
@@ -320,12 +325,24 @@ parse_contact(Data, Line, _Env, _Where) ->
Info
end.
-parse_typedef(Data, Line, _Env, _Where) ->
+parse_typedef(Data, Line, _Env, Where) ->
Def = edoc_parser:parse_typedef(Data, Line),
- {#t_typedef{name = #t_name{name = T}}, _} = Def,
- case edoc_types:is_predefined(T) of
+ {#t_typedef{name = #t_name{name = T}, args = As}, _} = Def,
+ NAs = length(As),
+ case edoc_types:is_predefined(T, NAs) of
true ->
- throw_error(Line, {"redefining built-in type '~w'.", [T]});
+ case
+ edoc_types:is_new_predefined(T, NAs)
+ orelse edoc_types:is_predefined_otp_type(T, NAs)
+ of
+ false ->
+ throw_error(Line, {"redefining built-in type '~w'.",
+ [T]});
+ true ->
+ warning(Line, Where, "redefining built-in type '~w'.",
+ [T]),
+ Def
+ end;
false ->
Def
end.
@@ -384,3 +401,107 @@ throw_error(L, file_not_string) ->
throw_error(L, "expected file name as a string");
throw_error(L, D) ->
throw({error, L, D}).
+
+%% Checks local types.
+
+-record(parms, {tab, warn, file, line}).
+
+check_types(Entries0, Opts, File) ->
+ Entries = edoc_data:hidden_filter(Entries0, Opts),
+ Tags = edoc_data:get_all_tags(Entries),
+ DT = ets:new(types, [bag]),
+ _ = [add_type(DT, Name, As, File, Line) ||
+ #tag{line = Line,
+ data = {#t_typedef{name = Name, args = As},_}} <- Tags],
+ Warn = proplists:get_value(report_missing_type, Opts,
+ ?REPORT_MISSING_TYPE) =:= true,
+ P = #parms{tab = DT, warn = Warn, file = File, line = 0},
+ try check_types(Tags, P)
+ after true = ets:delete(DT)
+ end.
+
+add_type(DT, Name, Args, File, Line) ->
+ NArgs = length(Args),
+ TypeName = {Name, NArgs},
+ case lists:member(TypeName, ets:lookup(DT, Name)) of
+ true ->
+ #t_name{name = N} = Name,
+ type_warning(Line, File, "duplicated type", N, NArgs);
+ false ->
+ ets:insert(DT, {Name, NArgs})
+ end.
+
+check_types([], _P)->
+ ok;
+check_types([Tag | Tags], P) ->
+ check_type(Tag, P, Tags).
+
+check_type(#tag{line = L, data = Data}, P0, Ts) ->
+ P = P0#parms{line = L},
+ case Data of
+ {#t_typedef{type = Type, defs = Defs},_} ->
+ check_type(Type, P, Defs++Ts);
+ #t_spec{type = Type, defs = Defs} ->
+ check_type(Type, P, Defs++Ts);
+ _->
+ check_types(Ts, P0)
+ end;
+check_type(#t_def{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_type{name = Name, args = Args}, P, Ts) ->
+ check_used_type(Name, Args, P),
+ check_types(Args++Ts, P);
+check_type(#t_var{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_fun{args = Args, range = Range}, P, Ts) ->
+ check_type(Range, P, Args++Ts);
+check_type(#t_tuple{types = Types}, P, Ts) ->
+ check_types(Types ++Ts, P);
+check_type(#t_list{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_nil{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_paren{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_nonempty_list{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_atom{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_integer{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_integer_range{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_binary{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_float{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_union{types = Types}, P, Ts) ->
+ check_types(Types++Ts, P);
+check_type(#t_record{fields = Fields}, P, Ts) ->
+ check_types(Fields++Ts, P);
+check_type(#t_field{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(undefined, P, Ts) ->
+ check_types(Ts, P).
+
+check_used_type(#t_name{name = N, module = Mod}=Name, Args, P) ->
+ NArgs = length(Args),
+ TypeName = {Name, NArgs},
+ DT = P#parms.tab,
+ case
+ Mod =/= []
+ orelse lists:member(TypeName, ets:lookup(DT, Name))
+ orelse edoc_types:is_predefined(N, NArgs)
+ orelse edoc_types:is_predefined_otp_type(N, NArgs)
+ of
+ true ->
+ ok;
+ false ->
+ #parms{warn = W, line = L, file = File} = P,
+ %% true = ets:insert(DT, TypeName),
+ [type_warning(L, File, "missing type", N, NArgs) || W]
+ end.
+
+type_warning(Line, File, S, N, NArgs) ->
+ AS = ["/"++integer_to_list(NArgs) || NArgs > 0],
+ warning(Line, File, S++" ~w~s", [N, AS]).
diff --git a/lib/edoc/src/edoc_types.erl b/lib/edoc/src/edoc_types.erl
index b0255f793d..1ded63dffe 100644
--- a/lib/edoc/src/edoc_types.erl
+++ b/lib/edoc/src/edoc_types.erl
@@ -14,6 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
+%% $Id$
+%%
%% @private
%% @copyright 2001-2003 Richard Carlsson
%% @author Richard Carlsson <[email protected]>
@@ -25,8 +27,9 @@
-module(edoc_types).
--export([is_predefined/1, to_ref/1, to_xml/2, to_label/1, arg_names/1,
- set_arg_names/2, arg_descs/1, range_desc/1]).
+-export([is_predefined/2, is_new_predefined/2, is_predefined_otp_type/2,
+ to_ref/1, to_xml/2, to_label/1, arg_names/1, set_arg_names/2,
+ arg_descs/1, range_desc/1]).
%% @headerfile "edoc_types.hrl"
@@ -34,27 +37,63 @@
-include("xmerl.hrl").
-is_predefined(any) -> true;
-is_predefined(atom) -> true;
-is_predefined(binary) -> true;
-is_predefined(bool) -> true;
-is_predefined(char) -> true;
-is_predefined(cons) -> true;
-is_predefined(deep_string) -> true;
-is_predefined(float) -> true;
-is_predefined(function) -> true;
-is_predefined(integer) -> true;
-is_predefined(list) -> true;
-is_predefined(nil) -> true;
-is_predefined(none) -> true;
-is_predefined(number) -> true;
-is_predefined(pid) -> true;
-is_predefined(port) -> true;
-is_predefined(reference) -> true;
-is_predefined(string) -> true;
-is_predefined(term) -> true;
-is_predefined(tuple) -> true;
-is_predefined(_) -> false.
+is_predefined(any, 0) -> true;
+is_predefined(atom, 0) -> true;
+is_predefined(binary, 0) -> true;
+is_predefined(bool, 0) -> true;
+is_predefined(char, 0) -> true;
+is_predefined(cons, 2) -> true;
+is_predefined(deep_string, 0) -> true;
+is_predefined(float, 0) -> true;
+is_predefined(function, 0) -> true;
+is_predefined(integer, 0) -> true;
+is_predefined(list, 0) -> true;
+is_predefined(list, 1) -> true;
+is_predefined(nil, 0) -> true;
+is_predefined(none, 0) -> true;
+is_predefined(number, 0) -> true;
+is_predefined(pid, 0) -> true;
+is_predefined(port, 0) -> true;
+is_predefined(reference, 0) -> true;
+is_predefined(string, 0) -> true;
+is_predefined(term, 0) -> true;
+is_predefined(tuple, 0) -> true;
+is_predefined(F, A) -> is_new_predefined(F, A).
+
+%% Should eventually be coalesced with is_predefined/2.
+is_new_predefined(arity, 0) -> true;
+is_new_predefined(bitstring, 0) -> true;
+is_new_predefined(boolean, 0) -> true;
+is_new_predefined(byte, 0) -> true;
+is_new_predefined(iodata, 0) -> true;
+is_new_predefined(iolist, 0) -> true;
+is_new_predefined(maybe_improper_list, 0) -> true;
+is_new_predefined(maybe_improper_list, 2) -> true;
+is_new_predefined(mfa, 0) -> true;
+is_new_predefined(module, 0) -> true;
+is_new_predefined(neg_integer, 0) -> true;
+is_new_predefined(node, 0) -> true;
+is_new_predefined(non_neg_integer, 0) -> true;
+is_new_predefined(nonempty_improper_list, 2) -> true;
+is_new_predefined(nonempty_list, 0) -> true;
+is_new_predefined(nonempty_list, 1) -> true;
+is_new_predefined(nonempty_maybe_improper_list, 0) -> true;
+is_new_predefined(nonempty_maybe_improper_list, 2) -> true;
+is_new_predefined(nonempty_string, 0) -> true;
+is_new_predefined(pos_integer, 0) -> true;
+is_new_predefined(timeout, 0) -> true;
+is_new_predefined(_, _) -> false.
+
+%% The following types will be removed later, but they are currently
+%% kind of built-in.
+is_predefined_otp_type(array, 0) -> true;
+is_predefined_otp_type(dict, 0) -> true;
+is_predefined_otp_type(digraph, 0) -> true;
+is_predefined_otp_type(gb_set, 0) -> true;
+is_predefined_otp_type(gb_tree, 0) -> true;
+is_predefined_otp_type(queue, 0) -> true;
+is_predefined_otp_type(set, 0) -> true;
+is_predefined_otp_type(_, _) -> false.
to_ref(#t_typedef{name = N}) ->
to_ref(N);
@@ -89,7 +128,9 @@ to_xml(#t_name{app = A, module = M, name = N}, _Env) ->
to_xml(#t_type{name = N, args = As}, Env) ->
Predef = case N of
#t_name{module = [], name = T} ->
- is_predefined(T);
+ NArgs = length(As),
+ (is_predefined(T, NArgs)
+ orelse is_predefined_otp_type(T, NArgs));
_ ->
false
end,
@@ -107,14 +148,30 @@ to_xml(#t_list{type = T}, Env) ->
{list, [wrap_utype(T, Env)]};
to_xml(#t_nil{}, _Env) ->
nil;
+to_xml(#t_paren{type = T}, Env) ->
+ {paren, [wrap_utype(T, Env)]};
+to_xml(#t_nonempty_list{type = T}, Env) ->
+ {nonempty_list, [wrap_utype(T, Env)]};
to_xml(#t_atom{val = V}, _Env) ->
{atom, [{value, io_lib:write(V)}], []};
to_xml(#t_integer{val = V}, _Env) ->
{integer, [{value, integer_to_list(V)}], []};
+to_xml(#t_integer_range{from = From, to = To}, _Env) ->
+ {range, [{value, integer_to_list(From)++".."++integer_to_list(To)}], []};
+to_xml(#t_binary{base_size = 0, unit_size = 0}, _Ens) ->
+ {binary, [{value, "<<>>"}], []};
+to_xml(#t_binary{base_size = B, unit_size = 0}, _Ens) ->
+ {binary, [{value, io_lib:fwrite("<<_:~w>>", [B])}], []};
+%to_xml(#t_binary{base_size = 0, unit_size = 8}, _Ens) ->
+% {binary, [{value, "binary()"}], []};
+to_xml(#t_binary{base_size = 0, unit_size = U}, _Ens) ->
+ {binary, [{value, io_lib:fwrite("<<_:_*~w>>", [U])}], []};
+to_xml(#t_binary{base_size = B, unit_size = U}, _Ens) ->
+ {binary, [{value, io_lib:fwrite("<<_:~w, _:_*~w>>", [B, U])}], []};
to_xml(#t_float{val = V}, _Env) ->
{float, [{value, io_lib:write(V)}], []};
to_xml(#t_union{types = Ts}, Env) ->
- {union, map(fun wrap_type/2, Ts, Env)};
+ {union, map(fun wrap_utype/2, Ts, Env)};
to_xml(#t_record{name = N = #t_atom{}, fields = Fs}, Env) ->
{record, [to_xml(N, Env) | map(fun to_xml/2, Fs, Env)]};
to_xml(#t_field{name = N = #t_atom{}, type = T}, Env) ->
diff --git a/lib/edoc/src/edoc_types.hrl b/lib/edoc/src/edoc_types.hrl
index 1dcbdd9493..1353bfb93a 100644
--- a/lib/edoc/src/edoc_types.hrl
+++ b/lib/edoc/src/edoc_types.hrl
@@ -1,6 +1,6 @@
%% =====================================================================
%% Header file for EDoc Type Representations
-%%
+%%
%% Copyright (C) 2001-2005 Richard Carlsson
%%
%% This library is free software; you can redistribute it and/or modify
@@ -29,13 +29,15 @@
-record(t_spec, {name, type, defs=[]}). % function specification
-%% @type type() = t_atom() | t_fun() | t_integer() | t_list() | t_nil()
-%% | t_tuple() | t_type() | t_union() | t_var()
+%% @type type() = t_atom() | t_binary() | t_float() | t_fun() | t_integer()
+%% | t_integer_range() | t_list() | t_nil()| t_nonempty_list()
+%% | t_record() | t_tuple() | t_type() | t_union() | t_var()
+%% | t_paren()
%% @type t_typedef() = #t_typedef{name = t_name(),
%% args = [type()],
-%% type = type(),
-%% defs = [t_def()]}
+%% type = type() | undefined,
+%% defs = [t_def()]}.
-record(t_typedef, {name, args, type,
defs=[]}). % type declaration/definition
@@ -45,7 +47,7 @@
-record(t_throws, {type, defs=[]}). % exception declaration
-%% @type t_def() = #t_def{name = t_name(),
+%% @type t_def() = #t_def{name = t_type() | t_var(),
%% type = type()}
-record(t_def, {name, type}). % local definition 'name = type'
@@ -75,7 +77,9 @@
%% name = t_name(),
%% args = [type()]}
--record(t_type, {a=[], name, args = []}). % abstract type 'name(...)'
+-record(t_type, {a=[], % abstract type 'name(...)'
+ name,
+ args = []}).
%% @type t_union() = #t_union{a = list(),
%% types = [type()]}
@@ -102,6 +106,11 @@
-record(t_nil, {a=[]}). % empty-list constant '[]'
+%% @type t_nonempty_list() = #t_nonempty_list{a = list(),
+%% type = type()}
+
+-record(t_nonempty_list, {a=[], type}). % list type '[type, ...]'
+
%% @type t_atom() = #t_atom{a = list(),
%% val = atom()}
@@ -112,19 +121,37 @@
-record(t_integer, {a=[], val}). % integer constant
+%% @type t_integer_range() = #t_integer_range{a = list(),
+%% from = integer(),
+%% to = integer()}
+
+-record(t_integer_range, {a=[], from, to}).
+
+%% @type t_binary() = #t_binary{a = list(),
+%% base_size = integer(),
+%% unit_size = integer()}
+
+-record(t_binary, {a=[], base_size = 0, unit_size = 0}).
+
%% @type t_float() = #t_float{a = list(),
%% val = float()}
-record(t_float, {a=[], val}). % floating-point constant
%% @type t_record() = #t_list{a = list(),
-%% name = type(),
+%% name = t_atom(),
%% fields = [field()]}
--record(t_record, {a=[], name, fields = []}). % record type '#r{f1,...,fN}'
+-record(t_record, {a=[], % record "type" '#r{f1,...,fN}'
+ name,
+ fields = []}).
%% @type t_field() = #t_field{a = list(),
%% name = type(),
%% type = type()}
-record(t_field, {a=[], name, type}). % named field 'n1=t1'
+
+%% @type t_paren() = #t_paren{a = list(), type = type()}
+
+-record(t_paren, {a=[], type}). % parentheses
diff --git a/lib/kernel/src/application.erl b/lib/kernel/src/application.erl
index d9db23d652..2a193affd4 100644
--- a/lib/kernel/src/application.erl
+++ b/lib/kernel/src/application.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -204,12 +204,12 @@ get_env(Key) ->
get_env(Application, Key) ->
application_controller:get_env(Application, Key).
--spec get_all_env() -> [] | [{atom(), any()}].
+-spec get_all_env() -> [{atom(), any()}].
get_all_env() ->
application_controller:get_pid_all_env(group_leader()).
--spec get_all_env(atom()) -> [] | [{atom(), any()}].
+-spec get_all_env(atom()) -> [{atom(), any()}].
get_all_env(Application) ->
application_controller:get_all_env(Application).
diff --git a/lib/kernel/src/disk_log_1.erl b/lib/kernel/src/disk_log_1.erl
index 8ccdb88d12..266df84a03 100644
--- a/lib/kernel/src/disk_log_1.erl
+++ b/lib/kernel/src/disk_log_1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -581,11 +581,13 @@ done_scan(In, Out, OutName, FName, RecoveredTerms, BadChars) ->
file:delete(OutName),
throw(Error)
end.
-
+
+-spec repair_err(file:io_device(), #cache{}, file:filename(),
+ file:filename(), {'error', file:posix()}) -> no_return().
repair_err(In, Out, OutName, ErrFileName, Error) ->
file:close(In),
catch fclose(Out, OutName),
- % OutName is often the culprit, try to remove it anyway...
+ %% OutName is often the culprit, try to remove it anyway...
file:delete(OutName),
file_error(ErrFileName, Error).
diff --git a/lib/kernel/src/erl_ddll.erl b/lib/kernel/src/erl_ddll.erl
index 88f91de24f..ce64589a29 100644
--- a/lib/kernel/src/erl_ddll.erl
+++ b/lib/kernel/src/erl_ddll.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2011. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%% Dynamic Driver Loader and Linker
@@ -29,6 +29,11 @@
%%----------------------------------------------------------------------------
+-type path() :: string() | atom().
+-type driver() :: string() | atom().
+
+%%----------------------------------------------------------------------------
+
-spec start() -> {'error', {'already_started', 'undefined'}}.
start() ->
@@ -39,13 +44,13 @@ start() ->
stop() ->
ok.
--spec load_driver(Path :: string() | atom(), Driver :: string() | atom()) ->
+-spec load_driver(Path :: path(), Driver :: driver()) ->
'ok' | {'error', any()}.
load_driver(Path, Driver) ->
do_load_driver(Path, Driver, [{driver_options,[kill_ports]}]).
--spec load(Path :: string() | atom(), Driver :: string() | atom()) ->
+-spec load(Path :: path(), Driver :: driver()) ->
'ok' | {'error', any()}.
load(Path, Driver) ->
@@ -95,23 +100,23 @@ do_unload_driver(Driver,Flags) ->
end
end.
--spec unload_driver(Driver :: string() | atom()) -> 'ok' | {'error', any()}.
+-spec unload_driver(Driver :: driver()) -> 'ok' | {'error', any()}.
unload_driver(Driver) ->
do_unload_driver(Driver,[{monitor,pending_driver},kill_ports]).
--spec unload(Driver :: string() | atom()) -> 'ok' | {'error', any()}.
+-spec unload(Driver :: driver()) -> 'ok' | {'error', any()}.
unload(Driver) ->
do_unload_driver(Driver,[]).
--spec reload(Path :: string() | atom(), Driver :: string() | atom()) ->
+-spec reload(Path :: path(), Driver :: driver()) ->
'ok' | {'error', any()}.
reload(Path,Driver) ->
do_load_driver(Path, Driver, [{reload,pending_driver}]).
--spec reload_driver(Path :: string() | atom(), Driver :: string() | atom()) ->
+-spec reload_driver(Path :: path(), Driver :: driver()) ->
'ok' | {'error', any()}.
reload_driver(Path,Driver) ->
@@ -122,15 +127,15 @@ reload_driver(Path,Driver) ->
format_error(Code) ->
case Code of
- % This is the only error code returned only from erlang code...
- % 'permanent' has a translation in the emulator, even though the erlang code uses it to...
+ %% This is the only error code returned only from erlang code...
+ %% 'permanent' has a translation in the emulator, even though the erlang code uses it to...
load_cancelled ->
"Loading was cancelled from other process";
_ ->
erl_ddll:format_error_int(Code)
end.
--spec info(Driver :: string() | atom()) -> [{atom(), any()}].
+-spec info(Driver :: driver()) -> [{atom(), any()}, ...].
info(Driver) ->
[{processes, erl_ddll:info(Driver,processes)},
diff --git a/lib/kernel/src/error_handler.erl b/lib/kernel/src/error_handler.erl
index 885eeb2b0f..6f69f4ccb9 100644
--- a/lib/kernel/src/error_handler.erl
+++ b/lib/kernel/src/error_handler.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -80,9 +80,13 @@ int() -> int.
%%
%% Crash providing a beautiful stack backtrace.
%%
+-spec crash(atom(), [term()]) -> no_return().
+
crash(Fun, Args) ->
crash({Fun,Args}).
+-spec crash(atom(), atom(), arity()) -> no_return().
+
crash(M, F, A) ->
crash({M,F,A}).
diff --git a/lib/kernel/src/global.erl b/lib/kernel/src/global.erl
index 081e7e2f93..6343acd000 100644
--- a/lib/kernel/src/global.erl
+++ b/lib/kernel/src/global.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -113,8 +113,9 @@
syncers = [] :: [pid()],
node_name = node() :: node(),
the_locker, the_registrar, trace,
- global_lock_down = false
+ global_lock_down = false :: boolean()
}).
+-type state() :: #state{}.
%%% There are also ETS tables used for bookkeeping of locks and names
%%% (the first position is the key):
@@ -399,6 +400,9 @@ info() ->
%%%-----------------------------------------------------------------
%%% Call-back functions from gen_server
%%%-----------------------------------------------------------------
+
+-spec init([]) -> {'ok', state()}.
+
init([]) ->
process_flag(trap_exit, true),
_ = ets:new(global_locks, [set, named_table, protected]),
@@ -542,6 +546,11 @@ init([]) ->
%% sent by each node to all new nodes (Node becomes known to them)
%%-----------------------------------------------------------------
+-spec handle_call(term(), {pid(), term()}, state()) ->
+ {'noreply', state()} |
+ {'reply', term(), state()} |
+ {'stop', 'normal', 'stopped', state()}.
+
handle_call({whereis, Name}, From, S) ->
do_whereis(Name, From),
{noreply, S};
@@ -621,6 +630,9 @@ handle_call(Request, From, S) ->
%% init_connect
%%
%%========================================================================
+
+-spec handle_cast(term(), state()) -> {'noreply', state()}.
+
handle_cast({init_connect, Vsn, Node, InitMsg}, S) ->
%% Sent from global_name_server at Node.
?trace({'####', init_connect, {vsn, Vsn}, {node,Node},{initmsg,InitMsg}}),
@@ -782,6 +794,11 @@ handle_cast(Request, S) ->
"handle_cast(~p, _)\n", [Request]),
{noreply, S}.
+%%========================================================================
+
+-spec handle_info(term(), state()) ->
+ {'noreply', state()} | {'stop', term(), state()}.
+
handle_info({'EXIT', Locker, _Reason}=Exit, #state{the_locker=Locker}=S) ->
{stop, {locker_died,Exit}, S#state{the_locker=undefined}};
handle_info({'EXIT', Registrar, _}=Exit, #state{the_registrar=Registrar}=S) ->
@@ -1122,12 +1139,17 @@ do_whereis(Name, From) ->
send_again({whereis, Name, From})
end.
+-spec terminate(term(), state()) -> 'ok'.
+
terminate(_Reason, _S) ->
true = ets:delete(global_names),
true = ets:delete(global_names_ext),
true = ets:delete(global_locks),
true = ets:delete(global_pid_names),
- true = ets:delete(global_pid_ids).
+ true = ets:delete(global_pid_ids),
+ ok.
+
+-spec code_change(term(), state(), term()) -> {'ok', state()}.
code_change(_OldVsn, S, _Extra) ->
{ok, S}.
@@ -1955,7 +1977,7 @@ delete_lock(Ref, S0) ->
Locks = pid_locks(Ref),
F = fun({ResourceId, LockRequesterId, PidRefs}, S) ->
{Pid, _RPid, Ref} = lists:keyfind(Ref, 3, PidRefs),
- remove_lock(ResourceId, LockRequesterId, Pid, PidRefs, true,S)
+ remove_lock(ResourceId, LockRequesterId, Pid, PidRefs, true, S)
end,
lists:foldl(F, S0, Locks).
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl
index 95a2f71ec0..d1feae771d 100644
--- a/lib/kernel/src/os.erl
+++ b/lib/kernel/src/os.erl
@@ -137,7 +137,7 @@ reverse_element([$"|T]) -> %"
reverse_element(List) ->
lists:reverse(List).
--spec extensions() -> [string()].
+-spec extensions() -> [string(),...].
%% Extensions in lower case
extensions() ->
case type() of
diff --git a/lib/stdlib/doc/src/filelib.xml b/lib/stdlib/doc/src/filelib.xml
index 47d64f245c..e39ce914f7 100644
--- a/lib/stdlib/doc/src/filelib.xml
+++ b/lib/stdlib/doc/src/filelib.xml
@@ -44,7 +44,7 @@
<section>
<title>DATA TYPES</title>
<code type="none">
-filename() = = string() | atom() | DeepList | RawFilename
+filename() = string() | atom() | DeepList | RawFilename
DeepList = [char() | atom() | DeepList]
RawFilename = binary()
If VM is in unicode filename mode, string() and char() are allowed to be > 255.
diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml
index efbb1fc078..81fb5cad3d 100644
--- a/lib/stdlib/doc/src/io.xml
+++ b/lib/stdlib/doc/src/io.xml
@@ -464,9 +464,9 @@ ok</pre>
<p>Prints the argument with the <c>string</c> syntax. The
argument is, if no Unicode translation modifier is present, an
<seealso marker="erts:erlang#iolist_definition">I/O list</seealso>, a binary, or an atom. If the Unicode translation modifier ('t') is in effect, the argument is chardata(), meaning that binaries are in UTF-8. The characters
- are printed without quotes. In this format, the printed
- argument is truncated to the given precision and field
- width.</p>
+ are printed without quotes. The string is first truncated
+ by the given precision and then padded and justified
+ to the given field width. The default precision is the field width.</p>
<p>This format can be used for printing any object and
truncating the output so it fits a specified field:</p>
<pre>
@@ -475,6 +475,8 @@ ok</pre>
ok
4> <input>io:fwrite("|~10s|~n", [io_lib:write({hey, hey, hey})]).</input>
|{hey,hey,h|
+5> <input>io:fwrite("|~-10.8s|~n", [io_lib:write({hey, hey, hey})]).</input>
+|{hey,hey |
ok</pre>
<p>A list with integers larger than 255 is considered an error if the Unicode translation modifier is not given:</p>
<pre>
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index eb1885021d..49a00a4ec7 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -558,28 +558,30 @@ iolist_to_chars(B) when is_binary(B) ->
string(S, none, _Adj, none, _Pad) -> S;
string(S, F, Adj, none, Pad) ->
- N = lists:flatlength(S),
- if N > F -> flat_trunc(S, F);
- N =:= F -> S;
- true -> adjust(S, chars(Pad, F-N), Adj)
- end;
+ string_field(S, F, Adj, lists:flatlength(S), Pad);
string(S, none, _Adj, P, Pad) ->
+ string_field(S, P, left, lists:flatlength(S), Pad);
+string(S, F, Adj, P, Pad) when F >= P ->
N = lists:flatlength(S),
- if N > P -> flat_trunc(S, P);
- N =:= P -> S;
- true -> [S|chars(Pad, P-N)]
- end;
-string(S, F, Adj, F, Pad) ->
- string(S, none, Adj, F, Pad);
-string(S, F, Adj, P, Pad) when F > P ->
- N = lists:flatlength(S),
- if N > F -> flat_trunc(S, F);
- N =:= F -> S;
- N > P -> adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
- N =:= P -> adjust(S, chars(Pad, F-P), Adj);
- true -> adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj)
+ if F > P ->
+ if N > P ->
+ adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
+ N < P ->
+ adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj);
+ true -> % N == P
+ adjust(S, chars(Pad, F-P), Adj)
+ end;
+ true -> % F == P
+ string_field(S, F, Adj, N, Pad)
end.
+string_field(S, F, _Adj, N, _Pad) when N > F ->
+ flat_trunc(S, F);
+string_field(S, F, Adj, N, Pad) when N < F ->
+ adjust(S, chars(Pad, F-N), Adj);
+string_field(S, _, _, _, _) -> % N == F
+ S.
+
%% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase)
%% -> [Char].
@@ -624,8 +626,8 @@ newline(F, right, _P, _Pad) -> chars($\n, F).
%%
adjust(Data, [], _) -> Data;
-adjust(Data, Pad, left) -> [Data,Pad];
-adjust(Data, Pad, right) -> [Pad,Data].
+adjust(Data, Pad, left) -> [Data|Pad];
+adjust(Data, Pad, right) -> [Pad|Data].
%% Flatten and truncate a deep list to at most N elements.
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index 497fd3c562..54a98985cd 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2010. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -27,7 +27,7 @@
otp_6282/1, otp_6354/1, otp_6495/1, otp_6517/1, otp_6502/1,
manpage/1, otp_6708/1, otp_7084/1, otp_7421/1,
io_lib_collect_line_3_wb/1, cr_whitespace_in_string/1,
- io_fread_newlines/1]).
+ io_fread_newlines/1, otp_8989/1]).
%-define(debug, true).
@@ -62,7 +62,7 @@ all() ->
otp_6282, otp_6354, otp_6495, otp_6517, otp_6502,
manpage, otp_6708, otp_7084, otp_7421,
io_lib_collect_line_3_wb, cr_whitespace_in_string,
- io_fread_newlines].
+ io_fread_newlines, otp_8989].
groups() ->
[].
@@ -1917,3 +1917,81 @@ read_newlines(Fd, Acc, N0) ->
eof ->
{lists:reverse(Acc),N0}
end.
+
+
+
+otp_8989(doc) ->
+ "OTP-8989 io:format for ~F.Ps ignores P in some cases";
+otp_8989(Suite) when is_list(Suite) ->
+ Hello = "Hello",
+ ?line " Hello" = fmt("~6.6s", [Hello]),
+ ?line " Hello" = fmt("~*.6s", [6,Hello]),
+ ?line " Hello" = fmt("~6.*s", [6,Hello]),
+ ?line " Hello" = fmt("~*.*s", [6,6,Hello]),
+ %%
+ ?line " Hello" = fmt("~6.5s", [Hello]),
+ ?line " Hello" = fmt("~*.5s", [6,Hello]),
+ ?line " Hello" = fmt("~6.*s", [5,Hello]),
+ ?line " Hello" = fmt("~*.*s", [6,5,Hello]),
+ %%
+ ?line " Hell" = fmt("~6.4s", [Hello]),
+ ?line " Hell" = fmt("~*.4s", [6,Hello]),
+ ?line " Hell" = fmt("~6.*s", [4,Hello]),
+ ?line " Hell" = fmt("~*.*s", [6,4,Hello]),
+ %%
+ ?line "Hello" = fmt("~5.5s", [Hello]),
+ ?line "Hello" = fmt("~*.5s", [5,Hello]),
+ ?line "Hello" = fmt("~5.*s", [5,Hello]),
+ ?line "Hello" = fmt("~*.*s", [5,5,Hello]),
+ %%
+ ?line " Hell" = fmt("~5.4s", [Hello]),
+ ?line " Hell" = fmt("~*.4s", [5,Hello]),
+ ?line " Hell" = fmt("~5.*s", [4,Hello]),
+ ?line " Hell" = fmt("~*.*s", [5,4,Hello]),
+ %%
+ ?line "Hell" = fmt("~4.4s", [Hello]),
+ ?line "Hell" = fmt("~*.4s", [4,Hello]),
+ ?line "Hell" = fmt("~4.*s", [4,Hello]),
+ ?line "Hell" = fmt("~*.*s", [4,4,Hello]),
+ %%
+ ?line " Hel" = fmt("~4.3s", [Hello]),
+ ?line " Hel" = fmt("~*.3s", [4,Hello]),
+ ?line " Hel" = fmt("~4.*s", [3,Hello]),
+ ?line " Hel" = fmt("~*.*s", [4,3,Hello]),
+ %%
+ %%
+ ?line "Hello " = fmt("~-6.6s", [Hello]),
+ ?line "Hello " = fmt("~*.6s", [-6,Hello]),
+ ?line "Hello " = fmt("~-6.*s", [6,Hello]),
+ ?line "Hello " = fmt("~*.*s", [-6,6,Hello]),
+ %%
+ ?line "Hello " = fmt("~-6.5s", [Hello]),
+ ?line "Hello " = fmt("~*.5s", [-6,Hello]),
+ ?line "Hello " = fmt("~-6.*s", [5,Hello]),
+ ?line "Hello " = fmt("~*.*s", [-6,5,Hello]),
+ %%
+ ?line "Hell " = fmt("~-6.4s", [Hello]),
+ ?line "Hell " = fmt("~*.4s", [-6,Hello]),
+ ?line "Hell " = fmt("~-6.*s", [4,Hello]),
+ ?line "Hell " = fmt("~*.*s", [-6,4,Hello]),
+ %%
+ ?line "Hello" = fmt("~-5.5s", [Hello]),
+ ?line "Hello" = fmt("~*.5s", [-5,Hello]),
+ ?line "Hello" = fmt("~-5.*s", [5,Hello]),
+ ?line "Hello" = fmt("~*.*s", [-5,5,Hello]),
+ %%
+ ?line "Hell " = fmt("~-5.4s", [Hello]),
+ ?line "Hell " = fmt("~*.4s", [-5,Hello]),
+ ?line "Hell " = fmt("~-5.*s", [4,Hello]),
+ ?line "Hell " = fmt("~*.*s", [-5,4,Hello]),
+ %%
+ ?line "Hell" = fmt("~-4.4s", [Hello]),
+ ?line "Hell" = fmt("~*.4s", [-4,Hello]),
+ ?line "Hell" = fmt("~-4.*s", [4,Hello]),
+ ?line "Hell" = fmt("~*.*s", [-4,4,Hello]),
+ %%
+ ?line "Hel " = fmt("~-4.3s", [Hello]),
+ ?line "Hel " = fmt("~*.3s", [-4,Hello]),
+ ?line "Hel " = fmt("~-4.*s", [3,Hello]),
+ ?line "Hel " = fmt("~*.*s", [-4,3,Hello]),
+ ok.