aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/common_test/doc/src/ct.xml2
-rw-r--r--lib/common_test/src/ct_logs.erl5
-rw-r--r--lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl68
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE.erl32
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl9
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl15
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ns.erl2
-rw-r--r--lib/common_test/test/ct_test_support.erl6
-rw-r--r--lib/common_test/test/telnet_server.erl42
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl161
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl13
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl122
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/exact2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/guard_update2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/map_in_guard22
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/typeflow2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/typesig4
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/maps12
-rw-r--r--lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl11
-rw-r--r--lib/diameter/src/base/diameter_config.erl125
-rw-r--r--lib/diameter/src/base/diameter_config_sup.erl58
-rw-r--r--lib/diameter/src/base/diameter_lib.erl33
-rw-r--r--lib/diameter/src/base/diameter_reg.erl423
-rw-r--r--lib/diameter/src/base/diameter_service.erl49
-rw-r--r--lib/diameter/src/base/diameter_session.erl5
-rw-r--r--lib/diameter/src/base/diameter_sup.erl3
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl4
-rw-r--r--lib/diameter/src/modules.mk3
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl37
-rw-r--r--lib/diameter/src/transport/diameter_tcp.erl31
-rw-r--r--lib/diameter/test/diameter_codec_test.erl13
-rw-r--r--lib/diameter/test/diameter_gen_sctp_SUITE.erl6
-rw-r--r--lib/diameter/test/diameter_reg_SUITE.erl19
-rw-r--r--lib/diameter/test/diameter_relay_SUITE.erl12
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl6
-rw-r--r--lib/diameter/test/diameter_transport_SUITE.erl9
-rw-r--r--lib/diameter/test/diameter_util.erl24
-rw-r--r--lib/edoc/src/edoc_layout.erl16
-rw-r--r--lib/edoc/src/edoc_parser.yrl6
-rw-r--r--lib/erl_docgen/src/docgen_otp_specs.erl16
-rw-r--r--lib/hipe/cerl/erl_types.erl904
-rw-r--r--lib/inets/test/ftp_SUITE.erl63
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap2.xmlsrc2
-rw-r--r--lib/stdlib/doc/src/erl_id_trans.xml3
-rw-r--r--lib/stdlib/doc/src/erl_parse.xml8
-rw-r--r--lib/stdlib/src/erl_expand_records.erl5
-rw-r--r--lib/stdlib/src/erl_lint.erl8
-rw-r--r--lib/stdlib/src/erl_parse.yrl39
-rw-r--r--lib/stdlib/src/erl_pp.erl24
-rw-r--r--lib/stdlib/src/lib.erl2
-rw-r--r--lib/stdlib/src/ms_transform.erl7
-rw-r--r--lib/stdlib/src/proplists.erl5
-rw-r--r--lib/stdlib/src/qlc.erl7
-rw-r--r--lib/stdlib/src/qlc_pt.erl18
-rw-r--r--lib/stdlib/src/sofs.erl3
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl5
-rw-r--r--lib/syntax_tools/src/Makefile2
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl39
-rw-r--r--lib/syntax_tools/src/erl_recomment.erl8
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl9
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl9
-rw-r--r--lib/tools/src/xref_base.erl215
-rw-r--r--lib/tools/src/xref_utils.erl8
63 files changed, 1646 insertions, 1147 deletions
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index 5231ef24a4..264bcff251 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -935,7 +935,7 @@
</func>
<func>
- <name>reload_config(Required) -&gt; ValueOrElement</name>
+ <name>reload_config(Required) -&gt; ValueOrElement | {error, Reason}</name>
<fsummary>Reloads configuration file containing specified configuration
key.</fsummary>
<type>
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 455864efb6..53245c596a 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -137,7 +137,8 @@ close(Info, StartDir) ->
%% so we need to use a local copy of the log cache data
LogCacheBin =
case make_last_run_index() of
- {error,_} -> % log server not responding
+ {error, Reason} -> % log server not responding
+ io:format("Warning! ct_logs not responding: ~p~n", [Reason]),
undefined;
LCB ->
LCB
@@ -240,7 +241,7 @@ call(Msg) ->
Pid ->
MRef = erlang:monitor(process,Pid),
Ref = make_ref(),
- ?MODULE ! {Msg,{self(),Ref}},
+ Pid ! {Msg,{self(),Ref}},
receive
{Ref, Result} ->
erlang:demonitor(MRef, [flush]),
diff --git a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
index 1404df6410..19eb1211fa 100644
--- a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
+++ b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
@@ -73,24 +73,28 @@ handles_to_multi_conn_pids(_Config) ->
ConnPid3 = ct_gen_conn:get_conn_pid(Handle3),
{true,true} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
+ monitor_procs([Handle1,ConnPid1,Handle2,ConnPid2,Handle3,ConnPid3]),
+
ok = proto:close(Handle1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1,ConnPid1]),
{false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)},
{true,true} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid2]),
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ConnPid2x = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(ConnPid2x),
+ monitor_procs([ConnPid2x]),
+
ok = proto:close(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2,ConnPid2x]),
{false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,ConnPid3]),
{false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
ok.
@@ -116,24 +120,28 @@ handles_to_single_conn_pids(_Config) ->
{undefined,Handle3,_,_}] = lists:sort(ct_util:get_connections(ConnPid)),
ct:pal("CONNS = ~n~p", [Conns]),
+ monitor_procs([Handle1,Handle2,Handle3,ConnPid]),
ok = proto:close(Handle1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1]),
{false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid]),
NewConnPid = ct_gen_conn:get_conn_pid(Handle2),
NewConnPid = ct_gen_conn:get_conn_pid(Handle3),
true = is_process_alive(Handle2),
true = is_process_alive(Handle3),
+ false = is_process_alive(ConnPid),
+
+ monitor_procs([NewConnPid]),
ok = proto:close(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2]),
{false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,NewConnPid]),
{false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)},
ok.
@@ -158,30 +166,37 @@ names_to_multi_conn_pids(_Config) ->
Handle1 = proto:open(mconn1),
+ monitor_procs([Handle1,ConnPid1,Handle2,ConnPid2,Handle3,ConnPid3]),
+
ok = proto:close(mconn1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1,ConnPid1]),
{false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid2]),
Handle2 = proto:open(mconn2), % should've been reconnected already
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ConnPid2x = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(ConnPid2x),
+ monitor_procs([ConnPid2x]),
+
ok = proto:close(mconn2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2,ConnPid2x]),
{false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)},
Handle2y = proto:open(mconn2),
ConnPid2y = ct_gen_conn:get_conn_pid(Handle2y),
{true,true} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)},
+
+ monitor_procs([Handle2y,ConnPid2y]),
+
ok = proto:close(mconn2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2y,ConnPid2y]),
{false,false} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,ConnPid3]),
{false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
ok.
@@ -211,16 +226,20 @@ names_to_single_conn_pids(_Config) ->
{sconn3,Handle3,_,_}] = lists:sort(ct_util:get_connections(ConnPid)),
ct:pal("CONNS on ~p = ~n~p", [ConnPid,Conns]),
+ monitor_procs([Handle1,Handle2,Handle3,ConnPid]),
+
ok = proto:close(sconn1),
- ct:sleep(100),
+ ok = wait_procs_down([Handle1]),
{false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)},
ok = proto:kill_conn_proc(Handle2),
- ct:sleep(100),
+ ok = wait_procs_down([ConnPid]),
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid)},
Handle2 = proto:open(sconn2), % should've been reconnected already
NewConnPid = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(NewConnPid),
+
+ monitor_procs([NewConnPid]),
Conns1 = [{sconn2,Handle2,_,_},
{sconn3,Handle3,_,_}] =
@@ -228,14 +247,29 @@ names_to_single_conn_pids(_Config) ->
ct:pal("CONNS on ~p = ~n~p", [NewConnPid,Conns1]),
ok = proto:close(sconn2),
- ct:sleep(100),
+ ok = wait_procs_down([Handle2]),
{false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- ct:sleep(100),
+ ok = wait_procs_down([Handle3,NewConnPid]),
{false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)},
ok.
+%%%-----------------------------------------------------------------
+monitor_procs(Pids) ->
+ [erlang:monitor(process,Pid) || Pid <- Pids],
+ ok.
+
+wait_procs_down([]) ->
+ ok;
+wait_procs_down(Pids) ->
+ receive
+ {'DOWN',_,process,Pid,_} ->
+ wait_procs_down(lists:delete(Pid,Pids))
+ after 2000 ->
+ timeout
+ end.
+
diff --git a/lib/common_test/test/ct_netconfc_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE.erl
index 03fbc17bd2..2919f01605 100644
--- a/lib/common_test/test/ct_netconfc_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE.erl
@@ -44,16 +44,28 @@
%% there will be clashes with logging processes etc).
%%--------------------------------------------------------------------
init_per_suite(Config) ->
- case application:load(crypto) of
- {error,Reason} when Reason=/={already_loaded,crypto} ->
- {skip, Reason};
- _ ->
- case application:load(ssh) of
- {error,Reason} when Reason=/={already_loaded,ssh} ->
- {skip, Reason};
- _ ->
- ct_test_support:init_per_suite(Config)
- end
+ case check_crypto_and_ssh() of
+ ok ->
+ ct_test_support:init_per_suite(Config);
+ Skip ->
+ Skip
+ end.
+
+check_crypto_and_ssh() ->
+ (catch code:load_file(crypto)),
+ case code:is_loaded(crypto) of
+ {file,_} ->
+ case ssh:start() of
+ Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ ct:log("ssh started",[]),
+ ok;
+ Other ->
+ ct:log("could not start ssh: ~p",[Other]),
+ {skip, "SSH could not be started!"}
+ end;
+ Other ->
+ ct:log("could not load crypto: ~p",[Other]),
+ {skip, "crypto could not be loaded!"}
end.
end_per_suite(Config) ->
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
index f34969683c..2aa6c4d354 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
@@ -124,8 +124,9 @@ end_per_testcase(_Case, _Config) ->
ok.
init_per_suite(Config) ->
- case catch ssh:start() of
- Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ (catch code:load_file(crypto)),
+ case {ssh:start(),code:is_loaded(crypto)} of
+ {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} ->
ct:log("ssh started",[]),
SshDir = filename:join(filename:dirname(code:which(?MODULE)),
"ssh_dir"),
@@ -133,7 +134,7 @@ init_per_suite(Config) ->
ct:log("netconf server started",[]),
[{netconf_server,Server},{ssh_dir,SshDir}|Config];
Other ->
- ct:log("could not start ssh: ~p",[Other]),
+ ct:log("could not start ssh or load crypto: ~p",[Other]),
{skip, "SSH could not be started!"}
end.
@@ -360,7 +361,7 @@ get(Config) ->
get_a_lot(Config) ->
SshDir = ?config(ssh_dir,Config),
{ok,Client} = open_success(SshDir),
- Descr = lists:append(lists:duplicate(1000,"Description of myserver! ")),
+ Descr = lists:append(lists:duplicate(100,"Description of myserver! ")),
Server = {server,[{xmlns,"myns"}],[{name,[],["myserver"]},
{description,[],[Descr]}]},
Data = lists:duplicate(100,Server),
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl
index 0a49cdabbb..a65275da43 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl
@@ -62,14 +62,15 @@ stop_node(Case) ->
init_per_suite(Config) ->
- case ssh:start() of
- Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ (catch code:load_file(crypto)),
+ case {ssh:start(),code:is_loaded(crypto)} of
+ {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} ->
ct:log("SSH started locally",[]),
SshDir = filename:join(filename:dirname(code:which(?MODULE)),
"ssh_dir"),
[{ssh_dir,SshDir}|Config];
Other ->
- ct:log("could not start ssh locally: ~p",[Other]),
+ ct:log("could not start ssh or load crypto locally: ~p",[Other]),
{skip, "SSH could not be started locally!"}
end.
@@ -85,15 +86,15 @@ remote_crash(Config) ->
{ok,Node} = ct_slave:start(nc_remote_crash),
Pa = filename:dirname(code:which(?NS)),
true = rpc:call(Node,code,add_patha,[Pa]),
-
- case rpc:call(Node,ssh,start,[]) of
- Ok when Ok==ok; Ok=={error,{already_started,ssh}} ->
+ rpc:call(Node,code,load_file,[crypto]),
+ case {rpc:call(Node,ssh,start,[]),rpc:call(Node,code,is_loaded,[crypto])} of
+ {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} ->
ct:log("SSH started remote",[]),
ns(Node,start,[?config(ssh_dir,Config)]),
ct:log("netconf server started remote",[]),
remote_crash(Node,Config);
Other ->
- ct:log("could not start ssh remote: ~p",[Other]),
+ ct:log("could not start ssh or load crypto remote: ~p",[Other]),
{skip, "SSH could not be started remote!"}
end.
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
index 9fb1fb6547..e62bc617fa 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
@@ -279,7 +279,7 @@ send({CM,Ch},Data) ->
%%% Split into many small parts and send to client
send_frag({CM,Ch},Data) ->
- Sz = rand:uniform(2000),
+ Sz = rand:uniform(1000),
case Data of
<<Chunk:Sz/binary,Rest/binary>> ->
ssh_connection:send(CM, Ch, Chunk),
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 477fcb8a26..e926abd885 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -484,7 +484,8 @@ get_events(_, Config) ->
{event_receiver,CTNode} ! {self(),get_events},
Events = receive {event_receiver,Evs} -> Evs end,
test_server:format(Level, "Stopping event receiver!~n", []),
- {event_receiver,CTNode} ! stop,
+ {event_receiver,CTNode} ! {self(),stop},
+ receive {event_receiver,stopped} -> ok end,
Events.
er() ->
@@ -499,8 +500,9 @@ er_loop(Evs) ->
{From,get_events} ->
From ! {event_receiver,lists:reverse(Evs)},
er_loop(Evs);
- stop ->
+ {From,stop} ->
unregister(event_receiver),
+ From ! {event_receiver,stopped},
ok
end.
diff --git a/lib/common_test/test/telnet_server.erl b/lib/common_test/test/telnet_server.erl
index 2c33cb268a..65300b0bdf 100644
--- a/lib/common_test/test/telnet_server.erl
+++ b/lib/common_test/test/telnet_server.erl
@@ -117,38 +117,64 @@ init_client(#state{client=Sock}=State) ->
dbg("Server sending: ~p~n",["login: "]),
R = case gen_tcp:send(Sock,"login: ") of
ok ->
- loop(State, 1);
+ loop(State);
Error ->
Error
end,
_ = gen_tcp:close(Sock),
R.
-loop(State, N) ->
+loop(State=#state{client=Sock}) ->
receive
- {tcp,_,Data} ->
+ {tcp,Sock,Data} ->
try handle_data(Data,State) of
{ok,State1} ->
- loop(State1, N);
+ loop(State1);
closed ->
+ _ = flush(State),
closed
catch
throw:Error ->
+ _ = flush(State),
Error
end;
- {tcp_closed, _} ->
+ {tcp_closed,Sock} ->
closed;
- {tcp_error,_,Error} ->
+ {tcp_error,Sock,Error} ->
{error,tcp,Error};
disconnect ->
- Sock = State#state.client,
dbg("Server closing connection on socket ~p~n", [Sock]),
+ timer:sleep(1000),
ok = gen_tcp:close(Sock),
- closed;
+ _ = flush(State);
stop ->
+ _ = flush(State),
stopped
end.
+flush(State=#state{client=Sock}) ->
+ receive
+ {tcp,Sock,Data} = M->
+ dbg("Message flushed after close or error: ~p~n", [M]),
+ try handle_data(Data,State) of
+ {ok,State1} ->
+ flush(State1);
+ closed ->
+ flush(State)
+ catch
+ throw:Error ->
+ Error
+ end;
+ {tcp_closed,Sock} = M ->
+ dbg("Message flushed after close or error: ~p~n", [M]),
+ ok;
+ {tcp_error,Sock,Error} = M ->
+ dbg("Message flushed after close or error: ~p~n", [M]),
+ {error,tcp,Error}
+ after 100 ->
+ ok
+ end.
+
handle_data(Cmd,#state{break=true}=State) ->
dbg("Server got data when in break mode: ~p~n",[Cmd]),
handle_break_cmd(Cmd,State);
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 272ad10e90..976a2b8955 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -53,7 +53,9 @@
%% to expand records and/or remote types that they might contain.
%%-----------------------------------------------------------------------
--type tmp_contract_fun() :: fun((sets:set(mfa()), types()) -> contract_pair()).
+-type cache() :: ets:tid().
+-type tmp_contract_fun() ::
+ fun((sets:set(mfa()), types(), cache()) -> contract_pair()).
-record(tmp_contract, {contract_funs = [] :: [tmp_contract_fun()],
forms = [] :: [{_, _}]}).
@@ -153,19 +155,30 @@ process_contract_remote_types(CodeServer) ->
ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer),
RecordDict = dialyzer_codeserver:get_records(CodeServer),
ContractFun =
- fun({_M, _F, _A}, {File, #tmp_contract{contract_funs = CFuns, forms = Forms}, Xtra}) ->
- NewCs = [CFun(ExpTypes, RecordDict) || CFun <- CFuns],
- Args = general_domain(NewCs),
- {File, #contract{contracts = NewCs, args = Args, forms = Forms}, Xtra}
+ fun({{_M, _F, _A}=MFA, {File, TmpContract, Xtra}}, C0) ->
+ #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract,
+ {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) ->
+ CFun(ExpTypes, RecordDict, C1)
+ end, C0, CFuns),
+ Args = general_domain(NewCs),
+ Contract = #contract{contracts = NewCs, args = Args, forms = Forms},
+ {{MFA, {File, Contract, Xtra}}, C2}
end,
ModuleFun =
- fun(_ModuleName, ContractDict) ->
- dict:map(ContractFun, ContractDict)
+ fun({ModuleName, ContractDict}, C3) ->
+ {NewContractList, C4} =
+ lists:mapfoldl(ContractFun, C3, dict:to_list(ContractDict)),
+ {{ModuleName, dict:from_list(NewContractList)}, C4}
end,
- NewContractDict = dict:map(ModuleFun, TmpContractDict),
- NewCallbackDict = dict:map(ModuleFun, TmpCallbackDict),
+ Cache = erl_types:cache__new(),
+ {NewContractList, C5} =
+ lists:mapfoldl(ModuleFun, Cache, dict:to_list(TmpContractDict)),
+ {NewCallbackList, _C6} =
+ lists:mapfoldl(ModuleFun, C5, dict:to_list(TmpCallbackDict)),
+ NewContractDict = dict:from_list(NewContractList),
+ NewCallbackDict = dict:from_list(NewCallbackList),
dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict,
- CodeServer).
+ CodeServer).
-type opaques_fun() :: fun((module()) -> [erl_types:erl_type()]).
@@ -431,19 +444,19 @@ contract_from_form(Forms, MFA, RecDict, FileLine) ->
contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict,
FileLine, TypeAcc, FormAcc) ->
TypeFun =
- fun(ExpTypes, AllRecords) ->
- NewType =
+ fun(ExpTypes, AllRecords, Cache) ->
+ {NewType, NewCache} =
try
- from_form_with_check(Form, ExpTypes, MFA, AllRecords)
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache)
catch
throw:{error, Msg} ->
{File, Line} = FileLine,
NewMsg = io_lib:format("~s:~p: ~s", [filename:basename(File),
- Line, Msg]),
+ Line, Msg]),
throw({error, NewMsg})
end,
NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
- {NewTypeNoVars, []}
+ {{NewTypeNoVars, []}, NewCache}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, []} | FormAcc],
@@ -452,13 +465,15 @@ contract_from_form([{type, _L1, bounded_fun,
[{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left],
MFA, RecDict, FileLine, TypeAcc, FormAcc) ->
TypeFun =
- fun(ExpTypes, AllRecords) ->
- {Constr1, VarDict} =
- process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords),
- NewType = from_form_with_check(Form, ExpTypes, MFA, AllRecords,
- VarDict),
+ fun(ExpTypes, AllRecords, Cache) ->
+ {Constr1, VarTable, Cache1} =
+ process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords,
+ Cache),
+ {NewType, NewCache} =
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords,
+ VarTable, Cache1),
NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
- {NewTypeNoVars, Constr1}
+ {{NewTypeNoVars, Constr1}, NewCache}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, Constr} | FormAcc],
@@ -466,74 +481,91 @@ contract_from_form([{type, _L1, bounded_fun,
contract_from_form([], _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) ->
{lists:reverse(TypeAcc), lists:reverse(FormAcc)}.
-process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords) ->
- Init0 = initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords),
+process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
+ {Init0, NewCache} = initialize_constraints(Constrs, MFA, RecDict, ExpTypes,
+ AllRecords, Cache),
Init = remove_cycles(Init0),
- constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords).
+ constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords, NewCache).
-initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords) ->
- initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, []).
+initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
+ initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ Cache, []).
-initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords, Acc) ->
- Acc;
-initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords, Acc) ->
+initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords,
+ Cache, Acc) ->
+ {Acc, Cache};
+initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords,
+ Cache, Acc) ->
case Constr of
{type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} ->
VarTable = erl_types:var_table__new(),
- T1 = final_form(Type1, ExpTypes, MFA, AllRecords, VarTable),
+ {T1, NewCache} =
+ final_form(Type1, ExpTypes, MFA, AllRecords, VarTable, Cache),
Entry = {T1, Type2},
- initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords, [Entry|Acc]);
+ initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords,
+ NewCache, [Entry|Acc]);
{type, _, constraint, [{atom,_,Name}, List]} ->
N = length(List),
throw({error,
io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])})
end.
-constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords) ->
+constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
VarTable = erl_types:var_table__new(),
- VarDict =
- constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarTable),
- constraints_fixpoint(VarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords).
-
-constraints_fixpoint(OldVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords) ->
- NewVarDict =
- constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, OldVarDict),
- case NewVarDict of
- OldVarDict ->
+ {VarTab, NewCache} =
+ constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTable, Cache),
+ constraints_fixpoint(VarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, NewCache).
+
+constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, Cache) ->
+ {NewVarTab, NewCache} =
+ constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ OldVarTab, Cache),
+ case NewVarTab of
+ OldVarTab ->
Fun =
fun(Key, Value, Acc) ->
[{subtype, erl_types:t_var(Key), Value}|Acc]
end,
- FinalConstrs = maps:fold(Fun, [], NewVarDict),
- {FinalConstrs, NewVarDict};
+ FinalConstrs = maps:fold(Fun, [], NewVarTab),
+ {FinalConstrs, NewVarTab, NewCache};
_Other ->
- constraints_fixpoint(NewVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords)
+ constraints_fixpoint(NewVarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, NewCache)
end.
-final_form(Form, ExpTypes, MFA, AllRecords, VarDict) ->
- from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict).
+final_form(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) ->
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache).
-from_form_with_check(Form, ExpTypes, MFA, AllRecords) ->
+from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache) ->
VarTable = erl_types:var_table__new(),
- from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable).
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache).
-from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict) ->
+from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) ->
Site = {spec, MFA},
- erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords, VarDict),
- erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarDict).
-
-constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict) ->
- Subtypes =
- constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict, []),
- insert_constraints(Subtypes).
-
-constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords, _VarDict, Acc) ->
- Acc;
-constraints_to_subs([C|Rest], MFA, RecDict, ExpTypes, AllRecords, VarDict, Acc) ->
- {T1, Form2} = C,
- T2 = final_form(Form2, ExpTypes, MFA, AllRecords, VarDict),
+ C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords,
+ VarTable, Cache),
+ erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarTable, C1).
+
+constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache) ->
+ {Subtypes, NewCache} =
+ constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache, []),
+ {insert_constraints(Subtypes), NewCache}.
+
+constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords,
+ _VarTab, Cache, Acc) ->
+ {Acc, Cache};
+constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache, Acc) ->
+ {T2, NewCache} =
+ final_form(Form2, ExpTypes, MFA, AllRecords, VarTab, Cache),
NewAcc = [{subtype, T1, T2}|Acc],
- constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords, VarDict, NewAcc).
+ constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, NewCache, NewAcc).
%% Replaces variables with '_' when necessary to break up cycles among
%% the constraints.
@@ -647,6 +679,7 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract, _Xtra}}|Left],
{value, {Ret, Args}} ->
Sig = erl_types:t_fun(Args, Ret),
{M, _F, _A} = MFA,
+ %% io:format("MFA ~p~n", [MFA]),
Opaques = FindOpaques(M),
{File, Line} = FileLine,
WarningInfo = {File, Line, MFA},
@@ -795,7 +828,7 @@ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) ->
t_from_forms_without_remote([{FType, []}], MFA, RecDict) ->
Site = {spec, MFA},
- Type1 = erl_types:t_from_form_without_remote(FType, Site, RecDict),
+ {Type1, _} = erl_types:t_from_form_without_remote(FType, Site, RecDict),
{ok, erl_types:subst_all_vars_to_any(Type1)};
t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) ->
%% 'When' constraints
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 3349b12932..9399789464 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -522,7 +522,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]),
?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]),
?debug("SigRange: ~s\n", [erl_types:t_to_string(SigRange)]),
- ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(NewArgTypes))]),
+ ?debug("ContrRet: ~s\n", [erl_types:t_to_string(ContrRet)]),
?debug("LocalRet: ~s\n", [erl_types:t_to_string(LocalRet)]),
State1 =
@@ -2954,11 +2954,15 @@ is_call_to_send(Tree) ->
Arity = cerl:call_arity(Tree),
cerl:is_c_atom(Mod)
andalso cerl:is_c_atom(Name)
- andalso (cerl:atom_val(Name) =:= '!')
+ andalso is_send(cerl:atom_val(Name))
andalso (cerl:atom_val(Mod) =:= erlang)
andalso (Arity =:= 2)
end.
+is_send('!') -> true;
+is_send(send) -> true;
+is_send(_) -> false.
+
is_lc_simple_list(Tree, TreeType, State) ->
Opaques = State#state.opaques,
Ann = cerl:get_ann(Tree),
@@ -3067,7 +3071,10 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State,
false ->
WarningInfo = {get_file(Ann), get_line(Ann), State#state.curr_fun},
Warn = {Tag, WarningInfo, Msg},
- ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]),
+ case Tag of
+ ?WARN_CONTRACT_RANGE -> ok;
+ _ -> ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)])
+ end,
State#state{warnings = [Warn|Warnings]}
end
end.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index d37701f03b..76a5cf3d0b 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -194,15 +194,18 @@ get_core_from_abstract_code(AbstrCode, Opts) ->
%%
%% ============================================================================
+-type type_table() :: erl_types:type_table().
+-type mod_records() :: dict:dict(module(), type_table()).
+
-spec get_record_and_type_info(abstract_code()) ->
- {'ok', dict:dict()} | {'error', string()}.
+ {'ok', type_table()} | {'error', string()}.
get_record_and_type_info(AbstractCode) ->
Module = get_module(AbstractCode),
get_record_and_type_info(AbstractCode, Module, dict:new()).
--spec get_record_and_type_info(abstract_code(), module(), dict:dict()) ->
- {'ok', dict:dict()} | {'error', string()}.
+-spec get_record_and_type_info(abstract_code(), module(), type_table()) ->
+ {'ok', type_table()} | {'error', string()}.
get_record_and_type_info(AbstractCode, Module, RecDict) ->
get_record_and_type_info(AbstractCode, Module, RecDict, "nofile").
@@ -299,92 +302,117 @@ get_record_fields([], _RecDict, Acc) ->
process_record_remote_types(CServer) ->
TempRecords = dialyzer_codeserver:get_temp_records(CServer),
ExpTypes = dialyzer_codeserver:get_exported_types(CServer),
- TempRecords1 = process_opaque_types0(TempRecords, ExpTypes),
+ Cache = erl_types:cache__new(),
+ {TempRecords1, Cache1} =
+ process_opaque_types0(TempRecords, ExpTypes, Cache),
+ %% A cache (not the field type cache) is used for speeding things up a bit.
+ VarTable = erl_types:var_table__new(),
ModuleFun =
- fun(Module, Record) ->
+ fun({Module, Record}, C0) ->
RecordFun =
- fun(Key, Value) ->
+ fun({Key, Value}, C2) ->
case Key of
{record, Name} ->
FieldFun =
- fun(Arity, Fields) ->
+ fun({Arity, Fields}, C4) ->
Site = {record, {Module, Name, Arity}},
- [{FieldName, Field,
- erl_types:t_from_form(Field,
- ExpTypes,
- Site,
- TempRecords1)}
- || {FieldName, Field, _} <- Fields]
+ {Fields1, C7} =
+ lists:mapfoldl(fun({FieldName, Field, _}, C5) ->
+ {FieldT, C6} =
+ erl_types:t_from_form
+ (Field, ExpTypes, Site,
+ TempRecords1, VarTable,
+ C5),
+ {{FieldName, Field, FieldT}, C6}
+ end, C4, Fields),
+ {{Arity, Fields1}, C7}
end,
{FileLine, Fields} = Value,
- {FileLine, orddict:map(FieldFun, Fields)};
- _Other -> Value
+ {FieldsList, C3} =
+ lists:mapfoldl(FieldFun, C2, orddict:to_list(Fields)),
+ {{Key, {FileLine, orddict:from_list(FieldsList)}}, C3};
+ _Other -> {{Key, Value}, C2}
end
end,
- dict:map(RecordFun, Record)
+ {RecordList, C1} =
+ lists:mapfoldl(RecordFun, C0, dict:to_list(Record)),
+ {{Module, dict:from_list(RecordList)}, C1}
end,
- NewRecords = dict:map(ModuleFun, TempRecords1),
- ok = check_record_fields(NewRecords, ExpTypes),
+ {NewRecordsList, C1} =
+ lists:mapfoldl(ModuleFun, Cache1, dict:to_list(TempRecords1)),
+ NewRecords = dict:from_list(NewRecordsList),
+ _C8 = check_record_fields(NewRecords, ExpTypes, C1),
dialyzer_codeserver:finalize_records(NewRecords, CServer).
%% erl_types:t_from_form() substitutes the declaration of opaque types
%% for the expanded type in some cases. To make sure the initial type,
%% any(), is not used, the expansion is done twice.
%% XXX: Recursive opaque types are not handled well.
-process_opaque_types0(TempRecords0, TempExpTypes) ->
- TempRecords1 = process_opaque_types(TempRecords0, TempExpTypes),
- process_opaque_types(TempRecords1, TempExpTypes).
+process_opaque_types0(TempRecords0, TempExpTypes, Cache) ->
+ {TempRecords1, NewCache} =
+ process_opaque_types(TempRecords0, TempExpTypes, Cache),
+ process_opaque_types(TempRecords1, TempExpTypes, NewCache).
-process_opaque_types(TempRecords, TempExpTypes) ->
+process_opaque_types(TempRecords, TempExpTypes, Cache) ->
+ VarTable = erl_types:var_table__new(),
ModuleFun =
- fun(Module, Record) ->
+ fun({Module, Record}, C0) ->
RecordFun =
- fun(Key, Value) ->
+ fun({Key, Value}, C2) ->
case Key of
{opaque, Name, NArgs} ->
{{_Module, _FileLine, Form, _ArgNames}=F, _Type} = Value,
Site = {type, {Module, Name, NArgs}},
- Type = erl_types:t_from_form(Form, TempExpTypes, Site,
- TempRecords),
- {F, Type};
- _Other -> Value
+ {Type, C3} =
+ erl_types:t_from_form(Form, TempExpTypes, Site,
+ TempRecords, VarTable, C2),
+ {{Key, {F, Type}}, C3};
+ _Other -> {{Key, Value}, C2}
end
end,
- dict:map(RecordFun, Record)
+ {RecordList, C1} =
+ lists:mapfoldl(RecordFun, C0, dict:to_list(Record)),
+ {{Module, dict:from_list(RecordList)}, C1}
+ %% dict:map(RecordFun, Record)
end,
- dict:map(ModuleFun, TempRecords).
+ {TempRecordList, NewCache} =
+ lists:mapfoldl(ModuleFun, Cache, dict:to_list(TempRecords)),
+ {dict:from_list(TempRecordList), NewCache}.
+ %% dict:map(ModuleFun, TempRecords).
-check_record_fields(Records, TempExpTypes) ->
+check_record_fields(Records, TempExpTypes, Cache) ->
+ VarTable = erl_types:var_table__new(),
CheckFun =
- fun({Module, Element}) ->
- CheckForm = fun(Form, Site) ->
- erl_types:t_check_record_fields(Form, TempExpTypes,
- Site, Records)
+ fun({Module, Element}, C0) ->
+ CheckForm = fun(Form, Site, C1) ->
+ erl_types:t_check_record_fields(Form, TempExpTypes,
+ Site, Records,
+ VarTable, C1)
end,
ElemFun =
- fun({Key, Value}) ->
+ fun({Key, Value}, C2) ->
case Key of
{record, Name} ->
FieldFun =
- fun({Arity, Fields}) ->
+ fun({Arity, Fields}, C3) ->
Site = {record, {Module, Name, Arity}},
- _ = [ok = CheckForm(Field, Site) ||
- {_, Field, _} <- Fields],
- ok
+ lists:foldl(fun({_, Field, _}, C4) ->
+ CheckForm(Field, Site, C4)
+ end, C3, Fields)
end,
{FileLine, Fields} = Value,
- Fun = fun() -> lists:foreach(FieldFun, Fields) end,
+ Fun = fun() -> lists:foldl(FieldFun, C2, Fields) end,
msg_with_position(Fun, FileLine);
{_OpaqueOrType, Name, NArgs} ->
Site = {type, {Module, Name, NArgs}},
{{_Module, FileLine, Form, _ArgNames}, _Type} = Value,
- Fun = fun() -> ok = CheckForm(Form, Site) end,
+ Fun = fun() -> CheckForm(Form, Site, C2) end,
msg_with_position(Fun, FileLine)
end
end,
- lists:foreach(ElemFun, dict:to_list(Element))
+ lists:foldl(ElemFun, C0, dict:to_list(Element))
end,
- lists:foreach(CheckFun, dict:to_list(Records)).
+ lists:foldl(CheckFun, Cache, dict:to_list(Records)).
msg_with_position(Fun, FileLine) ->
try Fun()
@@ -396,7 +424,7 @@ msg_with_position(Fun, FileLine) ->
throw({error, NewMsg})
end.
--spec merge_records(dict:dict(), dict:dict()) -> dict:dict().
+-spec merge_records(mod_records(), mod_records()) -> mod_records().
merge_records(NewRecords, OldRecords) ->
dict:merge(fun(_Key, NewVal, _OldVal) -> NewVal end, NewRecords, OldRecords).
@@ -410,7 +438,7 @@ merge_records(NewRecords, OldRecords) ->
-type spec_dict() :: dict:dict().
-type callback_dict() :: dict:dict().
--spec get_spec_info(module(), abstract_code(), dict:dict()) ->
+-spec get_spec_info(module(), abstract_code(), type_table()) ->
{'ok', spec_dict(), callback_dict()} | {'error', string()}.
get_spec_info(ModName, AbstractCode, RecordsDict) ->
@@ -676,7 +704,7 @@ format_errors([]) ->
format_sig(Type) ->
format_sig(Type, dict:new()).
--spec format_sig(erl_types:erl_type(), dict:dict()) -> string().
+-spec format_sig(erl_types:erl_type(), type_table()) -> string().
format_sig(Type, RecDict) ->
"fun(" ++ Sig = lists:flatten(erl_types:t_to_string(Type, RecDict)),
diff --git a/lib/dialyzer/test/map_SUITE_data/results/exact b/lib/dialyzer/test/map_SUITE_data/results/exact
index 374ada8869..ea00e61330 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/exact
+++ b/lib/dialyzer/test/map_SUITE_data/results/exact
@@ -1,3 +1,3 @@
exact.erl:15: Function t2/1 has no local return
-exact.erl:19: The variable _ can never match since previous clauses completely covered the type #{'a':=_, ...}
+exact.erl:19: The variable _ can never match since previous clauses completely covered the type #{'a':=_, _=>_}
diff --git a/lib/dialyzer/test/map_SUITE_data/results/guard_update b/lib/dialyzer/test/map_SUITE_data/results/guard_update
index e4bc892195..98df23907f 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/guard_update
+++ b/lib/dialyzer/test/map_SUITE_data/results/guard_update
@@ -1,5 +1,5 @@
guard_update.erl:5: Function t/0 has no local return
-guard_update.erl:6: The call guard_update:f(#{'a':=2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, ...})
+guard_update.erl:6: The call guard_update:f(#{'a':=2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, _=>_})
guard_update.erl:8: Clause guard cannot succeed. The variable M was matched against the type #{'a':=2}
guard_update.erl:8: Function f/1 has no local return
diff --git a/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2 b/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2
index 6bc0c010d7..f6fb98a863 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2
+++ b/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2
@@ -1,7 +1,7 @@
map_in_guard2.erl:10: The call map_in_guard2:assoc_guard_clause('not_a_map') will never return since it differs in the 1st argument from the success typing arguments: (map())
map_in_guard2.erl:12: The pattern 'true' can never match the type 'false'
-map_in_guard2.erl:14: The call map_in_guard2:exact_guard_clause(#{}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':=_, ...})
+map_in_guard2.erl:14: The call map_in_guard2:exact_guard_clause(#{}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':=_, _=>_})
map_in_guard2.erl:17: Clause guard cannot succeed. The variable M was matched against the type 'not_a_map'
map_in_guard2.erl:20: Function assoc_update/1 has no local return
map_in_guard2.erl:20: Guard test is_map(M::'not_a_map') can never succeed
diff --git a/lib/dialyzer/test/map_SUITE_data/results/typeflow b/lib/dialyzer/test/map_SUITE_data/results/typeflow
index e3378a24bb..acfb7f551e 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/typeflow
+++ b/lib/dialyzer/test/map_SUITE_data/results/typeflow
@@ -1,4 +1,4 @@
typeflow.erl:14: Function t2/1 has no local return
typeflow.erl:16: The call lists:sort(integer()) will never return since it differs in the 1st argument from the success typing arguments: ([any()])
-typeflow.erl:9: The variable _ can never match since previous clauses completely covered the type #{'a':=integer(), ...}
+typeflow.erl:9: The variable _ can never match since previous clauses completely covered the type #{'a':=integer(), _=>_}
diff --git a/lib/dialyzer/test/map_SUITE_data/results/typesig b/lib/dialyzer/test/map_SUITE_data/results/typesig
index 3049402860..fb2f851a7d 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/typesig
+++ b/lib/dialyzer/test/map_SUITE_data/results/typesig
@@ -1,5 +1,5 @@
typesig.erl:5: Function t1/0 has no local return
-typesig.erl:5: The call typesig:test(#{'a':=1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, ...})
+typesig.erl:5: The call typesig:test(#{'a':=1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_})
typesig.erl:6: Function t2/0 has no local return
-typesig.erl:6: The call typesig:test(#{'a':={'b'}}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, ...})
+typesig.erl:6: The call typesig:test(#{'a':={'b'}}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_})
diff --git a/lib/dialyzer/test/small_SUITE_data/results/maps1 b/lib/dialyzer/test/small_SUITE_data/results/maps1
index a178e96b20..f36f7f4926 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/maps1
+++ b/lib/dialyzer/test/small_SUITE_data/results/maps1
@@ -1,4 +1,4 @@
maps1.erl:43: Function t3/0 has no local return
-maps1.erl:44: The call maps1:foo(#{'greger'=>3, #{'arne'=>'anka'}=>45},1) will never return since it differs in the 1st and 2nd argument from the success typing arguments: (#{'beta':=_, ...},'b')
+maps1.erl:44: The call maps1:foo(#{'greger'=>3, #{'arne'=>'anka'}=>45},1) will never return since it differs in the 1st and 2nd argument from the success typing arguments: (#{'beta':=_, _=>_},'b')
maps1.erl:52: The variable Mod can never match since previous clauses completely covered the type #{}
diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl
new file mode 100644
index 0000000000..4d681b5cc7
--- /dev/null
+++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl
@@ -0,0 +1,11 @@
+-module(send).
+
+-export([s/0]).
+
+s() ->
+ self() ! n(), % no warning
+ erlang:send(self(), n()), % no warning
+ ok.
+
+n() ->
+ {1, 1}.
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 702f11593a..fdbbd412a1 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,17 +38,17 @@
-module(diameter_config).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2]}).
-
-export([start_service/2,
stop_service/1,
add_transport/2,
remove_transport/2,
have_transport/2,
- lookup/1]).
+ lookup/1,
+ subscribe/2]).
-%% child server start
--export([start_link/0]).
+%% server start
+-export([start_link/0,
+ start_link/1]).
%% gen_server callbacks
-export([init/1,
@@ -58,8 +58,8 @@
handle_info/2,
code_change/3]).
-%% diameter_sync requests.
--export([sync/1]).
+%% callbacks
+-export([sync/1]). %% diameter_sync requests
%% debug
-export([state/0,
@@ -69,7 +69,8 @@
-include("diameter_internal.hrl").
%% Server state.
--record(state, {id = diameter_lib:now()}).
+-record(state, {id = diameter_lib:now(),
+ role :: server | transport}).
%% Registered name of the server.
-define(SERVER, ?MODULE).
@@ -77,6 +78,9 @@
%% Table config is written to.
-define(TABLE, ?MODULE).
+%% Key on which a transport-specific child registers itself.
+-define(TRANSPORT_KEY(Ref), {?MODULE, transport, Ref}).
+
%% Workaround for dialyzer's lack of understanding of match specs.
-type match(T)
:: T | '_' | '$1' | '$2' | '$3' | '$4'.
@@ -225,6 +229,13 @@ pred(_) ->
?THROW(pred).
%% --------------------------------------------------------------------------
+%% # subscribe/2
+%% --------------------------------------------------------------------------
+
+subscribe(Ref, T) ->
+ diameter_reg:subscribe(?TRANSPORT_KEY(Ref), T).
+
+%% --------------------------------------------------------------------------
%% # have_transport/2
%%
%% Output: true | false
@@ -264,6 +275,9 @@ start_link() ->
Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}],
gen_server:start_link(ServerName, Module, Args, Options).
+start_link(T) ->
+ proc_lib:start_link(?MODULE, init, [T], infinity, []).
+
state() ->
call(state).
@@ -274,8 +288,27 @@ uptime() ->
%%% # init/1
%%% ----------------------------------------------------------
+%% ?SERVER start.
init([]) ->
- {ok, #state{}}.
+ {ok, #state{role = server}};
+
+%% Child start as a consequence of add_transport.
+init({SvcName, Type, Opts}) ->
+ Res = try
+ add(SvcName, Type, Opts)
+ catch
+ ?FAILURE(Reason) -> {error, Reason}
+ end,
+ proc_lib:init_ack({ok, self(), Res}),
+ loop(Res).
+
+%% loop/1
+
+loop({ok, _}) ->
+ gen_server:enter_loop(?MODULE, [], #state{role = transport});
+
+loop({error, _}) ->
+ ok. %% die
%%% ----------------------------------------------------------
%%% # handle_call/2
@@ -284,8 +317,8 @@ init([]) ->
handle_call(state, _, State) ->
{reply, State, State};
-handle_call(uptime, _, #state{id = Time} = State) ->
- {reply, diameter_lib:now_diff(Time), State};
+handle_call(uptime, _, #state{id = Time} = S) ->
+ {reply, diameter_lib:now_diff(Time), S};
handle_call(Req, From, State) ->
?UNEXPECTED([Req, From]),
@@ -304,30 +337,34 @@ handle_cast(Msg, State) ->
%%% # handle_info/2
%%% ----------------------------------------------------------
+%% remove_transport is telling published child to die.
+handle_info(stop, #state{role = transport} = S) ->
+ {stop, normal, S};
+
%% A service process has died. This is most likely a consequence of
%% stop_service, in which case the restart will find no config for the
%% service and do nothing. The entry keyed on the monitor ref is only
%% removed as a result of the 'DOWN' notification however.
-handle_info({'DOWN', MRef, process, _, Reason}, State) ->
+handle_info({'DOWN', MRef, process, _, Reason}, #state{role = server} = S) ->
[#monitor{service = SvcName} = T] = select([{#monitor{mref = MRef,
_ = '_'},
[],
['$_']}]),
queue_restart(Reason, SvcName),
delete_object(T),
- {noreply, State};
+ {noreply, S};
-handle_info({monitor, SvcName, Pid}, State) ->
- monitor(Pid, SvcName),
- {noreply, State};
+handle_info({monitor, SvcName, Pid}, #state{role = server} = S) ->
+ insert_monitor(Pid, SvcName),
+ {noreply, S};
-handle_info({restart, SvcName}, State) ->
+handle_info({restart, SvcName}, #state{role = server} = S) ->
restart(SvcName),
- {noreply, State};
+ {noreply, S};
-handle_info(restart, State) ->
+handle_info(restart, #state{role = server} = S) ->
restart(),
- {noreply, State};
+ {noreply, S};
handle_info(Info, State) ->
?UNEXPECTED([Info]),
@@ -404,19 +441,22 @@ sync({start_service, SvcName, Opts}) ->
sync({stop_service, SvcName}) ->
stop(SvcName);
+%% Start a child whose only purpose is to be alive for the lifetime of
+%% the transport configuration and publish itself in diameter_reg.
+%% This is to provide a way for processes to to be notified when the
+%% configuration is removed (diameter_reg:subscribe/2).
sync({add, SvcName, Type, Opts}) ->
- try
- add(SvcName, Type, Opts)
- catch
- ?FAILURE(Reason) -> {error, Reason}
- end;
+ {ok, _Pid, Res} = diameter_config_sup:start_child({SvcName, Type, Opts}),
+ Res;
sync({remove, SvcName, Pred}) ->
- remove(select([{#transport{service = '$1', _ = '_'},
+ Recs = select([{#transport{service = '$1', _ = '_'},
[{'=:=', '$1', {const, SvcName}}],
['$_']}]),
- SvcName,
- Pred).
+ F = fun(#transport{ref = R, type = T, options = O}) ->
+ Pred(R,T,O)
+ end,
+ remove(SvcName, lists:filter(F, Recs)).
%% start/3
@@ -438,8 +478,8 @@ startmon(SvcName, {ok, Pid}) ->
startmon(_, {error, _}) ->
ok.
-monitor(Pid, SvcName) ->
- MRef = erlang:monitor(process, Pid),
+insert_monitor(Pid, SvcName) ->
+ MRef = monitor(process, Pid),
insert(#monitor{mref = MRef, service = SvcName}).
%% queue_restart/2
@@ -503,6 +543,7 @@ add(SvcName, Type, Opts) ->
ok = transport_opts(Opts),
Ref = make_ref(),
+ true = diameter_reg:add_new(?TRANSPORT_KEY(Ref)),
T = {Ref, Type, Opts},
%% The call to the service returns error if the service isn't
%% started yet, which is harmless. The transport will be started
@@ -594,26 +635,30 @@ start_transport(SvcName, T) ->
No
end.
-%% remove/3
+%% remove/2
-remove(L, SvcName, Pred) ->
- rm(SvcName, lists:filter(fun(#transport{ref = R, type = T, options = O}) ->
- Pred(R,T,O)
- end,
- L)).
-
-rm(_, []) ->
+remove(_, []) ->
ok;
-rm(SvcName, L) ->
+
+remove(SvcName, L) ->
Refs = lists:map(fun(#transport{ref = R}) -> R end, L),
case stop_transport(SvcName, Refs) of
ok ->
+ lists:foreach(fun stop_child/1, Refs),
diameter_stats:flush(Refs),
lists:foreach(fun delete_object/1, L);
{error, _} = No ->
No
end.
+stop_child(Ref) ->
+ case diameter_reg:match(?TRANSPORT_KEY(Ref)) of
+ [{_, Pid}] -> %% tell the transport-specific child to die
+ Pid ! stop;
+ [] -> %% already removed/dead
+ ok
+ end.
+
stop_transport(SvcName, Refs) ->
case diameter_service:stop_transport(SvcName, Refs) of
ok ->
diff --git a/lib/diameter/src/base/diameter_config_sup.erl b/lib/diameter/src/base/diameter_config_sup.erl
new file mode 100644
index 0000000000..9524573378
--- /dev/null
+++ b/lib/diameter/src/base/diameter_config_sup.erl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2016. 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%
+%%
+
+%%
+%% Supervisor for config processes.
+%%
+
+-module(diameter_config_sup).
+
+-behaviour(supervisor).
+
+%% interface
+-export([start_link/0, %% supervisor start
+ start_child/1]). %% config start
+
+-export([init/1]).
+
+-define(NAME, ?MODULE). %% supervisor name
+
+%% start_link/0
+
+start_link() ->
+ SupName = {local, ?NAME},
+ supervisor:start_link(SupName, ?MODULE, []).
+
+%% start_child/1
+
+start_child(T) ->
+ supervisor:start_child(?NAME, [T]).
+
+%% init/1
+
+init([]) ->
+ Mod = diameter_config,
+ Flags = {simple_one_for_one, 0, 1},
+ ChildSpec = {Mod,
+ {Mod, start_link, []},
+ temporary,
+ 1000,
+ worker,
+ [Mod]},
+ {ok, {Flags, [ChildSpec]}}.
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 43b0ca24ab..b835e87967 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,18 +20,17 @@
-module(diameter_lib).
-compile({no_auto_import, [now/0]}).
--compile({nowarn_deprecated_function, [{erlang, now, 0}]}).
-export([info_report/2,
error_report/2,
warning_report/2,
now/0,
+ timestamp/0,
timestamp/1,
now_diff/1,
micro_diff/1,
micro_diff/2,
time/1,
- seed/0,
eval/1,
eval_name/1,
get_stacktrace/0,
@@ -110,6 +109,16 @@ now() ->
erlang:monotonic_time().
%% ---------------------------------------------------------------------------
+%% # timestamp/0
+%% ---------------------------------------------------------------------------
+
+-spec timestamp()
+ -> erlang:timestamp().
+
+timestamp() ->
+ timestamp(now()).
+
+%% ---------------------------------------------------------------------------
%% # timestamp/1
%% ---------------------------------------------------------------------------
@@ -184,24 +193,6 @@ time(Micro) -> %% elapsed time
{H, M, S, Micro rem 1000000}.
%% ---------------------------------------------------------------------------
-%% # seed/0
-%% ---------------------------------------------------------------------------
-
--spec seed()
- -> {erlang:timestamp(), {integer(), integer(), integer()}}.
-
-%% Return an argument for random:seed/1.
-
-seed() ->
- T = now(),
- {timestamp(T), seed(T)}.
-
-%% seed/1
-
-seed(T) -> %% monotonic time
- {erlang:phash2(node()), T, erlang:unique_integer()}.
-
-%% ---------------------------------------------------------------------------
%% # eval/1
%%
%% Evaluate a function in various forms.
diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl
index 7f198080ba..9027130063 100644
--- a/lib/diameter/src/base/diameter_reg.erl
+++ b/lib/diameter/src/base/diameter_reg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,14 +25,12 @@
-module(diameter_reg).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2]}).
-
-export([add/1,
add_new/1,
- del/1,
- repl/2,
+ remove/1,
match/1,
- wait/1]).
+ wait/1,
+ subscribe/2]).
-export([start_link/0]).
@@ -46,29 +44,32 @@
%% test
-export([pids/0,
- terms/0]).
+ terms/0,
+ subs/0,
+ waits/0]).
%% debug
-export([state/0,
uptime/0]).
--include("diameter_internal.hrl").
-
-define(SERVER, ?MODULE).
-define(TABLE, ?MODULE).
-%% Table entry used to keep from starting more than one monitor on the
-%% same process. This isn't a problem but there's no point in starting
-%% multiple monitors if we can avoid it. Note that we can't have a 2-tuple
-%% keyed on Pid since a registered term can be anything. Want the entry
-%% keyed on Pid so that lookup is fast.
--define(MONITOR(Pid, MRef), {Pid, monitor, MRef}).
-
-%% Table entry containing the Term -> Pid mapping.
--define(MAPPING(Term, Pid), {Term, Pid}).
+-type key() :: term().
+-type from() :: {pid(), term()}.
+-type pattern() :: term().
-record(state, {id = diameter_lib:now(),
- q = []}). %% [{From, Pat}]
+ receivers = dict:new()
+ :: dict:dict(pattern(), [[pid() | term()]%% subscribe
+ | from()]), %% wait
+ monitors = sets:new() :: sets:set(pid())}).
+
+%% The ?TABLE bag contains the Key -> Pid mapping, as {Key, Pid}
+%% tuples. Each pid is stored in the monitors set to ensure only one
+%% monitor for each pid: more are harmless, but unnecessary. A pattern
+%% is added to receivers a result of calls to wait/1 or subscribe/2:
+%% changes to ?TABLE causes processes to be notified as required.
%% ===========================================================================
%% # add(T)
@@ -77,18 +78,18 @@
%% this or other assocations can be retrieved using match/1.
%%
%% An association is removed when the calling process dies or as a
-%% result of calling del/1. Adding the same term more than once is
-%% equivalent to adding it exactly once.
+%% result of calling remove/1. Adding the same term more than once is
+%% equivalent to adding it once.
%%
%% Note that since match/1 takes a pattern as argument, specifying a
%% term that contains match variables is probably not a good idea
%% ===========================================================================
--spec add(any())
+-spec add(key())
-> true.
add(T) ->
- call({add, fun ets:insert/2, T, self()}).
+ call({add, false, T}).
%% ===========================================================================
%% # add_new(T)
@@ -97,36 +98,23 @@ add(T) ->
%% association, false being returned if an association already exists.
%% ===========================================================================
--spec add_new(any())
+-spec add_new(key())
-> boolean().
add_new(T) ->
- call({add, fun insert_new/2, T, self()}).
+ call({add, true, T}).
%% ===========================================================================
-%% # repl(T, NewT)
-%%
-%% Like add/1 but only replace an existing association on T, false
-%% being returned if it doesn't exist.
-%% ===========================================================================
-
--spec repl(any(), any())
- -> boolean().
-
-repl(T, U) ->
- call({repl, T, U, self()}).
-
-%% ===========================================================================
-%% # del(Term)
+%% # remove(Term)
%%
%% Remove any existing association of Term with self().
%% ===========================================================================
--spec del(any())
+-spec remove(key())
-> true.
-del(T) ->
- call({del, T, self()}).
+remove(T) ->
+ call({remove, T}).
%% ===========================================================================
%% # match(Pat)
@@ -139,12 +127,17 @@ del(T) ->
%% associations removed.)
%% ===========================================================================
--spec match(any())
- -> [{term(), pid()}].
+-spec match(pattern())
+ -> [{key(), pid()}].
match(Pat) ->
- ets:match_object(?TABLE, ?MAPPING(Pat, '_')).
+ match(Pat, '_').
+
+%% match/2
+match(Pat, Pid) ->
+ ets:match_object(?TABLE, {Pat, Pid}).
+
%% ===========================================================================
%% # wait(Pat)
%%
@@ -152,10 +145,29 @@ match(Pat) ->
%% It's up to the caller to ensure that the wait won't be forever.
%% ===========================================================================
+-spec wait(pattern())
+ -> [{key(), pid()}].
+
wait(Pat) ->
+ _ = match(Pat), %% ensure match can succeed
call({wait, Pat}).
%% ===========================================================================
+%% # subscribe(Pat, T)
+%%
+%% Like match/1, but additionally receive messages of the form
+%% {T, add|remove, {term(), pid()} when associations are added
+%% or removed.
+%% ===========================================================================
+
+-spec subscribe(Pat :: any(), T :: term())
+ -> [{term(), pid()}].
+
+subscribe(Pat, T) ->
+ _ = match(Pat), %% ensure match can succeed
+ call({subscribe, Pat, T}).
+
+%% ===========================================================================
start_link() ->
ServerName = {local, ?SERVER},
@@ -169,19 +181,15 @@ uptime() ->
call(uptime).
%% pids/0
-%%
-%% Return: list of {Pid, [Term, ...]}
+
+-spec pids()
+ -> [{pid(), [key()]}].
pids() ->
to_list(fun swap/1).
to_list(Fun) ->
- ets:foldl(fun(T,A) -> acc(Fun, T, A) end, orddict:new(), ?TABLE).
-
-acc(Fun, ?MAPPING(Term, Pid), Dict) ->
- append(Fun({Term, Pid}), Dict);
-acc(_, _, Dict) ->
- Dict.
+ ets:foldl(fun(T,D) -> append(Fun(T), D) end, orddict:new(), ?TABLE).
append({K,V}, Dict) ->
orddict:append(K, V, Dict).
@@ -189,14 +197,47 @@ append({K,V}, Dict) ->
id(T) -> T.
%% terms/0
-%%
-%% Return: list of {Term, [Pid, ...]}
+
+-spec terms()
+ -> [{key(), [pid()]}].
terms() ->
to_list(fun id/1).
swap({X,Y}) -> {Y,X}.
+%% subs/0
+
+-spec subs()
+ -> [{pattern(), [{pid(), term()}]}].
+
+subs() ->
+ #state{receivers = RD} = state(),
+ dict:fold(fun sub/3, orddict:new(), RD).
+
+sub(Pat, Ps, Dict) ->
+ lists:foldl(fun([P|T], D) -> orddict:append(Pat, {P,T}, D);
+ (_, D) -> D
+ end,
+ Dict,
+ Ps).
+
+%% waits/0
+
+-spec waits()
+ -> [{pattern(), [{from(), term()}]}].
+
+waits() ->
+ #state{receivers = RD} = state(),
+ dict:fold(fun wait/3, orddict:new(), RD).
+
+wait(Pat, Ps, Dict) ->
+ lists:foldl(fun({_,_} = F, D) -> orddict:append(Pat, F, D);
+ (_, D) -> D
+ end,
+ Dict,
+ Ps).
+
%% ----------------------------------------------------------
%% # init/1
%% ----------------------------------------------------------
@@ -209,57 +250,58 @@ init(_) ->
%% # handle_call/3
%% ----------------------------------------------------------
-handle_call({add, Fun, Key, Pid}, _, S) ->
- B = Fun(?TABLE, {Key, Pid}),
- monitor(B andalso no_monitor(Pid), Pid),
- {reply, B, pending(B, S)};
-
-handle_call({del, Key, Pid}, _, S) ->
- {reply, ets:delete_object(?TABLE, ?MAPPING(Key, Pid)), S};
-
-handle_call({repl, T, U, Pid}, _, S) ->
- MatchSpec = [{?MAPPING('$1', Pid),
- [{'=:=', '$1', {const, T}}],
- ['$_']}],
- {reply, repl(ets:select(?TABLE, MatchSpec), U, Pid), S};
-
-handle_call({wait, Pat}, From, #state{q = Q} = S) ->
- case find(Pat) of
- {ok, L} ->
- {reply, L, S};
- false ->
- {noreply, S#state{q = [{From, Pat} | Q]}}
+handle_call({add, Uniq, Key}, {Pid, _}, S0) ->
+ Rec = {Key, Pid},
+ S1 = flush(Uniq, Rec, S0),
+ {Res, New} = insert(Uniq, Rec),
+ {Recvs, S} = add(New, Rec, S1),
+ notify(Recvs, Rec),
+ {reply, Res, S};
+
+handle_call({remove, Key}, {Pid, _}, S) ->
+ Rec = {Key, Pid},
+ Recvs = delete([Rec], S),
+ ets:delete_object(?TABLE, Rec),
+ notify(Recvs, remove),
+ {reply, true, S};
+
+handle_call({wait, Pat}, {Pid, _} = From, #state{receivers = RD} = S) ->
+ NS = add_monitor(Pid, S),
+ case match(Pat) of
+ [_|_] = L ->
+ {reply, L, NS};
+ [] ->
+ {noreply, NS#state{receivers = dict:append(Pat, From, RD)}}
end;
+handle_call({subscribe, Pat, T}, {Pid, _}, #state{receivers = RD} = S) ->
+ NS = add_monitor(Pid, S),
+ {reply, match(Pat), NS#state{receivers = dict:append(Pat, [Pid | T], RD)}};
+
handle_call(state, _, S) ->
{reply, S, S};
handle_call(uptime, _, #state{id = Time} = S) ->
{reply, diameter_lib:now_diff(Time), S};
-handle_call(Req, From, S) ->
- ?UNEXPECTED([Req, From]),
+handle_call(_Req, _From, S) ->
{reply, nok, S}.
%% ----------------------------------------------------------
%% # handle_cast/2
%% ----------------------------------------------------------
-handle_cast(Msg, S)->
- ?UNEXPECTED([Msg]),
+handle_cast(_Msg, S)->
{noreply, S}.
%% ----------------------------------------------------------
%% # handle_info/2
%% ----------------------------------------------------------
-handle_info({'DOWN', MRef, process, Pid, _}, S) ->
- ets:delete_object(?TABLE, ?MONITOR(Pid, MRef)),
- ets:match_delete(?TABLE, ?MAPPING('_', Pid)),
- {noreply, S};
+handle_info({'DOWN', _MRef, process, Pid, _}, S) ->
+ {noreply, down(Pid, S)};
-handle_info(Info, S) ->
- ?UNEXPECTED([Info]),
+handle_info(_Info, S) ->
{noreply, S}.
%% ----------------------------------------------------------
@@ -278,71 +320,166 @@ code_change(_OldVsn, State, _Extra) ->
%% ===========================================================================
-monitor(true, Pid) ->
- ets:insert(?TABLE, ?MONITOR(Pid, erlang:monitor(process, Pid)));
-monitor(false, _) ->
- ok.
+%% insert/2
+
+insert(false, Rec) ->
+ Spec = [{'$1', [{'==', '$1', {const, Rec}}], ['$_']}],
+ X = '$end_of_table' /= ets:select(?TABLE, Spec, 1), %% entry exists?
+ X orelse ets:insert(?TABLE, Rec),
+ {true, not X};
-%% Do we need a monitor for the specified Pid?
-no_monitor(Pid) ->
- [] == ets:match_object(?TABLE, ?MONITOR(Pid, '_')).
+insert(true, Rec) ->
+ B = ets:insert_new(?TABLE, Rec), %% entry inserted?
+ {B, B}.
-%% insert_new/2
+%% add/3
-insert_new(?TABLE, {Key, _} = T) ->
- flush(ets:lookup(?TABLE, Key)),
- ets:insert_new(?TABLE, T).
+%% Only add a single monitor for any given process, since there's no
+%% use to more.
+add(true, {_Key, Pid} = Rec, S) ->
+ NS = add_monitor(Pid, S),
+ {Recvs, RD} = add(Rec, NS),
+ {Recvs, S#state{receivers = RD}};
+
+add(false = No, _, S) ->
+ {No, S}.
+
+%% add/2
+
+%% Notify processes whose patterns match the inserted key.
+add({_Key, Pid} = Rec, #state{receivers = RD}) ->
+ dict:fold(fun(Pt, Ps, A) ->
+ add(lists:member(Rec, match(Pt, Pid)), Pt, Ps, Rec, A)
+ end,
+ {sets:new(), RD},
+ RD).
+
+%% add/5
+
+add(true, Pat, Recvs, {_,_} = Rec, {Set, Dict}) ->
+ {lists:foldl(fun sets:add_element/2, Set, Recvs),
+ remove(fun erlang:is_list/1, Pat, Recvs, Dict)};
+
+add(false, _, _, _, Acc) ->
+ Acc.
+
+%% add_monitor/2
+
+add_monitor(Pid, #state{monitors = MS} = S) ->
+ add_monitor(sets:is_element(Pid, MS), Pid, S).
+
+%% add_monitor/3
+
+add_monitor(false, Pid, #state{monitors = MS} = S) ->
+ monitor(process, Pid),
+ S#state{monitors = sets:add_element(Pid, MS)};
+
+add_monitor(true, _, S) ->
+ S.
+
+%% delete/2
+
+delete(Recs, #state{receivers = RD}) ->
+ lists:foldl(fun(R,S) -> delete(R, RD, S) end, sets:new(), Recs).
+
+%% delete/3
+
+delete({_Key, Pid} = Rec, RD, Set) ->
+ dict:fold(fun(Pt, Ps, S) ->
+ delete(lists:member(Rec, match(Pt, Pid)), Rec, Ps, S)
+ end,
+ Set,
+ RD).
+
+%% delete/4
+
+%% Entry matches a pattern ...
+delete(true, Rec, Recvs, Set) ->
+ lists:foldl(fun(R,S) -> sets:add_element({R, Rec}, S) end,
+ Set,
+ Recvs);
+
+%% ... or not.
+delete(false, _, _, Set) ->
+ Set.
+
+%% notify/2
+
+notify(false = No, _) ->
+ No;
+
+notify(Recvs, remove = Op) ->
+ sets:fold(fun({P,R}, N) -> send(P, R, Op), N+1 end, 0, Recvs);
+
+notify(Recvs, {_,_} = Rec) ->
+ sets:fold(fun(P,N) -> send(P, Rec, add), N+1 end, 0, Recvs).
+
+%% send/3
+
+%% No processes waiting on remove, by construction: they've either
+%% received notification at add or aren't waiting.
+send([Pid | T], Rec, Op) ->
+ Pid ! {T, Op, Rec};
+
+send({_,_} = From, Rec, add) ->
+ gen_server:reply(From, [Rec]).
+
+%% down/2
+
+down(Pid, #state{monitors = MS} = S) ->
+ NS = flush(Pid, S),
+ Recvs = delete(match('_', Pid), NS),
+ ets:match_delete(?TABLE, {'_', Pid}),
+ notify(Recvs, remove),
+ NS#state{monitors = sets:del_element(Pid, MS)}.
+
+%% flush/3
%% Remove any processes that are dead but for which we may not have
-%% received 'DOWN' yet. This is to ensure that add_new can be used
-%% to register a unique name each time a process restarts.
-flush(List) ->
- lists:foreach(fun({_,P} = T) ->
- del(erlang:is_process_alive(P), T)
- end,
- List).
-
-del(Alive, T) ->
- Alive orelse ets:delete_object(?TABLE, T).
-
-%% repl/3
-
-repl([?MAPPING(_, Pid) = M], Key, Pid) ->
- ets:delete_object(?TABLE, M),
- true = ets:insert(?TABLE, ?MAPPING(Key, Pid));
-repl([], _, _) ->
- false.
-
-%% pending/1
-
-pending(true, #state{q = [_|_] = Q} = S) ->
- S#state{q = q(lists:reverse(Q), [])}; %% retain reply order
-pending(_, S) ->
+%% received 'DOWN' yet, to ensure that add_new can be used to register
+%% a unique name each time a registering process restarts.
+flush(true, {Key, Pid}, S) ->
+ Spec = [{{'$1', '$2'},
+ [{'andalso', {'==', '$1', {const, Key}},
+ {'/=', '$2', Pid}}],
+ ['$2']}],
+ lists:foldl(fun down/2, S, [P || P <- ets:select(?TABLE, Spec),
+ not is_process_alive(P)]);
+
+flush(false, _, S) ->
S.
-q([], Q) ->
- Q;
-q([{From, Pat} = T | Rest], Q) ->
- case find(Pat) of
- {ok, L} ->
- gen_server:reply(From, L),
- q(Rest, Q);
- false ->
- q(Rest, [T|Q])
- end.
-
-%% find/1
-
-find(Pat) ->
- try match(Pat) of
- [] ->
- false;
- L ->
- {ok, L}
- catch
- _:_ ->
- {ok, []}
- end.
+%% flush/2
+
+%% Process has died and should no longer receive messages/replies.
+flush(Pid, #state{receivers = RD} = S)
+ when is_pid(Pid) ->
+ S#state{receivers = dict:fold(fun(Pt,Ps,D) -> flush(Pid, Pt, Ps, D) end,
+ RD,
+ RD)}.
+
+%% flush/4
+
+flush(Pid, Pat, Recvs, Dict) ->
+ remove(fun(T) -> Pid /= head(T) end, Pat, Recvs, Dict).
+
+%% head/1
+
+head([P|_]) ->
+ P;
+
+head({P,_}) ->
+ P.
+
+%% remove/4
+
+remove(Pred, Key, Values, Dict) ->
+ case lists:filter(Pred, Values) of
+ [] ->
+ dict:erase(Key, Dict);
+ Rest ->
+ dict:store(Key, Rest, Dict)
+ end.
%% call/1
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index cfb5cb5b82..ccf68f4d93 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -136,7 +136,7 @@
state = ?WD_INITIAL :: match(wd_state()),
started = diameter_lib:now(),%% at process start
peer = false :: match(boolean() | pid())}).
- %% true at accepted, pid() at okay/reopen
+ %% true at accepted/remove, pid() at okay/reopen
%% Record representing a Peer State Machine processes implemented by
%% diameter_peer_fsm.
@@ -250,7 +250,7 @@ subscribe(SvcName) ->
diameter_reg:add({?MODULE, subscriber, SvcName}).
unsubscribe(SvcName) ->
- diameter_reg:del({?MODULE, subscriber, SvcName}).
+ diameter_reg:remove({?MODULE, subscriber, SvcName}).
subscriptions(Pat) ->
pmap(diameter_reg:match({?MODULE, subscriber, Pat})).
@@ -676,25 +676,34 @@ mod_state(Alias, ModS) ->
%% remove_transport
shutdown(Refs, #state{watchdogT = WatchdogT})
when is_list(Refs) ->
- ets:foldl(fun(P,ok) -> st(P, Refs), ok end, ok, WatchdogT);
+ ets:insert(WatchdogT, ets:foldl(fun(R,A) -> st(R, Refs, A) end,
+ [],
+ WatchdogT));
%% application/service shutdown
shutdown(Reason, #state{watchdogT = WatchdogT})
when Reason == application;
Reason == service ->
- diameter_lib:wait(ets:foldl(fun(P,A) -> st(P, Reason, A) end,
+ diameter_lib:wait(ets:foldl(fun(P,A) -> ss(P, Reason, A) end,
[],
WatchdogT)).
-%% st/2
+%% st/3
-st(#watchdog{ref = Ref, pid = Pid}, Refs) ->
- lists:member(Ref, Refs)
- andalso (Pid ! {shutdown, self(), transport}). %% 'DOWN' cleans up
+%% Mark replacement as started so that a subsequent accept doesn't
+%% result in a new process that isn't terminated.
+st(#watchdog{ref = Ref, pid = Pid, peer = P} = Rec, Refs, Acc) ->
+ case lists:member(Ref, Refs) of
+ true ->
+ Pid ! {shutdown, self(), transport}, %% 'DOWN' cleans up
+ [Rec#watchdog{peer = true} || P == false] ++ Acc;
+ false ->
+ Acc
+ end.
-%% st/3
+%% ss/3
-st(#watchdog{pid = Pid}, Reason, Acc) ->
+ss(#watchdog{pid = Pid}, Reason, Acc) ->
MRef = monitor(process, Pid),
Pid ! {shutdown, self(), Reason},
[MRef | Acc].
@@ -974,11 +983,22 @@ ms(_, Svc) ->
%% ---------------------------------------------------------------------------
accepted(Pid, _TPid, #state{watchdogT = WatchdogT} = S) ->
- #watchdog{ref = Ref, type = accept = T, peer = false, options = Opts}
+ #watchdog{type = accept = T, peer = P}
= Wd
= fetch(WatchdogT, Pid),
- ets:insert(WatchdogT, Wd#watchdog{peer = true}),%% mark replacement started
- start(Ref, T, Opts, S). %% start new watchdog
+ if not P ->
+ #watchdog{ref = Ref, options = Opts} = Wd,
+ %% Mark replacement started, and start new watchdog.
+ ets:insert(WatchdogT, Wd#watchdog{peer = true}),
+ start(Ref, T, Opts, S);
+ P ->
+ %% Transport removal in progress: true has been set in
+ %% shutdown/2, and the transport will die as a
+ %% consequence.
+ ok
+ end.
+
+%% fetch/2
fetch(Tid, Key) ->
[T] = ets:lookup(Tid, Key),
@@ -1317,8 +1337,7 @@ start_tc(Tc, T, _) ->
tc_timeout({Ref, _Type, _Opts} = T, #state{service_name = SvcName} = S) ->
tc(diameter_config:have_transport(SvcName, Ref), T, S).
-tc(true, {Ref, Type, Opts}, #state{service_name = SvcName}
- = S) ->
+tc(true, {Ref, Type, Opts}, #state{service_name = SvcName} = S) ->
send_event(SvcName, {reconnect, Ref, Opts}),
start(Ref, Type, Opts, S);
tc(false = No, _, _) -> %% removed
diff --git a/lib/diameter/src/base/diameter_session.erl b/lib/diameter/src/base/diameter_session.erl
index 53973649fd..d854bc36a5 100644
--- a/lib/diameter/src/base/diameter_session.erl
+++ b/lib/diameter/src/base/diameter_session.erl
@@ -158,10 +158,9 @@ session_id(Host) ->
%% ---------------------------------------------------------------------------
init() ->
- {Now, Seed} = diameter_lib:seed(),
- random:seed(Seed),
+ Now = diameter_lib:timestamp(),
Time = time32(Now),
- Seq = (?INT32 band (Time bsl 20)) bor (random:uniform(1 bsl 20) - 1),
+ Seq = (?INT32 band (Time bsl 20)) bor (rand:uniform(1 bsl 20) - 1),
ets:insert(diameter_sequence, [{origin_state_id, Time},
{session_base, Time bsl 32},
{sequence, Seq}]),
diff --git a/lib/diameter/src/base/diameter_sup.erl b/lib/diameter/src/base/diameter_sup.erl
index e89ede9843..482289cb9a 100644
--- a/lib/diameter/src/base/diameter_sup.erl
+++ b/lib/diameter/src/base/diameter_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
-export([init/1]).
-define(CHILDREN, [diameter_misc_sup,
+ diameter_config_sup,
diameter_watchdog_sup,
diameter_peer_fsm_sup,
diameter_transport_sup,
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index 3fd87b223e..2ba60a65fb 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -125,8 +125,6 @@ i({Ack, T, Pid, {RecvData,
= Svc}}) ->
monitor(process, Pid),
wait(Ack, Pid),
- {_, Seed} = diameter_lib:seed(),
- random:seed(Seed),
putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace
putr(dwr, dwr(Caps)), %%
{_,_} = Mask = proplists:get_value(sequence, SvcOpts),
@@ -565,7 +563,7 @@ tw(TwInit, Ms) ->
tw(T)
when is_integer(T), T >= 6000 ->
- T - 2000 + (random:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec.
+ T - 2000 + (rand:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec.
tw({M,F,A}) ->
apply(M,F,A).
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index 3b223ea391..4e4ce60ddf 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -1,7 +1,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2015. All Rights Reserved.
+# Copyright Ericsson AB 2010-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ RT_MODULES = \
base/diameter_callback \
base/diameter_capx \
base/diameter_config \
+ base/diameter_config_sup \
base/diameter_codec \
base/diameter_dict \
base/diameter_lib \
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index 8a80ce630a..4a005b853d 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -61,10 +61,6 @@
%% Remote addresses to accept connections from.
-define(DEFAULT_ACCEPT, []). %% any
-%% How long a listener with no associations lives before offing
-%% itself.
--define(LISTENER_TIMEOUT, 30000).
-
%% How long to wait for a transport process to attach after
%% association establishment.
-define(ACCEPT_TIMEOUT, 5000).
@@ -104,7 +100,6 @@
socket :: gen_sctp:sctp_socket(),
count = 0 :: uint(), %% attached transport processes
pending = {0, queue:new()},
- tref :: reference() | undefined,
accept :: [match()]}).
%% Field pending implements two queues: the first of transport-to-be
%% processes to which an association has been assigned but for which
@@ -216,14 +211,15 @@ init(T) ->
%% A process owning a listening socket.
i({listen, Ref, {Opts, Addrs}}) ->
+ [_] = diameter_config:subscribe(Ref, transport), %% assert existence
{[Matches], Rest} = proplists:split(Opts, [accept]),
{LAs, Sock} = AS = open(Addrs, Rest, ?DEFAULT_PORT),
ok = gen_sctp:listen(Sock, true),
true = diameter_reg:add_new({?MODULE, listener, {Ref, AS}}),
proc_lib:init_ack({ok, self(), LAs}),
- start_timer(#listener{ref = Ref,
- socket = Sock,
- accept = [[M] || {accept, M} <- Matches]});
+ #listener{ref = Ref,
+ socket = Sock,
+ accept = [[M] || {accept, M} <- Matches]};
%% A connecting transport.
i({connect, Pid, Opts, Addrs, Ref}) ->
@@ -431,13 +427,6 @@ putr(Key, Val) ->
getr(Key) ->
get({?MODULE, Key}).
-%% start_timer/1
-
-start_timer(#listener{count = 0} = S) ->
- S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)};
-start_timer(S) ->
- S.
-
%% l/2
%%
%% Transition listener state.
@@ -455,12 +444,10 @@ l({sctp, Sock, _RA, _RP, Data} = T, #listener{socket = Sock,
l({'DOWN', _MRef, process, TPid, _}, #listener{pending = {_,Q}} = S) ->
down(queue:member(TPid, Q), TPid, S);
-%% Timeout after the last accepting process has died.
-l({timeout, TRef, close = T}, #listener{tref = TRef,
- count = 0}) ->
- x(T);
-l({timeout, _, close}, #listener{} = S) ->
- S.
+%% Transport has been removed.
+l({transport, remove, _} = T, #listener{socket = Sock}) ->
+ gen_sctp:close(Sock),
+ x(T).
%% down/3
%%
@@ -472,15 +459,15 @@ down(true, TPid, #listener{pending = {N,Q},
= S) ->
NQ = queue:filter(fun(P) -> P /= TPid end, Q),
if N < 0 -> %% awaiting an association ...
- start_timer(S#listener{count = K-1,
- pending = {N+1, NQ}});
+ S#listener{count = K-1,
+ pending = {N+1, NQ}};
true -> %% ... or one has been assigned
S#listener{pending = {N-1, NQ}}
end;
%% ... or one that's already attached.
down(false, _TPid, #listener{count = K} = S) ->
- start_timer(S#listener{count = K-1}).
+ S#listener{count = K-1}.
%% t/2
%%
diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl
index 6a5e5fe89d..546c2cfa5e 100644
--- a/lib/diameter/src/transport/diameter_tcp.erl
+++ b/lib/diameter/src/transport/diameter_tcp.erl
@@ -57,7 +57,6 @@
-define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})).
-define(DEFAULT_PORT, 3868). %% RFC 3588, ch 2.1
--define(LISTENER_TIMEOUT, 30000).
-define(DEFAULT_FRAGMENT_TIMEOUT, 1000).
-define(IS_UINT32(N), (is_integer(N) andalso 0 =< N andalso 0 == N bsr 32)).
@@ -73,8 +72,10 @@
%% Listener process state.
-record(listener, {socket :: inet:socket(),
- count = 1 :: non_neg_integer(),
- tref :: reference() | undefined}).
+ count = 1 :: non_neg_integer()}). %% accepting processes
+%% The count of accepting processes was previously used to terminate
+%% the listening process, but diameter_reg:subscribe/2 is now used for
+%% this. Leave the the count for trace purposes.
%% Monitor process state.
-record(monitor,
@@ -240,6 +241,7 @@ i(#monitor{parent = Pid, transport = TPid} = S) ->
%% gen_tcp seems to so. Links should be left to supervisors.
i({listen, LRef, APid, {Mod, Opts, Addrs}}) ->
+ [_] = diameter_config:subscribe(LRef, transport), %% assert existence
{[LA, LP], Rest} = proplists:split(Opts, [ip, port]),
LAddrOpt = get_addr(LA, Addrs),
LPort = get_port(LP),
@@ -248,7 +250,7 @@ i({listen, LRef, APid, {Mod, Opts, Addrs}}) ->
true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}),
proc_lib:init_ack({ok, self(), {LAddr, LSock}}),
monitor(process, APid),
- start_timer(#listener{socket = LSock}).
+ #listener{socket = LSock}.
laddr([], Mod, Sock) ->
{ok, {Addr, _Port}} = sockname(Mod, Sock),
@@ -484,13 +486,6 @@ putr(Key, Val) ->
getr(Key) ->
get({?MODULE, Key}).
-%% start_timer/1
-
-start_timer(#listener{count = 0} = S) ->
- S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)};
-start_timer(S) ->
- S.
-
%% m/2
%%
%% Transition monitor state.
@@ -512,21 +507,19 @@ m({'DOWN', _, process, Pid, _}, #monitor{parent = Pid,
%%
%% Transition listener state.
-%% Another accept transport is attaching.
+%% An accepting transport is attaching.
l({accept, TPid}, #listener{count = N} = S) ->
monitor(process, TPid),
S#listener{count = N+1};
%% Accepting process has died.
l({'DOWN', _, process, _, _}, #listener{count = N} = S) ->
- start_timer(S#listener{count = N-1});
+ S#listener{count = N-1};
-%% Timeout after the last accepting process has died.
-l({timeout, TRef, close = T}, #listener{tref = TRef,
- count = 0}) ->
- x(T);
-l({timeout, _, close}, #listener{} = S) ->
- S.
+%% Transport has been removed.
+l({transport, remove, _} = T, #listener{socket = Sock}) ->
+ gen_tcp:close(Sock),
+ x(T).
%% t/2
%%
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index 78308856ac..37c41a1761 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -230,13 +230,12 @@ v(Max, Ord, E)
when Ord =< Max ->
diameter_enum:to_list(E);
v(Max, Ord, E) ->
- random:seed(diameter_util:seed()),
v(Max, Ord, E, []).
v(0, _, _, Acc) ->
Acc;
v(N, Ord, E, Acc) ->
- v(N-1, Ord, E, [E(random:uniform(Ord)) | Acc]).
+ v(N-1, Ord, E, [E(rand:uniform(Ord)) | Acc]).
%% arity/3
@@ -518,15 +517,7 @@ random(M) ->
random(0,M).
random(Mn,Mx) ->
- seed(get({?MODULE, seed})),
- Mn + random:uniform(Mx - Mn + 1) - 1.
-
-seed(undefined) ->
- put({?MODULE, seed}, true),
- random:seed(diameter_util:seed());
-
-seed(true) ->
- ok.
+ Mn + rand:uniform(Mx - Mn + 1) - 1.
%% run/1
%%
diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
index cbd7fc8ec5..79db39ca45 100644
--- a/lib/diameter/test/diameter_gen_sctp_SUITE.erl
+++ b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -365,8 +365,8 @@ open(Opts) ->
assoc(Sock) ->
receive
- ?SCTP(Sock, {[], #sctp_assoc_change{state = S,
- assoc_id = Id}}) ->
+ ?SCTP(Sock, {_, #sctp_assoc_change{state = S,
+ assoc_id = Id}}) ->
comm_up = S, %% assert
Id
end.
diff --git a/lib/diameter/test/diameter_reg_SUITE.erl b/lib/diameter/test/diameter_reg_SUITE.erl
index 3d9ad8bfa8..e2a1ca00c3 100644
--- a/lib/diameter/test/diameter_reg_SUITE.erl
+++ b/lib/diameter/test/diameter_reg_SUITE.erl
@@ -33,8 +33,7 @@
%% testcases
-export([add/1,
add_new/1,
- del/1,
- repl/1,
+ remove/1,
terms/1,
pids/1]).
@@ -56,8 +55,7 @@ groups() ->
tc() ->
[add,
add_new,
- del,
- repl,
+ remove,
terms,
pids].
@@ -82,20 +80,11 @@ add_new(_) ->
true = ?reg:add_new(Ref),
false = ?reg:add_new(Ref).
-del(_) ->
+remove(_) ->
Ref = make_ref(),
true = ?reg:add_new(Ref),
true = ?reg:add_new({Ref}),
- true = ?reg:del({Ref}),
- [{Ref, Pid}] = ?reg:match(Ref),
- Pid = self().
-
-repl(_) ->
- Ref = make_ref(),
- true = ?reg:add_new({Ref}),
- true = ?reg:repl({Ref}, Ref),
- false = ?reg:add_new(Ref),
- false = ?reg:repl({Ref}, Ref),
+ true = ?reg:remove({Ref}),
[{Ref, Pid}] = ?reg:match(Ref),
Pid = self().
diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl
index f766f54a80..b5e520e642 100644
--- a/lib/diameter/test/diameter_relay_SUITE.erl
+++ b/lib/diameter/test/diameter_relay_SUITE.erl
@@ -171,8 +171,9 @@ connect(Config) ->
Conns)).
disconnect(Config) ->
- lists:foreach(fun({{CN,CR},{SN,SR}}) -> ?util:disconnect(CN,CR,SN,SR) end,
- ?util:read_priv(Config, "cfg")).
+ [] = [{T,C} || C <- ?util:read_priv(Config, "cfg"),
+ T <- [break(C)],
+ T /= ok].
stop_services(_Config) ->
[] = [{H,T} || H <- ?SERVICES,
@@ -184,6 +185,13 @@ stop(_Config) ->
%% ----------------------------------------
+break({{CN,CR},{SN,SR}}) ->
+ try
+ ?util:disconnect(CN,CR,SN,SR)
+ after
+ diameter:remove_transport(SN, SR)
+ end.
+
server(Name, Dict) ->
ok = diameter:start_service(Name, ?SERVICE(Name, Dict)),
{Name, ?util:listen(Name, tcp)}.
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 976abf9138..6f3a4801ee 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -428,7 +428,11 @@ remove_transports(Config) ->
server_service = SN}
= group(Config),
[LRef | Cs] = ?util:read_priv(Config, "transport"),
- [?util:disconnect(CN, C, SN, LRef) || C <- Cs].
+ try
+ [] = [T || C <- Cs, T <- [?util:disconnect(CN, C, SN, LRef)], T /= ok]
+ after
+ ok = diameter:remove_transport(SN, LRef)
+ end.
stop_services(Config) ->
#group{client_service = CN,
diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl
index 53d2d6660e..c94f46b7a5 100644
--- a/lib/diameter/test/diameter_transport_SUITE.erl
+++ b/lib/diameter/test/diameter_transport_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -140,7 +140,9 @@ sctp_accept(Config) ->
-define(PEER_COUNT, 8).
accept(Prot) ->
- T = {Prot, make_ref()},
+ Ref = make_ref(),
+ true = diameter_reg:add_new({diameter_config, transport, Ref}), %% fake it
+ T = {Prot, Ref},
[] = ?util:run(?util:scramble(acc(2*?PEER_COUNT, T, []))).
acc(0, _, Acc) ->
@@ -336,13 +338,12 @@ make_msg() ->
%% crypto:rand_bytes/1 isn't available on all platforms (since openssl
%% isn't) so roll our own.
rand_bytes(N) ->
- random:seed(diameter_util:seed()),
rand_bytes(N, <<>>).
rand_bytes(0, Bin) ->
Bin;
rand_bytes(N, Bin) ->
- Oct = random:uniform(256) - 1,
+ Oct = rand:uniform(256) - 1,
rand_bytes(N-1, <<Oct, Bin/binary>>).
%% ===========================================================================
diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl
index 52b747e99c..37fcbbc267 100644
--- a/lib/diameter/test/diameter_util.erl
+++ b/lib/diameter/test/diameter_util.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,7 +31,6 @@
fold/3,
foldl/3,
scramble/1,
- seed/0,
unique_string/0,
have_sctp/0]).
@@ -178,23 +177,15 @@ scramble(L) ->
[[fun s/1, L]]).
s(L) ->
- random:seed(seed()),
s([], L).
s(Acc, []) ->
Acc;
s(Acc, L) ->
- {H, [T|Rest]} = lists:split(random:uniform(length(L)) - 1, L),
+ {H, [T|Rest]} = lists:split(rand:uniform(length(L)) - 1, L),
s([T|Acc], H ++ Rest).
%% ---------------------------------------------------------------------------
-%% seed/0
-
-seed() ->
- {_,T} = diameter_lib:seed(),
- T.
-
-%% ---------------------------------------------------------------------------
%% unique_string/0
unique_string() ->
@@ -345,11 +336,12 @@ transport(SvcName, Ref) ->
disconnect(Client, Ref, Server, LRef) ->
true = diameter:subscribe(Server),
ok = diameter:remove_transport(Client, Ref),
- ok = receive
- {diameter_event, Server, {down, LRef, _, _}} -> ok
- after 10000 ->
- {Client, Ref, Server, LRef, process_info(self(), messages)}
- end.
+ receive
+ {diameter_event, Server, {down, LRef, _, _}} ->
+ ok
+ after 10000 ->
+ {Client, Ref, Server, LRef, process_info(self(), messages)}
+ end.
%% ---------------------------------------------------------------------------
diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl
index e86d090b13..ef57b7b084 100644
--- a/lib/edoc/src/edoc_layout.erl
+++ b/lib/edoc/src/edoc_layout.erl
@@ -901,17 +901,11 @@ t_map(Es) ->
t_map_field(#xmlElement{content = [K,V]}=E) ->
KElem = t_utype_elem(K),
VElem = t_utype_elem(V),
- AT = get_attrval(assoc_type, E),
- IsAny = fun(["any","()"]) -> true; (_) -> false end,
- case AT =:= "assoc" andalso IsAny(KElem) andalso IsAny(VElem) of
- true -> "...";
- false ->
- AS = case AT of
- "assoc" -> " => ";
- "exact" -> " := "
- end,
- KElem ++ [AS] ++ VElem
- end.
+ AS = case get_attrval(assoc_type, E) of
+ "assoc" -> " => ";
+ "exact" -> " := "
+ end,
+ KElem ++ [AS] ++ VElem.
t_record(E, Es) ->
Name = ["#"] ++ t_type(get_elem(atom, Es)),
diff --git a/lib/edoc/src/edoc_parser.yrl b/lib/edoc/src/edoc_parser.yrl
index 983e2f8496..68a3439f10 100644
--- a/lib/edoc/src/edoc_parser.yrl
+++ b/lib/edoc/src/edoc_parser.yrl
@@ -82,9 +82,6 @@ utype_map_field -> utype '=>' utype : #t_map_field{assoc_type = assoc,
utype_map_field -> utype ':=' utype : #t_map_field{assoc_type = exact,
k_type = '$1',
v_type = '$3'}.
-utype_map_field -> '...' : #t_map_field{assoc_type = assoc,
- k_type = any(),
- v_type = any()}.
utype_tuple -> '{' utypes '}' : lists:reverse('$2').
@@ -354,9 +351,6 @@ all_vars([#t_var{} | As]) ->
all_vars(As) ->
As =:= [].
-any() ->
- #t_type{name = #t_name{name = any}, args = []}.
-
%% ---------------------------------------------------------------------
%% @doc EDoc type specification parsing. Parses the content of
diff --git a/lib/erl_docgen/src/docgen_otp_specs.erl b/lib/erl_docgen/src/docgen_otp_specs.erl
index 5bc3be7a8d..6c41147e27 100644
--- a/lib/erl_docgen/src/docgen_otp_specs.erl
+++ b/lib/erl_docgen/src/docgen_otp_specs.erl
@@ -446,17 +446,11 @@ t_map(Es) ->
t_map_field(#xmlElement{content = [K,V]}=E) ->
KElem = t_utype_elem(K),
VElem = t_utype_elem(V),
- AT = get_attrval(assoc_type, E),
- IsAny = fun(["any","()"]) -> true; (_) -> false end,
- case AT =:= "assoc" andalso IsAny(KElem) andalso IsAny(VElem) of
- true -> "...";
- false ->
- AS = case AT of
- "assoc" -> " => ";
- "exact" -> " := "
- end,
- KElem ++ [AS] ++ VElem
- end.
+ AS = case get_attrval(assoc_type, E) of
+ "assoc" -> " => ";
+ "exact" -> " := "
+ end,
+ KElem ++ [AS] ++ VElem.
t_record(E, Es) ->
Name = ["#"] ++ t_type(get_elem(atom, Es)),
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index c383541020..7826dada9d 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -80,11 +80,9 @@
t_float/0,
t_var_names/1,
t_form_to_string/1,
- t_from_form/4,
- t_from_form/5,
+ t_from_form/6,
t_from_form_without_remote/3,
- t_check_record_fields/4,
- t_check_record_fields/5,
+ t_check_record_fields/6,
t_from_range/2,
t_from_range_unsafe/2,
t_from_term/1,
@@ -221,6 +219,7 @@
is_erl_type/1,
atom_to_string/1,
var_table__new/0,
+ cache__new/0,
map_pairwise_merge/3
]).
@@ -237,7 +236,7 @@
-export([t_is_identifier/1]).
-endif.
--export_type([erl_type/0, opaques/0, type_table/0, var_table/0]).
+-export_type([erl_type/0, opaques/0, type_table/0, var_table/0, cache/0]).
%%-define(DEBUG, true).
@@ -328,7 +327,7 @@
%% Auxiliary types and convenient macros
%%
--type parse_form() :: erl_parse:abstract_expr().
+-type parse_form() :: erl_parse:abstract_type().
-type rng_elem() :: 'pos_inf' | 'neg_inf' | integer().
-record(int_set, {set :: [integer()]}).
@@ -375,9 +374,11 @@
-type opaques() :: [erl_type()] | 'universe'.
-type record_key() :: {'record', atom()}.
--type type_key() :: {'type' | 'opaque', atom(), arity()}.
+-type type_key() :: {'type' | 'opaque', mfa()}.
-type record_value() :: [{atom(), erl_parse:abstract_expr(), erl_type()}].
--type type_value() :: {module(), erl_type(), atom()}.
+-type type_value() :: {{module(), {file:name(), erl_anno:line()},
+ erl_parse:abstract_type(), ArgNames :: [atom()]},
+ erl_type()}.
-type type_table() :: dict:dict(record_key() | type_key(),
record_value() | type_value()).
@@ -757,8 +758,8 @@ t_opaque_from_records(RecDict) ->
{{Module, _FileLine, _Form, ArgNames}, _Type}) ->
%% Args = args_to_types(ArgNames),
%% List = lists:zip(ArgNames, Args),
- %% TmpVarDict = dict:from_list(List),
- %% Rep = t_from_form(Type, RecDict, TmpVarDict),
+ %% TmpVarTab = maps:to_list(List),
+ %% Rep = t_from_form(Type, RecDict, TmpVarTab),
Rep = t_any(), % not used for anything right now
Args = [t_any() || _ <- ArgNames],
t_opaque(Module, Name, Args, Rep)
@@ -1769,7 +1770,8 @@ mapdict_insert(E={_,_,_}, T) -> [E|T].
t_map_mandatoriness(), erl_type())
-> t_map_pair() | false),
erl_type(), erl_type()) -> t_map_dict().
-map_pairwise_merge(F, ?map(APairs, ADefK, ADefV), ?map(BPairs, BDefK, BDefV)) ->
+map_pairwise_merge(F, ?map(APairs, ADefK, ADefV),
+ ?map(BPairs, BDefK, BDefV)) ->
map_pairwise_merge(F, APairs, ADefK, ADefV, BPairs, BDefK, BDefV).
map_pairwise_merge(_, [], _, _, [], _, _) -> [];
@@ -4302,7 +4304,6 @@ t_to_string(?map(Pairs0,DefK,DefV), RecDict) ->
{Pairs, ExtraEl} =
case {DefK, DefV} of
{?none, ?none} -> {Pairs0, []};
- {?any, ?any} -> {Pairs0, ["..."]};
_ -> {Pairs0 ++ [{DefK,?opt,DefV}], []}
end,
Tos = fun(T) -> case T of
@@ -4414,33 +4415,30 @@ mod_name(Mod, Name) ->
-type type_names() :: [type_key() | record_key()].
--type mta() :: {module(), atom(), arity()}.
--type mra() :: {module(), atom(), arity()}.
--type site() :: {'type', mta()} | {'spec', mfa()} | {'record', mra()}.
+-type mta() :: {module(), atom(), arity()}.
+-type mra() :: {module(), atom(), arity()}.
+-type site() :: {'type', mta()} | {'spec', mfa()} | {'record', mra()}.
+-type cache_key() :: {module(), atom(), expand_depth(),
+ [erl_type()], type_names()}.
+-opaque cache() :: #{cache_key() => {erl_type(), expand_limit()}}.
--spec t_from_form(parse_form(), sets:set(mfa()),
- site(), mod_records()) -> erl_type().
+-spec t_from_form(parse_form(), sets:set(mfa()), site(), mod_records(),
+ var_table(), cache()) -> {erl_type(), cache()}.
-t_from_form(Form, ExpTypes, Site, RecDict) ->
- t_from_form(Form, ExpTypes, Site, RecDict, maps:new()).
-
--spec t_from_form(parse_form(), sets:set(mfa()),
- site(), mod_records(), var_table()) -> erl_type().
-
-t_from_form(Form, ExpTypes, Site, RecDict, VarDict) ->
- {T, _} = t_from_form1(Form, ExpTypes, Site, RecDict, VarDict),
- T.
+t_from_form(Form, ExpTypes, Site, RecDict, VarTab, Cache) ->
+ t_from_form1(Form, ExpTypes, Site, RecDict, VarTab, Cache).
%% Replace external types with with none().
-spec t_from_form_without_remote(parse_form(), site(), type_table()) ->
- erl_type().
+ {erl_type(), cache()}.
t_from_form_without_remote(Form, Site, TypeTable) ->
Module = site_module(Site),
RecDict = dict:from_list([{Module, TypeTable}]),
ExpTypes = replace_by_none,
- {T, _} = t_from_form1(Form, ExpTypes, Site, RecDict, maps:new()),
- T.
+ VarTab = var_table__new(),
+ Cache = cache__new(),
+ t_from_form1(Form, ExpTypes, Site, RecDict, VarTab, Cache).
%% REC_TYPE_LIMIT is used for limiting the depth of recursive types.
%% EXPAND_LIMIT is used for limiting the size of types by
@@ -4453,34 +4451,53 @@ t_from_form_without_remote(Form, Site, TypeTable) ->
-type expand_depth() :: integer().
+-record(from_form, {site :: site(),
+ xtypes :: sets:set(mfa()) | 'replace_by_none',
+ mrecs :: mod_records(),
+ vtab :: var_table(),
+ tnames :: type_names()}).
+
-spec t_from_form1(parse_form(), sets:set(mfa()) | 'replace_by_none',
- site(), mod_records(), var_table()) ->
- {erl_type(), expand_limit()}.
+ site(), mod_records(), var_table(), cache()) ->
+ {erl_type(), cache()}.
-t_from_form1(Form, ET, Site, MR, V) ->
+t_from_form1(Form, ET, Site, MR, V, C) ->
TypeNames = initial_typenames(Site),
- t_from_form1(Form, TypeNames, ET, Site, MR, V, ?EXPAND_DEPTH).
+ State = #from_form{site = Site,
+ xtypes = ET,
+ mrecs = MR,
+ vtab = V,
+ tnames = TypeNames},
+ L = ?EXPAND_LIMIT,
+ {T1, L1, C1} = from_form(Form, State, ?EXPAND_DEPTH, L, C),
+ if
+ L1 =< 0 ->
+ from_form_loop(Form, State, 1, L, C1);
+ true ->
+ {T1, C1}
+ end.
initial_typenames({type, _MTA}=Site) -> [Site];
initial_typenames({spec, _MFA}) -> [];
initial_typenames({record, _MRA}) -> [].
-t_from_form1(Form, TypeNames, ET, Site, MR, V, D) ->
- L = ?EXPAND_LIMIT,
- {T, L1} = t_from_form(Form, TypeNames, ET, Site, MR, V, D, L),
+from_form_loop(Form, State, D, Limit, C) ->
+ {T1, L1, C1} = from_form(Form, State, D, Limit, C),
+ Delta = Limit - L1,
if
- L1 =< 0, D > 1 ->
- D1 = D div 2,
- t_from_form1(Form, TypeNames, ET, Site, MR, V, D1);
+ %% Save some time by assuming next depth will exceed the limit.
+ Delta * 8 > Limit ->
+ {T1, C1};
true ->
- {T, L1}
+ D1 = D + 1,
+ from_form_loop(Form, State, D1, Limit, C1)
end.
--spec t_from_form(parse_form(), type_names(),
- sets:set(mfa()) | 'replace_by_none',
- site(), mod_records(), var_table(),
- expand_depth(), expand_limit())
- -> {erl_type(), expand_limit()}.
+-spec from_form(parse_form(),
+ #from_form{},
+ expand_depth(),
+ expand_limit(),
+ cache()) -> {erl_type(), expand_limit(), cache()}.
%% If there is something wrong with parse_form()
%% throw({error, io_lib:chars()} is called;
@@ -4490,330 +4507,336 @@ t_from_form1(Form, TypeNames, ET, Site, MR, V, D) ->
%%
%% It is assumed that site_module(S) can be found in MR.
-t_from_form(_, _TypeNames, _ET, _S, _MR, _V, D, L) when D =< 0 ; L =< 0 ->
- {t_any(), L};
-t_from_form({var, _L, '_'}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_any(), L};
-t_from_form({var, _L, Name}, _TypeNames, _ET, _S, _MR, V, _D, L) ->
+from_form(_, _S, D, L, C) when D =< 0 ; L =< 0 ->
+ {t_any(), L, C};
+from_form({var, _L, '_'}, _S, _D, L, C) ->
+ {t_any(), L, C};
+from_form({var, _L, Name}, S, _D, L, C) ->
+ V = S#from_form.vtab,
case maps:find(Name, V) of
- error -> {t_var(Name), L};
- {ok, Val} -> {Val, L}
+ error -> {t_var(Name), L, C};
+ {ok, Val} -> {Val, L, C}
end;
-t_from_form({ann_type, _L, [_Var, Type]}, TypeNames, ET, S, MR, V, D, L) ->
- t_from_form(Type, TypeNames, ET, S, MR, V, D, L);
-t_from_form({paren_type, _L, [Type]}, TypeNames, ET, S, MR, V, D, L) ->
- t_from_form(Type, TypeNames, ET, S, MR, V, D, L);
-t_from_form({remote_type, _L, [{atom, _, Module}, {atom, _, Type}, Args]},
- TypeNames, ET, S, MR, V, D, L) ->
- remote_from_form(Module, Type, Args, TypeNames, ET, S, MR, V, D, L);
-t_from_form({atom, _L, Atom}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_atom(Atom), L};
-t_from_form({integer, _L, Int}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_integer(Int), L};
-t_from_form({op, _L, _Op, _Arg} = Op, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
+from_form({ann_type, _L, [_Var, Type]}, S, D, L, C) ->
+ from_form(Type, S, D, L, C);
+from_form({paren_type, _L, [Type]}, S, D, L, C) ->
+ from_form(Type, S, D, L, C);
+from_form({remote_type, _L, [{atom, _, Module}, {atom, _, Type}, Args]},
+ S, D, L, C) ->
+ remote_from_form(Module, Type, Args, S, D, L, C);
+from_form({atom, _L, Atom}, _S, _D, L, C) ->
+ {t_atom(Atom), L, C};
+from_form({integer, _L, Int}, _S, _D, L, C) ->
+ {t_integer(Int), L, C};
+from_form({op, _L, _Op, _Arg} = Op, _S, _D, L, C) ->
case erl_eval:partial_eval(Op) of
{integer, _, Val} ->
- {t_integer(Val), L};
+ {t_integer(Val), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Op])})
end;
-t_from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
+from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _S, _D, L, C) ->
case erl_eval:partial_eval(Op) of
{integer, _, Val} ->
- {t_integer(Val), L};
+ {t_integer(Val), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Op])})
end;
-t_from_form({type, _L, any, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_any(), L};
-t_from_form({type, _L, arity, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_arity(), L};
-t_from_form({type, _L, atom, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_atom(), L};
-t_from_form({type, _L, binary, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_binary(), L};
-t_from_form({type, _L, binary, [Base, Unit]} = Type,
- _TypeNames, _ET, _S, _MR, _V, _D, L) ->
+from_form({type, _L, any, []}, _S, _D, L, C) ->
+ {t_any(), L, C};
+from_form({type, _L, arity, []}, _S, _D, L, C) ->
+ {t_arity(), L, C};
+from_form({type, _L, atom, []}, _S, _D, L, C) ->
+ {t_atom(), L, C};
+from_form({type, _L, binary, []}, _S, _D, L, C) ->
+ {t_binary(), L, C};
+from_form({type, _L, binary, [Base, Unit]} = Type, _S, _D, L, C) ->
case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of
{{integer, _, B}, {integer, _, U}} when B >= 0, U >= 0 ->
- {t_bitstr(U, B), L};
+ {t_bitstr(U, B), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Type])})
end;
-t_from_form({type, _L, bitstring, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_bitstr(), L};
-t_from_form({type, _L, bool, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_boolean(), L}; % XXX: Temporarily
-t_from_form({type, _L, boolean, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_boolean(), L};
-t_from_form({type, _L, byte, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_byte(), L};
-t_from_form({type, _L, char, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_char(), L};
-t_from_form({type, _L, float, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_float(), L};
-t_from_form({type, _L, function, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_fun(), L};
-t_from_form({type, _L, 'fun', []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_fun(), L};
-t_from_form({type, _L, 'fun', [{type, _, any}, Range]}, TypeNames,
- ET, S, MR, V, D, L) ->
- {T, L1} = t_from_form(Range, TypeNames, ET, S, MR, V, D - 1, L - 1),
- {t_fun(T), L1};
-t_from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]},
- TypeNames, ET, S, MR, V, D, L) ->
- {Dom1, L1} = list_from_form(Domain, TypeNames, ET, S, MR, V, D, L),
- {Ran1, L2} = t_from_form(Range, TypeNames, ET, S, MR, V, D, L1),
- {t_fun(Dom1, Ran1), L2};
-t_from_form({type, _L, identifier, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_identifier(), L};
-t_from_form({type, _L, integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_integer(), L};
-t_from_form({type, _L, iodata, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_iodata(), L};
-t_from_form({type, _L, iolist, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_iolist(), L};
-t_from_form({type, _L, list, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_list(), L};
-t_from_form({type, _L, list, [Type]}, TypeNames, ET, S, MR, V, D, L) ->
- {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D - 1, L - 1),
- {t_list(T), L1};
-t_from_form({type, _L, map, any}, TypeNames, ET, S, MR, V, D, L) ->
- builtin_type(map, t_map(), TypeNames, ET, S, MR, V, D, L);
-t_from_form({type, _L, map, List}, TypeNames, ET, S, MR, V, D, L) ->
- {Pairs1, L5} =
- fun PairsFromForm(_, L1) when L1 =< 0 -> {[{?any,?opt,?any}], L1};
- PairsFromForm([], L1) -> {[], L1};
- PairsFromForm([{type, _, Oper, [KF, VF]}|T], L1) ->
- {Key, L2} = t_from_form(KF, TypeNames, ET, S, MR, V, D - 1, L1),
- {Val, L3} = t_from_form(VF, TypeNames, ET, S, MR, V, D - 1, L2),
- {Pairs0, L4} = PairsFromForm(T, L3 - 1),
+from_form({type, _L, bitstring, []}, _S, _D, L, C) ->
+ {t_bitstr(), L, C};
+from_form({type, _L, bool, []}, _S, _D, L, C) ->
+ {t_boolean(), L, C}; % XXX: Temporarily
+from_form({type, _L, boolean, []}, _S, _D, L, C) ->
+ {t_boolean(), L, C};
+from_form({type, _L, byte, []}, _S, _D, L, C) ->
+ {t_byte(), L, C};
+from_form({type, _L, char, []}, _S, _D, L, C) ->
+ {t_char(), L, C};
+from_form({type, _L, float, []}, _S, _D, L, C) ->
+ {t_float(), L, C};
+from_form({type, _L, function, []}, _S, _D, L, C) ->
+ {t_fun(), L, C};
+from_form({type, _L, 'fun', []}, _S, _D, L, C) ->
+ {t_fun(), L, C};
+from_form({type, _L, 'fun', [{type, _, any}, Range]}, S, D, L, C) ->
+ {T, L1, C1} = from_form(Range, S, D - 1, L - 1, C),
+ {t_fun(T), L1, C1};
+from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]},
+ S, D, L, C) ->
+ {Dom1, L1, C1} = list_from_form(Domain, S, D, L, C),
+ {Ran1, L2, C2} = from_form(Range, S, D, L1, C1),
+ {t_fun(Dom1, Ran1), L2, C2};
+from_form({type, _L, identifier, []}, _S, _D, L, C) ->
+ {t_identifier(), L, C};
+from_form({type, _L, integer, []}, _S, _D, L, C) ->
+ {t_integer(), L, C};
+from_form({type, _L, iodata, []}, _S, _D, L, C) ->
+ {t_iodata(), L, C};
+from_form({type, _L, iolist, []}, _S, _D, L, C) ->
+ {t_iolist(), L, C};
+from_form({type, _L, list, []}, _S, _D, L, C) ->
+ {t_list(), L, C};
+from_form({type, _L, list, [Type]}, S, D, L, C) ->
+ {T, L1, C1} = from_form(Type, S, D - 1, L - 1, C),
+ {t_list(T), L1, C1};
+from_form({type, _L, map, any}, S, D, L, C) ->
+ builtin_type(map, t_map(), S, D, L, C);
+from_form({type, _L, map, List}, S, D0, L, C) ->
+ {Pairs1, L5, C5} =
+ fun PairsFromForm(_, L1, C1) when L1 =< 0 -> {[{?any,?opt,?any}], L1, C1};
+ PairsFromForm([], L1, C1) -> {[], L1, C1};
+ PairsFromForm([{type, _, Oper, [KF, VF]}|T], L1, C1) ->
+ D = D0 - 1,
+ {Key, L2, C2} = from_form(KF, S, D, L1, C1),
+ {Val, L3, C3} = from_form(VF, S, D, L2, C2),
+ {Pairs0, L4, C4} = PairsFromForm(T, L3 - 1, C3),
case Oper of
- map_field_assoc -> {[{Key,?opt, Val}|Pairs0], L4};
- map_field_exact -> {[{Key,?mand,Val}|Pairs0], L4}
+ map_field_assoc -> {[{Key,?opt, Val}|Pairs0], L4, C4};
+ map_field_exact -> {[{Key,?mand,Val}|Pairs0], L4, C4}
end
- end(List, L),
+ end(List, L, C),
try
{Pairs, DefK, DefV} = map_from_form(Pairs1, [], [], [], ?none, ?none),
- {t_map(Pairs, DefK, DefV), L5}
- catch none -> {t_none(), L5}
+ {t_map(Pairs, DefK, DefV), L5, C5}
+ catch none -> {t_none(), L5, C5}
end;
-t_from_form({type, _L, mfa, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_mfa(), L};
-t_from_form({type, _L, module, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_module(), L};
-t_from_form({type, _L, nil, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_nil(), L};
-t_from_form({type, _L, neg_integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_neg_integer(), L};
-t_from_form({type, _L, non_neg_integer, []}, _TypeNames, _ET, _S, _MR,
- _V, _D, L) ->
- {t_non_neg_integer(), L};
-t_from_form({type, _L, no_return, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_unit(), L};
-t_from_form({type, _L, node, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_node(), L};
-t_from_form({type, _L, none, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_none(), L};
-t_from_form({type, _L, nonempty_list, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_nonempty_list(), L};
-t_from_form({type, _L, nonempty_list, [Type]}, TypeNames, ET, S, MR, V, D, L) ->
- {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D, L - 1),
- {t_nonempty_list(T), L1};
-t_from_form({type, _L, nonempty_improper_list, [Cont, Term]}, TypeNames,
- ET, S, MR, V, D, L) ->
- {T1, L1} = t_from_form(Cont, TypeNames, ET, S, MR, V, D, L - 1),
- {T2, L2} = t_from_form(Term, TypeNames, ET, S, MR, V, D, L1),
- {t_cons(T1, T2), L2};
-t_from_form({type, _L, nonempty_maybe_improper_list, []}, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
- {t_cons(?any, ?any), L};
-t_from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]},
- TypeNames, ET, S, MR, V, D, L) ->
- {T1, L1} = t_from_form(Cont, TypeNames, ET, S, MR, V, D, L - 1),
- {T2, L2} = t_from_form(Term, TypeNames, ET, S, MR, V, D, L1),
- {t_cons(T1, T2), L2};
-t_from_form({type, _L, nonempty_string, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_nonempty_string(), L};
-t_from_form({type, _L, number, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_number(), L};
-t_from_form({type, _L, pid, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_pid(), L};
-t_from_form({type, _L, port, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_port(), L};
-t_from_form({type, _L, pos_integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_pos_integer(), L};
-t_from_form({type, _L, maybe_improper_list, []}, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
- {t_maybe_improper_list(), L};
-t_from_form({type, _L, maybe_improper_list, [Content, Termination]},
- TypeNames, ET, S, MR, V, D, L) ->
- {T1, L1} = t_from_form(Content, TypeNames, ET, S, MR, V, D, L - 1),
- {T2, L2} = t_from_form(Termination, TypeNames, ET, S, MR, V, D, L1),
- {t_maybe_improper_list(T1, T2), L2};
-t_from_form({type, _L, product, Elements}, TypeNames, ET, S, MR, V, D, L) ->
- {Lst, L1} = list_from_form(Elements, TypeNames, ET, S, MR, V, D - 1, L),
- {t_product(Lst), L1};
-t_from_form({type, _L, range, [From, To]} = Type,
- _TypeNames, _ET, _S, _MR, _V, _D, L) ->
+from_form({type, _L, mfa, []}, _S, _D, L, C) ->
+ {t_mfa(), L, C};
+from_form({type, _L, module, []}, _S, _D, L, C) ->
+ {t_module(), L, C};
+from_form({type, _L, nil, []}, _S, _D, L, C) ->
+ {t_nil(), L, C};
+from_form({type, _L, neg_integer, []}, _S, _D, L, C) ->
+ {t_neg_integer(), L, C};
+from_form({type, _L, non_neg_integer, []}, _S, _D, L, C) ->
+ {t_non_neg_integer(), L, C};
+from_form({type, _L, no_return, []}, _S, _D, L, C) ->
+ {t_unit(), L, C};
+from_form({type, _L, node, []}, _S, _D, L, C) ->
+ {t_node(), L, C};
+from_form({type, _L, none, []}, _S, _D, L, C) ->
+ {t_none(), L, C};
+from_form({type, _L, nonempty_list, []}, _S, _D, L, C) ->
+ {t_nonempty_list(), L, C};
+from_form({type, _L, nonempty_list, [Type]}, S, D, L, C) ->
+ {T, L1, C1} = from_form(Type, S, D, L - 1, C),
+ {t_nonempty_list(T), L1, C1};
+from_form({type, _L, nonempty_improper_list, [Cont, Term]}, S, D, L, C) ->
+ {T1, L1, C1} = from_form(Cont, S, D, L - 1, C),
+ {T2, L2, C2} = from_form(Term, S, D, L1, C1),
+ {t_cons(T1, T2), L2, C2};
+from_form({type, _L, nonempty_maybe_improper_list, []}, _S, _D, L, C) ->
+ {t_cons(?any, ?any), L, C};
+from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]},
+ S, D, L, C) ->
+ {T1, L1, C1} = from_form(Cont, S, D, L - 1, C),
+ {T2, L2, C2} = from_form(Term, S, D, L1, C1),
+ {t_cons(T1, T2), L2, C2};
+from_form({type, _L, nonempty_string, []}, _S, _D, L, C) ->
+ {t_nonempty_string(), L, C};
+from_form({type, _L, number, []}, _S, _D, L, C) ->
+ {t_number(), L, C};
+from_form({type, _L, pid, []}, _S, _D, L, C) ->
+ {t_pid(), L, C};
+from_form({type, _L, port, []}, _S, _D, L, C) ->
+ {t_port(), L, C};
+from_form({type, _L, pos_integer, []}, _S, _D, L, C) ->
+ {t_pos_integer(), L, C};
+from_form({type, _L, maybe_improper_list, []}, _S, _D, L, C) ->
+ {t_maybe_improper_list(), L, C};
+from_form({type, _L, maybe_improper_list, [Content, Termination]},
+ S, D, L, C) ->
+ {T1, L1, C1} = from_form(Content, S, D, L - 1, C),
+ {T2, L2, C2} = from_form(Termination, S, D, L1, C1),
+ {t_maybe_improper_list(T1, T2), L2, C2};
+from_form({type, _L, product, Elements}, S, D, L, C) ->
+ {Lst, L1, C1} = list_from_form(Elements, S, D - 1, L, C),
+ {t_product(Lst), L1, C1};
+from_form({type, _L, range, [From, To]} = Type, _S, _D, L, C) ->
case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
{{integer, _, FromVal}, {integer, _, ToVal}} ->
- {t_from_range(FromVal, ToVal), L};
+ {t_from_range(FromVal, ToVal), L, C};
_ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Type])})
end;
-t_from_form({type, _L, record, [Name|Fields]}, TypeNames, ET, S, MR, V, D, L) ->
- record_from_form(Name, Fields, TypeNames, ET, S, MR, V, D, L);
-t_from_form({type, _L, reference, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_reference(), L};
-t_from_form({type, _L, string, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_string(), L};
-t_from_form({type, _L, term, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_any(), L};
-t_from_form({type, _L, timeout, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_timeout(), L};
-t_from_form({type, _L, tuple, any}, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {t_tuple(), L};
-t_from_form({type, _L, tuple, Args}, TypeNames, ET, S, MR, V, D, L) ->
- {Lst, L1} = list_from_form(Args, TypeNames, ET, S, MR, V, D - 1, L),
- {t_tuple(Lst), L1};
-t_from_form({type, _L, union, Args}, TypeNames, ET, S, MR, V, D, L) ->
- {Lst, L1} = list_from_form(Args, TypeNames, ET, S, MR, V, D, L),
- {t_sup(Lst), L1};
-t_from_form({user_type, _L, Name, Args}, TypeNames, ET, S, MR, V, D, L) ->
- type_from_form(Name, Args, TypeNames, ET, S, MR, V, D, L);
-t_from_form({type, _L, Name, Args}, TypeNames, ET, S, MR, V, D, L) ->
+from_form({type, _L, record, [Name|Fields]}, S, D, L, C) ->
+ record_from_form(Name, Fields, S, D, L, C);
+from_form({type, _L, reference, []}, _S, _D, L, C) ->
+ {t_reference(), L, C};
+from_form({type, _L, string, []}, _S, _D, L, C) ->
+ {t_string(), L, C};
+from_form({type, _L, term, []}, _S, _D, L, C) ->
+ {t_any(), L, C};
+from_form({type, _L, timeout, []}, _S, _D, L, C) ->
+ {t_timeout(), L, C};
+from_form({type, _L, tuple, any}, _S, _D, L, C) ->
+ {t_tuple(), L, C};
+from_form({type, _L, tuple, Args}, S, D, L, C) ->
+ {Lst, L1, C1} = list_from_form(Args, S, D - 1, L, C),
+ {t_tuple(Lst), L1, C1};
+from_form({type, _L, union, Args}, S, D, L, C) ->
+ {Lst, L1, C1} = list_from_form(Args, S, D, L, C),
+ {t_sup(Lst), L1, C1};
+from_form({user_type, _L, Name, Args}, S, D, L, C) ->
+ type_from_form(Name, Args, S, D, L, C);
+from_form({type, _L, Name, Args}, S, D, L, C) ->
%% Compatibility: modules compiled before Erlang/OTP 18.0.
- type_from_form(Name, Args, TypeNames, ET, S, MR, V, D, L);
-t_from_form({opaque, _L, Name, {Mod, Args, Rep}}, _TypeNames,
- _ET, _S, _MR, _V, _D, L) ->
+ type_from_form(Name, Args, S, D, L, C);
+from_form({opaque, _L, Name, {Mod, Args, Rep}}, _S, _D, L, C) ->
%% XXX. To be removed.
- {t_opaque(Mod, Name, Args, Rep), L}.
+ {t_opaque(Mod, Name, Args, Rep), L, C}.
-builtin_type(Name, Type, TypeNames, ET, Site, MR, V, D, L) ->
+builtin_type(Name, Type, S, D, L, C) ->
+ #from_form{site = Site, mrecs = MR} = S,
M = site_module(Site),
case dict:find(M, MR) of
{ok, R} ->
case lookup_type(Name, 0, R) of
{_, {{_M, _FL, _F, _A}, _T}} ->
- type_from_form(Name, [], TypeNames, ET, Site, MR, V, D, L);
+ type_from_form(Name, [], S, D, L, C);
error ->
- {Type, L}
+ {Type, L, C}
end;
error ->
- {Type, L}
+ {Type, L, C}
end.
-type_from_form(Name, Args, TypeNames, ET, Site0, MR, V, D, L) ->
+type_from_form(Name, Args, S, D, L, C) ->
+ #from_form{site = Site, mrecs = MR, tnames = TypeNames} = S,
ArgsLen = length(Args),
- Module = site_module(Site0),
- {ok, R} = dict:find(Module, MR),
+ Module = site_module(Site),
TypeName = {type, {Module, Name, ArgsLen}},
+ case can_unfold_more(TypeName, TypeNames) of
+ true ->
+ {ok, R} = dict:find(Module, MR),
+ type_from_form1(Name, Args, ArgsLen, R, TypeName, TypeNames,
+ S, D, L, C);
+ false ->
+ {t_any(), L, C}
+ end.
+
+type_from_form1(Name, Args, ArgsLen, R, TypeName, TypeNames, S, D, L, C) ->
case lookup_type(Name, ArgsLen, R) of
- {type, {{Module, _FileName, Form, ArgNames}, _Type}} ->
- case can_unfold_more(TypeName, TypeNames) of
- true ->
- NewTypeNames = [TypeName|TypeNames],
- {ArgTypes, L1} =
- list_from_form(Args, TypeNames, ET, Site0, MR, V, D, L),
- List = lists:zip(ArgNames, ArgTypes),
- TmpV = maps:from_list(List),
- Site = TypeName,
- t_from_form(Form, NewTypeNames, ET, Site, MR, TmpV, D, L1);
- false ->
- {t_any(), L}
- end;
- {opaque, {{Module, _FileName, Form, ArgNames}, Type}} ->
- case can_unfold_more(TypeName, TypeNames) of
- true ->
- NewTypeNames = [TypeName|TypeNames],
- {ArgTypes, L1} =
- list_from_form(Args, NewTypeNames, ET, Site0, MR, V, D, L),
+ {Tag, {{Module, _FileName, Form, ArgNames}, Type}} ->
+ NewTypeNames = [TypeName|TypeNames],
+ S1 = S#from_form{tnames = NewTypeNames},
+ {ArgTypes, L1, C1} = list_from_form(Args, S1, D, L, C),
+ CKey = cache_key(Module, Name, ArgTypes, TypeNames, D),
+ case cache_find(CKey, C) of
+ {CachedType, DeltaL} ->
+ {CachedType, L1 - DeltaL, C};
+ error ->
List = lists:zip(ArgNames, ArgTypes),
TmpV = maps:from_list(List),
- Site = TypeName,
- {Rep, L2} =
- t_from_form(Form, NewTypeNames, ET, Site, MR, TmpV, D, L1),
- Rep1 = choose_opaque_type(Rep, Type),
- Rep2 = case cannot_have_opaque(Rep1, TypeName, TypeNames) of
- true -> Rep1;
- false ->
- ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
- t_opaque(Module, Name, ArgTypes2, Rep1)
- end,
- {Rep2, L2};
- false -> {t_any(), L}
+ S2 = S1#from_form{site = TypeName, vtab = TmpV},
+ Fun = fun(DD, LL) -> from_form(Form, S2, DD, LL, C1) end,
+ {NewType, L3, C3} =
+ case Tag of
+ type ->
+ recur_limit(Fun, D, L1, TypeName, TypeNames);
+ opaque ->
+ {Rep, L2, C2} = recur_limit(Fun, D, L1, TypeName, TypeNames),
+ Rep1 = choose_opaque_type(Rep, Type),
+ Rep2 = case cannot_have_opaque(Rep1, TypeName, TypeNames) of
+ true -> Rep1;
+ false ->
+ ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
+ t_opaque(Module, Name, ArgTypes2, Rep1)
+ end,
+ {Rep2, L2, C2}
+ end,
+ C4 = cache_put(CKey, NewType, L1 - L3, C3),
+ {NewType, L3, C4}
end;
error ->
- Msg = io_lib:format("Unable to find type ~w/~w\n", [Name, ArgsLen]),
+ Msg = io_lib:format("Unable to find type ~w/~w\n",
+ [Name, ArgsLen]),
throw({error, Msg})
end.
-remote_from_form(RemMod, Name, Args, TypeNames, ET, S, MR, V, D, L) ->
+remote_from_form(RemMod, Name, Args, S, D, L, C) ->
+ #from_form{xtypes = ET, mrecs = MR, tnames = TypeNames} = S,
if
ET =:= replace_by_none ->
- {t_none(), L};
+ {t_none(), L, C};
true ->
ArgsLen = length(Args),
MFA = {RemMod, Name, ArgsLen},
case dict:find(RemMod, MR) of
error ->
self() ! {self(), ext_types, MFA},
- {t_any(), L};
+ {t_any(), L, C};
{ok, RemDict} ->
- RemType = {type, MFA},
case sets:is_element(MFA, ET) of
true ->
- case lookup_type(Name, ArgsLen, RemDict) of
- {type, {{_Mod, _FileLine, Form, ArgNames}, _Type}} ->
- case can_unfold_more(RemType, TypeNames) of
- true ->
- NewTypeNames = [RemType|TypeNames],
- {ArgTypes, L1} = list_from_form(Args, TypeNames,
- ET, S, MR, V, D, L),
- List = lists:zip(ArgNames, ArgTypes),
- TmpVarDict = maps:from_list(List),
- Site = RemType,
- t_from_form(Form, NewTypeNames, ET,
- Site, MR, TmpVarDict, D, L1);
- false ->
- {t_any(), L}
- end;
- {opaque, {{Mod, _FileLine, Form, ArgNames}, Type}} ->
- case can_unfold_more(RemType, TypeNames) of
- true ->
- NewTypeNames = [RemType|TypeNames],
- {ArgTypes, L1} = list_from_form(Args, NewTypeNames,
- ET, S, MR, V, D, L),
- List = lists:zip(ArgNames, ArgTypes),
- TmpVarDict = maps:from_list(List),
- Site = RemType,
- {NewRep, L2} =
- t_from_form(Form, NewTypeNames, ET, Site, MR,
- TmpVarDict, D, L1),
- NewRep1 = choose_opaque_type(NewRep, Type),
- NewRep2 =
- case
- cannot_have_opaque(NewRep1, RemType, TypeNames)
- of
- true -> NewRep1;
- false ->
- ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
- t_opaque(Mod, Name, ArgTypes2, NewRep1)
- end,
- {NewRep2, L2};
- false ->
- {t_any(), L}
- end;
- error ->
- Msg = io_lib:format("Unable to find remote type ~w:~w()\n",
- [RemMod, Name]),
- throw({error, Msg})
+ RemType = {type, MFA},
+ case can_unfold_more(RemType, TypeNames) of
+ true ->
+ remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict,
+ RemType, TypeNames, S, D, L, C);
+ false ->
+ {t_any(), L, C}
end;
false ->
self() ! {self(), ext_types, {RemMod, Name, ArgsLen}},
- {t_any(), L}
+ {t_any(), L, C}
end
end
end.
+remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict, RemType, TypeNames,
+ S, D, L, C) ->
+ case lookup_type(Name, ArgsLen, RemDict) of
+ {Tag, {{Mod, _FileLine, Form, ArgNames}, Type}} ->
+ NewTypeNames = [RemType|TypeNames],
+ S1 = S#from_form{tnames = NewTypeNames},
+ {ArgTypes, L1, C1} = list_from_form(Args, S1, D, L, C),
+ CKey = cache_key(RemMod, Name, ArgTypes, TypeNames, D),
+ %% case error of
+ case cache_find(CKey, C) of
+ {CachedType, DeltaL} ->
+ {CachedType, L - DeltaL, C};
+ error ->
+ List = lists:zip(ArgNames, ArgTypes),
+ TmpVarTab = maps:from_list(List),
+ S2 = S1#from_form{site = RemType, vtab = TmpVarTab},
+ Fun = fun(DD, LL) -> from_form(Form, S2, DD, LL, C1) end,
+ {NewType, L3, C3} =
+ case Tag of
+ type ->
+ recur_limit(Fun, D, L1, RemType, TypeNames);
+ opaque ->
+ {NewRep, L2, C2} = recur_limit(Fun, D, L1, RemType, TypeNames),
+ NewRep1 = choose_opaque_type(NewRep, Type),
+ NewRep2 =
+ case cannot_have_opaque(NewRep1, RemType, TypeNames) of
+ true -> NewRep1;
+ false ->
+ ArgTypes2 = subst_all_vars_to_any_list(ArgTypes),
+ t_opaque(Mod, Name, ArgTypes2, NewRep1)
+ end,
+ {NewRep2, L2, C2}
+ end,
+ C4 = cache_put(CKey, NewType, L1 - L3, C3),
+ {NewType, L3, C4}
+ end;
+ error ->
+ Msg = io_lib:format("Unable to find remote type ~w:~w()\n",
+ [RemMod, Name]),
+ throw({error, Msg})
+ end.
+
subst_all_vars_to_any_list(Types) ->
[subst_all_vars_to_any(Type) || Type <- Types].
@@ -4836,63 +4859,67 @@ choose_opaque_type(Type, DeclType) ->
false -> DeclType
end.
-record_from_form({atom, _, Name}, ModFields, TypeNames, ET, S, MR, V, D, L) ->
- case can_unfold_more({record, Name}, TypeNames) of
+record_from_form({atom, _, Name}, ModFields, S, D0, L0, C) ->
+ #from_form{site = Site, mrecs = MR, tnames = TypeNames} = S,
+ RecordType = {record, Name},
+ case can_unfold_more(RecordType, TypeNames) of
true ->
- M = site_module(S),
+ M = site_module(Site),
{ok, R} = dict:find(M, MR),
case lookup_record(Name, R) of
{ok, DeclFields} ->
- NewTypeNames = [{record, Name}|TypeNames],
- S1 = {record, {M, Name, length(DeclFields)}},
- {GetModRec, L1} = get_mod_record(ModFields, DeclFields,
- NewTypeNames, ET, S1, MR, V, D, L),
- case GetModRec of
- {error, FieldName} ->
- throw({error, io_lib:format("Illegal declaration of #~w{~w}\n",
- [Name, FieldName])});
- {ok, NewFields} ->
- {NewFields1, L2} =
- fields_from_form(NewFields, NewTypeNames, ET, S1, MR,
- maps:new(), D, L1),
- Rec = t_tuple(
- [t_atom(Name)|[Type
- || {_FieldName, Type} <- NewFields1]]),
- {Rec, L2}
- end;
+ NewTypeNames = [RecordType|TypeNames],
+ Site1 = {record, {M, Name, length(DeclFields)}},
+ S1 = S#from_form{site = Site1, tnames = NewTypeNames},
+ Fun = fun(D, L) ->
+ {GetModRec, L1, C1} =
+ get_mod_record(ModFields, DeclFields, S1, D, L, C),
+ case GetModRec of
+ {error, FieldName} ->
+ throw({error,
+ io_lib:format("Illegal declaration of #~w{~w}\n",
+ [Name, FieldName])});
+ {ok, NewFields} ->
+ S2 = S1#from_form{vtab = var_table__new()},
+ {NewFields1, L2, C2} =
+ fields_from_form(NewFields, S2, D, L1, C1),
+ Rec = t_tuple(
+ [t_atom(Name)|[Type
+ || {_FieldName, Type} <- NewFields1]]),
+ {Rec, L2, C2}
+ end
+ end,
+ recur_limit(Fun, D0, L0, RecordType, TypeNames);
error ->
throw({error, io_lib:format("Unknown record #~w{}\n", [Name])})
end;
false ->
- {t_any(), L}
+ {t_any(), L0, C}
end.
-get_mod_record([], DeclFields, _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {{ok, DeclFields}, L};
-get_mod_record(ModFields, DeclFields, TypeNames, ET, S, MR, V, D, L) ->
+get_mod_record([], DeclFields, _S, _D, L, C) ->
+ {{ok, DeclFields}, L, C};
+get_mod_record(ModFields, DeclFields, S, D, L, C) ->
DeclFieldsDict = lists:keysort(1, DeclFields),
- {ModFieldsDict, L1} =
- build_field_dict(ModFields, TypeNames, ET, S, MR, V, D, L),
+ {ModFieldsDict, L1, C1} = build_field_dict(ModFields, S, D, L, C),
case get_mod_record_types(DeclFieldsDict, ModFieldsDict, []) of
- {error, _FieldName} = Error -> {Error, L1};
+ {error, _FieldName} = Error -> {Error, L1, C1};
{ok, FinalKeyDict} ->
Fields = [lists:keyfind(FieldName, 1, FinalKeyDict)
|| {FieldName, _, _} <- DeclFields],
- {{ok, Fields}, L1}
+ {{ok, Fields}, L1, C1}
end.
-build_field_dict(FieldTypes, TypeNames, ET, S, MR, V, D, L) ->
- build_field_dict(FieldTypes, TypeNames, ET, S, MR, V, D, L, []).
+build_field_dict(FieldTypes, S, D, L, C) ->
+ build_field_dict(FieldTypes, S, D, L, C, []).
build_field_dict([{type, _, field_type, [{atom, _, Name}, Type]}|Left],
- TypeNames, ET, S, MR, V, D, L, Acc) ->
- {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D, L - 1),
+ S, D, L, C, Acc) ->
+ {T, L1, C1} = from_form(Type, S, D, L - 1, C),
NewAcc = [{Name, Type, T}|Acc],
- {Dict, L2} =
- build_field_dict(Left, TypeNames, ET, S, MR, V, D, L1, NewAcc),
- {Dict, L2};
-build_field_dict([], _TypeNames, _ET, _S, _MR, _V, _D, L, Acc) ->
- {lists:keysort(1, Acc), L}.
+ build_field_dict(Left, S, D, L1, C1, NewAcc);
+build_field_dict([], _S, _D, L, C, Acc) ->
+ {lists:keysort(1, Acc), L, C}.
get_mod_record_types([{FieldName, _Abstr, _DeclType}|Left1],
[{FieldName, TypeForm, ModType}|Left2],
@@ -4910,20 +4937,19 @@ get_mod_record_types(_, [{FieldName2, _FormType, _ModType}|_], _Acc) ->
%% It is important to create a limited version of the record type
%% since nested record types can otherwise easily result in huge
%% terms.
-fields_from_form([], _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {[], L};
-fields_from_form([{Name, Abstr, _Type}|Tail], TypeNames, ET, S, MR,
- V, D, L) ->
- {T, L1} = t_from_form(Abstr, TypeNames, ET, S, MR, V, D, L),
- {F, L2} = fields_from_form(Tail, TypeNames, ET, S, MR, V, D, L1),
- {[{Name, T}|F], L2}.
-
-list_from_form([], _TypeNames, _ET, _S, _MR, _V, _D, L) ->
- {[], L};
-list_from_form([H|Tail], TypeNames, ET, S, MR, V, D, L) ->
- {H1, L1} = t_from_form(H, TypeNames, ET, S, MR, V, D, L - 1),
- {T1, L2} = list_from_form(Tail, TypeNames, ET, S, MR, V, D, L1),
- {[H1|T1], L2}.
+fields_from_form([], _S, _D, L, C) ->
+ {[], L, C};
+fields_from_form([{Name, Abstr, _Type}|Tail], S, D, L, C) ->
+ {T, L1, C1} = from_form(Abstr, S, D, L, C),
+ {F, L2, C2} = fields_from_form(Tail, S, D, L1, C1),
+ {[{Name, T}|F], L2, C2}.
+
+list_from_form([], _S, _D, L, C) ->
+ {[], L, C};
+list_from_form([H|Tail], S, D, L, C) ->
+ {H1, L1, C1} = from_form(H, S, D, L - 1, C),
+ {T1, L2, C2} = list_from_form(Tail, S, D, L1, C1),
+ {[H1|T1], L2, C2}.
%% Sorts, combines non-singleton pairs, and applies precendence and
%% mandatoriness rules.
@@ -4969,80 +4995,140 @@ promote_to_mand(MKs, [E={K,_,V}|T]) ->
false -> E
end|promote_to_mand(MKs, T)].
--spec t_check_record_fields(parse_form(), sets:set(mfa()), site(),
- mod_records()) -> ok.
+-define(RECUR_EXPAND_LIMIT, 10).
+-define(RECUR_EXPAND_DEPTH, 2).
-t_check_record_fields(Form, ExpTypes, Site, RecDict) ->
- t_check_record_fields(Form, ExpTypes, Site, RecDict, maps:new()).
+%% If more of the limited resources is spent on the non-recursive
+%% forms, more warnings are found. And the analysis is also a bit
+%% faster.
+%%
+%% Setting REC_TYPE_LIMIT to 1 would work also work well.
+
+recur_limit(Fun, D, L, _, _) when L =< ?RECUR_EXPAND_DEPTH,
+ D =< ?RECUR_EXPAND_LIMIT ->
+ Fun(D, L);
+recur_limit(Fun, D, L, TypeName, TypeNames) ->
+ case is_recursive(TypeName, TypeNames) of
+ true ->
+ {T, L1, C1} = Fun(?RECUR_EXPAND_DEPTH, ?RECUR_EXPAND_LIMIT),
+ {T, L - L1, C1};
+ false ->
+ Fun(D, L)
+ end.
-spec t_check_record_fields(parse_form(), sets:set(mfa()), site(),
- mod_records(), var_table()) -> ok.
+ mod_records(), var_table(), cache()) -> cache().
+
+t_check_record_fields(Form, ExpTypes, Site, RecDict, VarTable, Cache) ->
+ State = #from_form{site = Site,
+ xtypes = ExpTypes,
+ mrecs = RecDict,
+ vtab = VarTable,
+ tnames = []},
+ check_record_fields(Form, State, Cache).
+
+-spec check_record_fields(parse_form(), #from_form{}, cache()) -> cache().
%% If there is something wrong with parse_form()
%% throw({error, io_lib:chars()} is called.
-t_check_record_fields({var, _L, _}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({ann_type, _L, [_Var, Type]}, ET, S, MR, V) ->
- t_check_record_fields(Type, ET, S, MR, V);
-t_check_record_fields({paren_type, _L, [Type]}, ET, S, MR, V) ->
- t_check_record_fields(Type, ET, S, MR, V);
-t_check_record_fields({remote_type, _L, [{atom, _, _}, {atom, _, _}, Args]},
- ET, S, MR, V) ->
- list_check_record_fields(Args, ET, S, MR, V);
-t_check_record_fields({atom, _L, _}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({integer, _L, _}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({op, _L, _Op, _Arg}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({op, _L, _Op, _Arg1, _Arg2}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({type, _L, tuple, any}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({type, _L, map, any}, _ET, _S, _MR, _V) -> ok;
-t_check_record_fields({type, _L, binary, [_Base, _Unit]}, _ET, _S, _MR, _V) ->
- ok;
-t_check_record_fields({type, _L, 'fun', [{type, _, any}, Range]},
- ET, S, MR, V) ->
- t_check_record_fields(Range, ET, S, MR, V);
-t_check_record_fields({type, _L, range, [_From, _To]}, _ET, _S, _MR, _V) ->
- ok;
-t_check_record_fields({type, _L, record, [Name|Fields]}, ET, S, MR, V) ->
- check_record(Name, Fields, ET, S, MR, V);
-t_check_record_fields({type, _L, _, Args}, ET, S, MR, V) ->
- list_check_record_fields(Args, ET, S, MR, V);
-t_check_record_fields({user_type, _L, _Name, Args}, ET, S, MR, V) ->
- list_check_record_fields(Args, ET, S, MR, V).
-
-check_record({atom, _, Name}, ModFields, ET, Site, MR, V) ->
+check_record_fields({var, _L, _}, _S, C) -> C;
+check_record_fields({ann_type, _L, [_Var, Type]}, S, C) ->
+ check_record_fields(Type, S, C);
+check_record_fields({paren_type, _L, [Type]}, S, C) ->
+ check_record_fields(Type, S, C);
+check_record_fields({remote_type, _L, [{atom, _, _}, {atom, _, _}, Args]},
+ S, C) ->
+ list_check_record_fields(Args, S, C);
+check_record_fields({atom, _L, _}, _S, C) -> C;
+check_record_fields({integer, _L, _}, _S, C) -> C;
+check_record_fields({op, _L, _Op, _Arg}, _S, C) -> C;
+check_record_fields({op, _L, _Op, _Arg1, _Arg2}, _S, C) -> C;
+check_record_fields({type, _L, tuple, any}, _S, C) -> C;
+check_record_fields({type, _L, map, any}, _S, C) -> C;
+check_record_fields({type, _L, binary, [_Base, _Unit]}, _S, C) -> C;
+check_record_fields({type, _L, 'fun', [{type, _, any}, Range]}, S, C) ->
+ check_record_fields(Range, S, C);
+check_record_fields({type, _L, range, [_From, _To]}, _S, C) -> C;
+check_record_fields({type, _L, record, [Name|Fields]}, S, C) ->
+ check_record(Name, Fields, S, C);
+check_record_fields({type, _L, _, Args}, S, C) ->
+ list_check_record_fields(Args, S, C);
+check_record_fields({user_type, _L, _Name, Args}, S, C) ->
+ list_check_record_fields(Args, S, C).
+
+check_record({atom, _, Name}, ModFields, S, C) ->
+ #from_form{site = Site, mrecs = MR} = S,
M = site_module(Site),
{ok, R} = dict:find(M, MR),
{ok, DeclFields} = lookup_record(Name, R),
- case check_fields(Name, ModFields, DeclFields, ET, Site, MR, V) of
+ case check_fields(Name, ModFields, DeclFields, S, C) of
{error, FieldName} ->
throw({error, io_lib:format("Illegal declaration of #~w{~w}\n",
[Name, FieldName])});
- ok -> ok
+ C1 -> C1
end.
check_fields(RecName, [{type, _, field_type, [{atom, _, Name}, Abstr]}|Left],
- DeclFields, ET, Site0, MR, V) ->
+ DeclFields, S, C) ->
+ #from_form{site = Site0, xtypes = ET, mrecs = MR, vtab = V} = S,
M = site_module(Site0),
Site = {record, {M, RecName, length(DeclFields)}},
- Type = t_from_form(Abstr, ET, Site, MR, V),
+ {Type, C1} = t_from_form(Abstr, ET, Site, MR, V, C),
{Name, _, DeclType} = lists:keyfind(Name, 1, DeclFields),
TypeNoVars = subst_all_vars_to_any(Type),
case t_is_subtype(TypeNoVars, DeclType) of
false -> {error, Name};
- true -> check_fields(RecName, Left, DeclFields, ET, Site0, MR, V)
+ true -> check_fields(RecName, Left, DeclFields, S, C1)
end;
-check_fields(_RecName, [], _Decl, _ET, _Site, _MR, _V) ->
- ok.
+check_fields(_RecName, [], _Decl, _S, C) ->
+ C.
-list_check_record_fields([], _ET, _S, _MR, _V) ->
- ok;
-list_check_record_fields([H|Tail], ET, S, MR, V) ->
- ok = t_check_record_fields(H, ET, S, MR, V),
- list_check_record_fields(Tail, ET, S, MR, V).
+list_check_record_fields([], _S, C) ->
+ C;
+list_check_record_fields([H|Tail], S, C) ->
+ C1 = check_record_fields(H, S, C),
+ list_check_record_fields(Tail, S, C1).
site_module({_, {Module, _, _}}) ->
Module.
+-spec cache__new() -> cache().
+
+cache__new() ->
+ maps:new().
+
+-spec cache_key(module(), atom(), [erl_type()],
+ type_names(), expand_depth()) -> cache_key().
+
+%% If TypeNames is left out from the key, the cache is smaller, and
+%% the form-to-type translation is faster. But it would be a shame if,
+%% for example, any() is used, where a more complex type should be
+%% used. There is also a slight risk of creating unnecessarily big
+%% types.
+
+cache_key(Module, Name, ArgTypes, TypeNames, D) ->
+ {Module, Name, D, ArgTypes, TypeNames}.
+
+-spec cache_find(cache_key(), cache()) ->
+ {erl_type(), expand_limit()} | 'error'.
+
+cache_find(Key, Cache) ->
+ case maps:find(Key, Cache) of
+ {ok, Value} ->
+ Value;
+ error ->
+ error
+ end.
+
+-spec cache_put(cache_key(), erl_type(), expand_limit(), cache()) -> cache().
+
+cache_put(_Key, _Type, DeltaL, Cache) when DeltaL < 0 ->
+ %% The type is truncated; do not reuse it.
+ Cache;
+cache_put(Key, Type, DeltaL, Cache) ->
+ maps:put(Key, {Type, DeltaL}, Cache).
+
-spec t_var_names([erl_type()]) -> [atom()].
t_var_names([{var, _, Name}|L]) when L =/= '_' ->
@@ -5141,10 +5227,15 @@ t_form_to_string({type, _L, Name, []} = T) ->
M = mod,
D0 = dict:new(),
MR = dict:from_list([{M, D0}]),
- S = {type, {M,Name,0}},
- V = #{},
- {T1, _} =
- t_from_form(T, [], sets:new(), S, MR, V, _Deep=1000, _ALot=100000),
+ Site = {type, {M,Name,0}},
+ V = var_table__new(),
+ C = cache__new(),
+ State = #from_form{site = Site,
+ xtypes = sets:new(),
+ mrecs = MR,
+ vtab = V,
+ tnames = []},
+ {T1, _, _} = from_form(T, State, _Deep=1000, _ALot=1000000, C),
t_to_string(T1)
catch throw:{error, _} -> atom_to_string(Name) ++ "()"
end;
@@ -5220,6 +5311,7 @@ lookup_record(Tag, Arity, RecDict) when is_atom(Tag) ->
error -> error
end.
+-spec lookup_type(_, _, _) -> {'type' | 'opaque', type_value()} | 'error'.
lookup_type(Name, Arity, RecDict) ->
case dict:find({type, Name, Arity}, RecDict) of
error ->
diff --git a/lib/inets/test/ftp_SUITE.erl b/lib/inets/test/ftp_SUITE.erl
index a8d39e3fe7..e2dec0c42a 100644
--- a/lib/inets/test/ftp_SUITE.erl
+++ b/lib/inets/test/ftp_SUITE.erl
@@ -114,6 +114,7 @@ ftp_tests()->
%%% ftpservers list of servers to check if they are available
%%% The element is:
%%% {Name, % string(). The os command name
+%%% Path, % string(). The os PATH syntax, e.g "/bin:/usr/bin"
%%% StartCommand, % fun()->{ok,start_result()} | {error,string()}.
%%% % The command to start the daemon with.
%%% ChkUp, % fun(start_result()) -> string(). Os command to check
@@ -129,12 +130,13 @@ ftp_tests()->
-define(default_ftp_servers,
[{"vsftpd",
- fun(__CONF__) ->
+ "/sbin:/usr/sbin:/usr/local/sbin",
+ fun(__CONF__, AbsName) ->
DataDir = proplists:get_value(data_dir,__CONF__),
ConfFile = filename:join(DataDir, "vsftpd.conf"),
PrivDir = proplists:get_value(priv_dir,__CONF__),
AnonRoot = PrivDir,
- Cmd = ["vsftpd "++filename:join(DataDir,"vsftpd.conf"),
+ Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"),
" -oftpd_banner=erlang_otp_testing",
" -oanon_root=\"",AnonRoot,"\"",
" -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"",
@@ -856,28 +858,51 @@ chk_no_dir(PathList, Config) ->
%%--------------------------------------------------------------------
find_executable(Config) ->
- FTPservers = case proplists:get_value(ftpservers,Config) of
- undefined -> ?default_ftp_servers;
- L -> L
- end,
- case lists:dropwhile(fun not_available/1, FTPservers) of
- [] -> false;
- [FTPD_data|_] -> {ok, FTPD_data}
- end.
+ search_executable(proplists:get_value(ftpservers, Config, ?default_ftp_servers)).
+
+
+search_executable([{Name,Paths,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}|Srvrs]) ->
+ case os_find(Name,Paths) of
+ false ->
+ ct:log("~p not found",[Name]),
+ search_executable(Srvrs);
+ AbsName ->
+ ct:comment("Found ~p",[AbsName]),
+ {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}}
+ end;
+search_executable([]) ->
+ false.
-not_available({Name,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}) ->
- os:find_executable(Name) == false.
+os_find(Name, Paths) ->
+ case os:find_executable(Name, Paths) of
+ false -> os:find_executable(Name);
+ AbsName -> AbsName
+ end.
-start_ftpd(Config) ->
- {Name,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} = proplists:get_value(ftpd_data, Config),
- case StartCmd(Config) of
+%%%----------------------------------------------------------------
+start_ftpd(Config0) ->
+ {AbsName,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} =
+ proplists:get_value(ftpd_data, Config0),
+ case StartCmd(Config0, AbsName) of
{ok,StartResult} ->
- [{ftpd_host,Host},
- {ftpd_port,Port},
- {ftpd_start_result,StartResult} | ConfigRewrite(Config)];
+ Config = [{ftpd_host,Host},
+ {ftpd_port,Port},
+ {ftpd_start_result,StartResult} | ConfigRewrite(Config0)],
+ try
+ ftp__close(ftp__open(Config,[verbose]))
+ of
+ Config1 when is_list(Config1) ->
+ ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]),
+ Config
+ catch
+ Class:Exception ->
+ ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p",
+ [AbsName,Host,Port,Class,Exception]),
+ {skip, [AbsName," started but unusuable"]}
+ end;
{error,Msg} ->
- {skip, [Name," not started: ",Msg]}
+ {skip, [AbsName," not started: ",Msg]}
end.
stop_ftpd(Config) ->
diff --git a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
index ba0746e736..37389ce5ae 100644
--- a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
@@ -317,7 +317,7 @@
sex = male,
phone = 98108,
room_no = {221, 015}},
- insert_emp(Me, 'B/SFR', [Erlang, mnesia, otp]).</code>
+ insert_emp(Emp, 'B/SFR', [Erlang, mnesia, otp]).</code>
<note><p>For information about Funs, see "Fun Expressions" in
section <c>Erlang Reference Manual</c> in System
Documentation..</p>
diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml
index 153bd4148e..649490f8b3 100644
--- a/lib/stdlib/doc/src/erl_id_trans.xml
+++ b/lib/stdlib/doc/src/erl_id_trans.xml
@@ -49,7 +49,8 @@
<name>parse_transform(Forms, Options) -> Forms</name>
<fsummary>Transform Erlang forms</fsummary>
<type>
- <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>]</v>
+ <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>
+ | <seealso marker="erl_parse#type-form_info">erl_parse:form_info()</seealso>]</v>
<v>Options = [<seealso marker="compile#type-option">compile:option()</seealso>]</v>
</type>
<desc>
diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml
index 32fce16d68..771ccc2dc6 100644
--- a/lib/stdlib/doc/src/erl_parse.xml
+++ b/lib/stdlib/doc/src/erl_parse.xml
@@ -73,6 +73,14 @@
<name name="error_info"></name>
</datatype>
<datatype>
+ <name name="form_info"></name>
+ <desc><p>Tuples <c>{error, error_info()}</c> and <c>{warning,
+ error_info()}</c>, denoting syntactically incorrect forms and
+ warnings, and <c>{eof, line()}</c>, denoting an end-of-stream
+ encountered before a complete form had been parsed.</p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="token"></name>
</datatype>
</datatypes>
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index 9c0a7fb7d5..ebcbc54ab1 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -37,8 +37,9 @@
checked_ra=[] % successfully accessed records
}).
--spec(module(AbsForms, CompileOptions) -> AbsForms when
+-spec(module(AbsForms, CompileOptions) -> AbsForms2 when
AbsForms :: [erl_parse:abstract_form()],
+ AbsForms2 :: [erl_parse:abstract_form()],
CompileOptions :: [compile:option()]).
%% Is is assumed that Fs is a valid list of forms. It should pass
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 2508f96b91..e9332ce069 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -99,7 +99,7 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
module='', %Module
behaviour=[], %Behaviour
exports=gb_sets:empty() :: gb_sets:set(fa()),%Exports
- imports=[] :: [fa()], %Imports, an orddict()
+ imports=[] :: orddict:orddict(fa(), module()),%Imports
compile=[], %Compile flags
records=dict:new() %Record definitions
:: dict:dict(atom(), {line(),Fields :: term()}),
@@ -467,7 +467,7 @@ used_vars(Exprs, BindingsList) ->
%% really all ordsets!
-spec(module(AbsForms) -> {ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
Warnings :: [{file:filename(),[ErrorInfo]}],
Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}],
ErrorInfo :: error_info()).
@@ -479,7 +479,7 @@ module(Forms) ->
-spec(module(AbsForms, FileName) ->
{ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
FileName :: atom() | string(),
Warnings :: [{file:filename(),[ErrorInfo]}],
Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}],
@@ -492,7 +492,7 @@ module(Forms, FileName) ->
-spec(module(AbsForms, FileName, CompileOptions) ->
{ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
FileName :: atom() | string(),
CompileOptions :: [compile:option()],
Warnings :: [{file:filename(),[ErrorInfo]}],
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index a896de4f1c..85b2816451 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -170,9 +170,6 @@ fun_type -> '(' top_types ')' '->' top_type
: {type, ?anno('$1'), 'fun',
[{type, ?anno('$1'), product, '$2'},'$5']}.
-map_pair_types -> '...' : [{type, ?anno('$1'), map_field_assoc,
- [{type, ?anno('$1'), any, []},
- {type, ?anno('$1'), any, []}]}].
map_pair_types -> map_pair_type : ['$1'].
map_pair_types -> map_pair_type ',' map_pair_types : ['$1'|'$3'].
@@ -534,7 +531,7 @@ Erlang code.
-compile([{hipe,[{regalloc,linear_scan}]}]).
-export_type([abstract_clause/0, abstract_expr/0, abstract_form/0,
- abstract_type/0, error_info/0]).
+ abstract_type/0, form_info/0, error_info/0]).
%% Start of Abstract Format
@@ -546,7 +543,6 @@ Erlang code.
| af_export()
| af_import()
| af_export_type()
- | af_optional_callbacks()
| af_compile()
| af_file()
| af_record_decl()
@@ -573,9 +569,6 @@ Erlang code.
-type af_ta_list() :: [{type_name(), arity()}].
--type af_optional_callbacks() ::
- {'attribute', anno(), 'optional_callbacks', af_fa_list()}.
-
-type af_compile() :: {'attribute', anno(), 'compile', any()}.
-type af_file() :: {'attribute', anno(), 'file', {string(), anno()}}.
@@ -867,16 +860,22 @@ Erlang code.
| af_unary_op(af_singleton_integer_type())
| af_binary_op(af_singleton_integer_type()).
--type af_literal() :: af_atom() | af_integer() | af_float() | af_string().
+-type af_literal() :: af_atom()
+ | af_character()
+ | af_float()
+ | af_integer()
+ | af_string().
-type af_atom() :: af_lit_atom(atom()).
-type af_lit_atom(A) :: {'atom', anno(), A}.
--type af_integer() :: {'integer', anno(), non_neg_integer()}.
+-type af_character() :: {'char', anno(), char()}.
-type af_float() :: {'float', anno(), float()}.
+-type af_integer() :: {'integer', anno(), non_neg_integer()}.
+
-type af_string() :: {'string', anno(), string()}.
-type af_match(T) :: {'match', anno(), af_pattern(), T}.
@@ -944,6 +943,10 @@ Erlang code.
-type type_name() :: atom().
+-type form_info() :: {'eof', erl_anno:line()}
+ | {'error', erl_scan:error_info() | error_info()}
+ | {'warning', erl_scan:error_info() | error_info()}.
+
%% End of Abstract Format
%% XXX. To be refined.
@@ -1503,8 +1506,9 @@ type_preop_prec('#') -> {700,800}.
| abstract_type().
-spec map_anno(Fun, Abstr) -> NewAbstr when
- Fun :: fun((Anno) -> Anno),
+ Fun :: fun((Anno) -> NewAnno),
Anno :: erl_anno:anno(),
+ NewAnno :: erl_anno:anno(),
Abstr :: erl_parse_tree(),
NewAbstr :: erl_parse_tree().
@@ -1513,14 +1517,14 @@ map_anno(F0, Abstr) ->
{NewAbstr, []} = modify_anno1(Abstr, [], F),
NewAbstr.
--spec fold_anno(Fun, Acc0, Abstr) -> NewAbstr when
+-spec fold_anno(Fun, Acc0, Abstr) -> Acc1 when
Fun :: fun((Anno, AccIn) -> AccOut),
Anno :: erl_anno:anno(),
Acc0 :: term(),
+ Acc1 :: term(),
AccIn :: term(),
AccOut :: term(),
- Abstr :: erl_parse_tree(),
- NewAbstr :: erl_parse_tree().
+ Abstr :: erl_parse_tree().
fold_anno(F0, Acc0, Abstr) ->
F = fun(A, Acc) -> {A, F0(A, Acc)} end,
@@ -1528,8 +1532,9 @@ fold_anno(F0, Acc0, Abstr) ->
NewAcc.
-spec mapfold_anno(Fun, Acc0, Abstr) -> {NewAbstr, Acc1} when
- Fun :: fun((Anno, AccIn) -> {Anno, AccOut}),
+ Fun :: fun((Anno, AccIn) -> {NewAnno, AccOut}),
Anno :: erl_anno:anno(),
+ NewAnno :: erl_anno:anno(),
Acc0 :: term(),
Acc1 :: term(),
AccIn :: term(),
@@ -1545,7 +1550,9 @@ mapfold_anno(F, Acc0, Abstr) ->
Abstr :: erl_parse_tree().
new_anno(Term) ->
- map_anno(fun erl_anno:new/1, Term).
+ F = fun(L, Acc) -> {erl_anno:new(L), Acc} end,
+ {NewAbstr, []} = modify_anno1(Term, [], F),
+ NewAbstr.
-spec anno_to_term(Abstr) -> term() when
Abstr :: erl_parse_tree().
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index ca764675fc..016962f538 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -70,13 +70,13 @@
%%%
-spec(form(Form) -> io_lib:chars() when
- Form :: erl_parse:abstract_form()).
+ Form :: erl_parse:abstract_form() | erl_parse:form_info()).
form(Thing) ->
form(Thing, none).
-spec(form(Form, Options) -> io_lib:chars() when
- Form :: erl_parse:abstract_form(),
+ Form :: erl_parse:abstract_form() | erl_parse:form_info(),
Options :: options()).
form(Thing, Options) ->
@@ -344,27 +344,9 @@ binary_type(I1, I2) ->
map_type(Fs) ->
{first,[$#],map_pair_types(Fs)}.
-map_pair_types(Fs0) ->
- Fs = replace_any_map(Fs0),
+map_pair_types(Fs) ->
tuple_type(Fs, fun map_pair_type/2).
-replace_any_map([{type,Line,map_field_assoc,[KType,VType]}]=Fs) ->
- IsAny = fun({type,_,any,[]}) -> true;
- %% ({var,_,'_'}) -> true;
- (_) -> false
- end,
- case IsAny(KType) andalso IsAny(VType) of
- true ->
- [{type,Line,map_field_assoc,any}];
- false ->
- Fs
- end;
-replace_any_map([F|Fs]) ->
- [F|replace_any_map(Fs)];
-replace_any_map([]) -> [].
-
-map_pair_type({type,_Line,map_field_assoc,any}, _Prec) ->
- leaf("...");
map_pair_type({type,_Line,map_field_assoc,[KType,VType]}, Prec) ->
{list,[{cstep,[ltype(KType, Prec),leaf(" =>")],ltype(VType, Prec)}]};
map_pair_type({type,_Line,map_field_exact,[KType,VType]}, Prec) ->
diff --git a/lib/stdlib/src/lib.erl b/lib/stdlib/src/lib.erl
index 6fba63a895..56654097d9 100644
--- a/lib/stdlib/src/lib.erl
+++ b/lib/stdlib/src/lib.erl
@@ -73,7 +73,7 @@ nonl([H|T]) -> [H|nonl(T)].
send(To, Msg) -> To ! Msg.
--spec sendw(To, Msg) -> Msg when
+-spec sendw(To, Msg) -> term() when
To :: pid() | atom() | {atom(), node()},
Msg :: term().
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index 24b5fde1db..c0eea652e7 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -224,8 +224,9 @@ transform_from_shell(Dialect, Clauses, BoundEnvironment) ->
%% Called when translating during compiling
%%
--spec parse_transform(Forms, Options) -> Forms when
- Forms :: [erl_parse:abstract_form()],
+-spec parse_transform(Forms, Options) -> Forms2 when
+ Forms :: [erl_parse:abstract_form() | erl_parse:form_info()],
+ Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()],
Options :: term().
parse_transform(Forms, _Options) ->
diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl
index 8e99ec0ed9..5356467b19 100644
--- a/lib/stdlib/src/proplists.erl
+++ b/lib/stdlib/src/proplists.erl
@@ -438,8 +438,9 @@ substitute_aliases_1([], P) ->
%% @see normalize/2
-spec substitute_negations(Negations, ListIn) -> ListOut when
- Negations :: [{Key, Key}],
- Key :: term(),
+ Negations :: [{Key1, Key2}],
+ Key1 :: term(),
+ Key2 :: term(),
ListIn :: [term()],
ListOut :: [term()].
diff --git a/lib/stdlib/src/qlc.erl b/lib/stdlib/src/qlc.erl
index b396ba7057..f3665824f2 100644
--- a/lib/stdlib/src/qlc.erl
+++ b/lib/stdlib/src/qlc.erl
@@ -734,10 +734,11 @@ table(TraverseFun, Options) when is_function(TraverseFun) ->
table(T1, T2) ->
erlang:error(badarg, [T1, T2]).
--spec(transform_from_evaluator(LC, Bs) -> Expr when
+-spec(transform_from_evaluator(LC, Bs) -> Return when
LC :: abstract_expr(),
- Expr :: abstract_expr(),
- Bs :: erl_eval:binding_struct()).
+ Bs :: erl_eval:binding_struct(),
+ Return :: {ok, abstract_expr()}
+ | {not_ok, {error, module(), Reason :: term()}}).
transform_from_evaluator(LC, Bs0) ->
qlc_pt:transform_from_evaluator(LC, Bs0).
diff --git a/lib/stdlib/src/qlc_pt.erl b/lib/stdlib/src/qlc_pt.erl
index e4b9768b12..0db63b81f4 100644
--- a/lib/stdlib/src/qlc_pt.erl
+++ b/lib/stdlib/src/qlc_pt.erl
@@ -67,8 +67,8 @@
%%%
-spec(parse_transform(Forms, Options) -> Forms2 when
- Forms :: [erl_parse:abstract_form()],
- Forms2 :: [erl_parse:abstract_form()],
+ Forms :: [erl_parse:abstract_form() | erl_parse:form_info()],
+ Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()],
Options :: [Option],
Option :: type_checker | compile:option()).
@@ -117,19 +117,21 @@ parse_transform(Forms0, Options) ->
true = ets:delete(NodeInfo)
end.
--spec(transform_from_evaluator(LC, Bs) -> Expr when
+-spec(transform_from_evaluator(LC, Bs) -> Return when
LC :: erl_parse:abstract_expr(),
- Expr :: erl_parse:abstract_expr(),
- Bs :: erl_eval:binding_struct()).
+ Bs :: erl_eval:binding_struct(),
+ Return :: {ok, erl_parse:abstract_expr()}
+ | {not_ok, {error, module(), Reason :: term()}}).
transform_from_evaluator(LC, Bindings) ->
?DEBUG("qlc Parse Transform (Evaluator Version)~n", []),
transform_expression(LC, Bindings, false).
--spec(transform_expression(LC, Bs) -> Expr when
+-spec(transform_expression(LC, Bs) -> Return when
LC :: erl_parse:abstract_expr(),
- Expr :: erl_parse:abstract_expr(),
- Bs :: erl_eval:binding_struct()).
+ Bs :: erl_eval:binding_struct(),
+ Return :: {ok, erl_parse:abstract_expr()}
+ | {not_ok, [{error, Reason :: term()}]}).
transform_expression(LC, Bindings) ->
transform_expression(LC, Bindings, true).
diff --git a/lib/stdlib/src/sofs.erl b/lib/stdlib/src/sofs.erl
index b18df2ad09..c244e06ca4 100644
--- a/lib/stdlib/src/sofs.erl
+++ b/lib/stdlib/src/sofs.erl
@@ -621,6 +621,9 @@ canonical_relation(Sets) when ?IS_SET(Sets) ->
%%% Functions on binary relations only.
%%%
+-spec(rel2fam(BinRel) -> Family when
+ Family :: family(),
+ BinRel :: binary_relation()).
rel2fam(R) ->
relation_to_family(R).
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index a48ba7b5b7..951a17d3eb 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -1112,15 +1112,14 @@ pr_1014(Config) ->
ok = pp_forms(<<"-type t() :: #{any() => _}. ">>),
ok = pp_forms(<<"-type t() :: #{_ => any()}. ">>),
ok = pp_forms(<<"-type t() :: #{any() => any()}. ">>),
- ok = pp_forms(<<"-type t() :: #{...}. ">>),
- ok = pp_forms(<<"-type t() :: #{atom() := integer(), ...}. ">>),
+ ok = pp_forms(<<"-type t() :: #{atom() := integer(), any() => any()}. ">>),
FileName = filename('pr_1014.erl', Config),
C = <<"-module pr_1014.\n"
"-compile export_all.\n"
"-type m() :: #{..., a := integer()}.\n">>,
ok = file:write_file(FileName, C),
- {error,[{_,[{3,erl_parse,["syntax error before: ","','"]}]}],_} =
+ {error,[{_,[{3,erl_parse,["syntax error before: ","'...'"]}]}],_} =
compile:file(FileName, [return]),
ok.
diff --git a/lib/syntax_tools/src/Makefile b/lib/syntax_tools/src/Makefile
index 2e91adf8af..8325db45a8 100644
--- a/lib/syntax_tools/src/Makefile
+++ b/lib/syntax_tools/src/Makefile
@@ -29,7 +29,7 @@ ERL_COMPILE_FLAGS += -pa $(EBIN) -pa ./ -I$(INCLUDE)
ifeq ($(NATIVE_LIBS_ENABLED),yes)
ERL_COMPILE_FLAGS += +native
endif
-ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import -Werror # +warn_missing_spec +warn_untyped_record
+ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import #-Werror # +warn_missing_spec +warn_untyped_record
SOURCES=erl_syntax.erl erl_prettypr.erl erl_syntax_lib.erl \
erl_comment_scan.erl erl_recomment.erl erl_tidy.erl \
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 119d375746..f1615b2610 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -1130,13 +1130,14 @@ lay_2(Node, Ctxt) ->
any_size ->
text("map()");
Fs ->
- {Prec, _PrecR} = type_preop_prec('#'),
- Es = lay_map_fields(Fs,
- floating(text(",")),
- reset_prec(Ctxt)),
+ Ctxt1 = reset_prec(Ctxt),
+ Es = seq(Fs,
+ floating(text(",")), Ctxt1,
+ fun lay/2),
D = beside(floating(text("#{")),
beside(par(Es),
floating(text("}")))),
+ {Prec, _PrecR} = type_preop_prec('#'),
maybe_parentheses(D, Prec, Ctxt)
end;
@@ -1400,36 +1401,6 @@ lay_error_info(T, Ctxt) ->
lay_concrete(T, Ctxt) ->
lay(erl_syntax:abstract(T), Ctxt).
-lay_map_fields([H | T], Separator, Ctxt) ->
- case T of
- [] ->
- [case erl_syntax:type(H) of
- map_type_assoc ->
- lay_last_type_assoc(H, Ctxt);
- _ ->
- lay(H, Ctxt)
- end];
- _ ->
- [maybe_append(Separator, lay(H, Ctxt))
- | lay_map_fields(T, Separator, Ctxt)]
- end;
-lay_map_fields([], _, _) ->
- [empty()].
-
-lay_last_type_assoc(Node, Ctxt) ->
- Name = erl_syntax:map_type_assoc_name(Node),
- Value = erl_syntax:map_type_assoc_value(Node),
- IsAny = fun({type,_,any,[]}) -> true;
- %% ({var,_,'_'}) -> true;
- (_) -> false
- end,
- case IsAny(Name) andalso IsAny(Value) of
- true ->
- text("...");
- false ->
- lay_type_assoc(Name, Value, Ctxt)
- end.
-
lay_type_assoc(Name, Value, Ctxt) ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(Name, Ctxt1),
diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl
index 5ce533285d..c1141b2bc6 100644
--- a/lib/syntax_tools/src/erl_recomment.erl
+++ b/lib/syntax_tools/src/erl_recomment.erl
@@ -601,16 +601,16 @@ expand_comment(C) ->
-record(leaf, {min = 0 :: integer(),
max = 0 :: integer(),
- precomments = [] :: [erl_syntax:syntaxTree()],
- postcomments = [] :: [erl_syntax:syntaxTree()],
+ precomments = [] :: [erl_comment_scan:comment()],
+ postcomments = [] :: [erl_comment_scan:comment()],
value :: erl_syntax:syntaxTree()}).
-record(tree, {min = 0 :: integer(),
max = 0 :: integer(),
type :: atom(),
attrs :: erl_syntax:syntaxTreeAttributes(),
- precomments = [] :: [erl_syntax:syntaxTree()],
- postcomments = [] :: [erl_syntax:syntaxTree()],
+ precomments = [] :: [erl_comment_scan:comment()],
+ postcomments = [] :: [erl_comment_scan:comment()],
subtrees = [] :: [extendedSyntaxTree()]}).
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index f4cda814fc..ee42e56172 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -443,7 +443,14 @@
-type syntaxTree() :: #tree{} | #wrapper{} | erl_parse().
--type erl_parse() :: erl_parse:abstract_form() | erl_parse:abstract_expr().
+-type erl_parse() :: erl_parse:abstract_clause()
+ | erl_parse:abstract_expr()
+ | erl_parse:abstract_form()
+ | erl_parse:abstract_type()
+ | erl_parse:form_info()
+ %% To shut up Dialyzer:
+ | {bin_element, _, _, _, _}.
+
%% The representation built by the Erlang standard library parser
%% `erl_parse'. This is a subset of the {@link syntaxTree()} type.
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl
index 5621d3a293..e4f8a1c3de 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl
@@ -49,9 +49,12 @@
-type m1() :: #{} | map().
-type m2() :: #{a := m1(), b => #{} | fy:m2()}.
--type m3() :: #{...}.
--type m4() :: #{_ => _, ...}.
--type m5() :: #{any() => any(), ...}. % Currently printed as `#{..., ...}'.
+%-type m3() :: #{...}.
+%-type m4() :: #{_ => _, ...}.
+%-type m5() :: #{any() => any(), ...}.
+-type m3() :: #{any() => any()}.
+-type m4() :: #{_ => _, any() => any()}.
+-type m5() :: #{any() => any(), any() => any()}.
-type b1() :: B1 :: binary() | (BitString :: bitstring()).
-define(PAIR(A, B), {(A), (B)}).
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
index 4322943c59..bb9815f9b0 100644
--- a/lib/tools/src/xref_base.erl
+++ b/lib/tools/src/xref_base.erl
@@ -669,16 +669,45 @@ do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) ->
warnings(War, unreadable, Unreadable),
case Errors of
[] ->
- do_add_modules(FileNames, AppName, Bui, Ver, War, State, []);
+ do_add_modules(FileNames, AppName, Bui, Ver, War, State);
[Error | _] ->
throw(Error)
end.
-do_add_modules([], _AppName, _OB, _OV, _OW, State, Modules) ->
+do_add_modules(Files, AppName, OB, OV, OW, State0) ->
+ NFiles = length(Files),
+ Reader = fun(SplitName, State) ->
+ _Pid = read_module(SplitName, AppName, OB, OV, OW, State)
+ end,
+ N = parallelism(),
+ Files1 = start_readers(Files, Reader, State0, N),
+ %% Increase the number of readers towards the end to decrease the
+ %% waiting time for the collecting process:
+ Nx = N,
+ add_mods(Files1, Reader, State0, [], NFiles, Nx).
+
+add_mods(_, _ReaderFun, State, Modules, 0, _Nx) ->
{ok, sort(Modules), State};
-do_add_modules([File | Files], AppName, OB, OV, OW, State, Modules) ->
- {ok, M, NewState} = do_add_module(File, AppName, OB, OV, OW, State),
- do_add_modules(Files, AppName, OB, OV, OW, NewState, M ++ Modules).
+add_mods(Files, ReaderFun, State, Modules, N, Nx) ->
+ {I, Nx1} = case Nx > 0 of
+ false -> {1, Nx};
+ true -> {2, Nx - 1}
+ end,
+ Files1 = start_readers(Files, ReaderFun, State, I),
+ {ok, M, NewState} = process_module(State),
+ add_mods(Files1, ReaderFun, NewState, M ++ Modules, N - 1, Nx1).
+
+start_readers([SplitName|Files], ReaderFun, State, N) when N > 0 ->
+ _Pid = ReaderFun(SplitName, State),
+ start_readers(Files, ReaderFun, State, N - 1);
+start_readers(Files, _ReaderFun, _State, _) ->
+ Files.
+
+parallelism() ->
+ case erlang:system_info(multi_scheduling) of
+ enabled -> erlang:system_info(schedulers_online);
+ _ -> 1
+ end.
%% -> {ok, Module, State} | throw(Error)
do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
@@ -692,50 +721,75 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
%% -> {ok, Module, State} | throw(Error)
%% Options: verbose, warnings, builtins
-do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) ->
- File = filename:join(Dir, Basename),
- {ok, M, Bad, NewState} =
- do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State),
- _ = filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad),
- {ok, M, NewState}.
-
-do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) ->
- message(Verbose, reading_beam, [File]),
- Mode = State#xref.mode,
+do_add_module(SplitName, AppName, Builtins, Verbose, Warnings, State) ->
+ _Pid = read_module(SplitName, AppName, Builtins, Verbose, Warnings, State),
+ process_module(State).
+
+read_module(SplitName, AppName, Builtins, Verbose, Warnings, State) ->
Me = self(),
- Fun = fun() -> Me ! {self(), abst(File, Builtins, Mode)} end,
- case xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]) of
+ #xref{mode = Mode} = State,
+ Fun =
+ fun() ->
+ Me ! {?MODULE,
+ read_a_module(SplitName, AppName, Builtins, Verbose,
+ Warnings, Mode)}
+ end,
+ spawn_opt(Fun, [link, {min_heap_size, 1000000}, {priority, high}]).
+
+read_a_module({Dir, BaseName}, AppName, Builtins, Verbose, Warnings, Mode) ->
+ File = filename:join(Dir, BaseName),
+ case abst(File, Builtins, Mode) of
{ok, _M, no_abstract_code} when Verbose ->
- message(Verbose, skipped_beam, []),
- {ok, [], [], State};
+ message(Verbose, no_debug_info, [File]),
+ no;
{ok, _M, no_abstract_code} when not Verbose ->
message(Warnings, no_debug_info, [File]),
- {ok, [], [], State};
+ no;
{ok, M, Data, UnresCalls0} ->
- %% Remove duplicates. Identical unresolved calls on the
- %% same line are counted as _one_ unresolved call.
- UnresCalls = usort(UnresCalls0),
- message(Verbose, done, []),
- NoUnresCalls = length(UnresCalls),
- case NoUnresCalls of
- 0 -> ok;
- 1 -> warnings(Warnings, unresolved_summary1, [[M]]);
- N -> warnings(Warnings, unresolved_summary, [[M, N]])
- end,
- T = case xref_utils:file_info(File) of
- {ok, {_, _, _, Time}} -> Time;
- Error -> throw(Error)
- end,
- XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
- mtime = T, builtins = Builtins,
- no_unresolved = NoUnresCalls},
- do_add_module(State, XMod, UnresCalls, Data);
+ message(Verbose, done, [File]),
+ %% Remove duplicates. Identical unresolved calls on the
+ %% same line are counted as _one_ unresolved call.
+ UnresCalls = usort(UnresCalls0),
+ NoUnresCalls = length(UnresCalls),
+ case NoUnresCalls of
+ 0 -> ok;
+ 1 -> warnings(Warnings, unresolved_summary1, [[M]]);
+ N -> warnings(Warnings, unresolved_summary, [[M, N]])
+ end,
+ case xref_utils:file_info(File) of
+ {ok, {_, _, _, Time}} ->
+ XMod = #xref_mod{name = M, app_name = AppName,
+ dir = Dir, mtime = Time,
+ builtins = Builtins,
+ no_unresolved = NoUnresCalls},
+ {ok, PrepMod, Bad} =
+ prepare_module(Mode, XMod, UnresCalls, Data),
+ foreach(fun({Tag,B}) ->
+ warnings(Warnings, Tag,
+ [[File,B]])
+ end, Bad),
+ {ok, PrepMod};
+ Error -> Error
+ end;
Error ->
message(Verbose, error, []),
- throw(Error)
+ Error
end.
-abst(File, Builtins, Mode) when Mode =:= functions ->
+process_module(State) ->
+ receive
+ {?MODULE, Reply} ->
+ case Reply of
+ no ->
+ {ok, [], State};
+ {ok, PrepMod} ->
+ finish_module(PrepMod, State);
+ Error ->
+ throw(Error)
+ end
+ end.
+
+abst(File, Builtins, _Mode = functions) ->
case beam_lib:chunks(File, [abstract_code, exports, attributes]) of
{ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code ->
{ok, M, NoA};
@@ -762,7 +816,7 @@ abst(File, Builtins, Mode) when Mode =:= functions ->
Error when element(1, Error) =:= error ->
Error
end;
-abst(File, Builtins, Mode) when Mode =:= modules ->
+abst(File, Builtins, _Mode = modules) ->
case beam_lib:chunks(File, [exports, imports, attributes]) of
{ok, {Mod, [{exports,X0}, {imports,I0}, {attributes,At}]}} ->
X1 = mfa_exports(X0, At, Mod),
@@ -856,19 +910,13 @@ deprecated_flag(_) -> undefined.
%% dom CallAt = LC U XC
%% Attrs is collected from the attribute 'xref' (experimental).
do_add_module(S, XMod, Unres, Data) ->
- M = XMod#xref_mod.name,
- case dict:find(M, S#xref.modules) of
- {ok, OldXMod} ->
- BF2 = module_file(XMod),
- BF1 = module_file(OldXMod),
- throw_error({module_clash, {M, BF1, BF2}});
- error ->
- do_add_module(S, M, XMod, Unres, Data)
- end.
+ #xref{mode = Mode} = S,
+ Mode = S#xref.mode,
+ {ok, PrepMod, Bad} = prepare_module(Mode, XMod, Unres, Data),
+ {ok, Ms, NS} = finish_module(PrepMod, S),
+ {ok, Ms, Bad, NS}.
-%%do_add_module(S, M, _XMod, _Unres, Data)->
-%% {ok, M, [], S};
-do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
+prepare_module(_Mode = functions, XMod, Unres0, Data) ->
{DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Attrs, Depr} = Data,
%% Bad is a list of bad values of 'xref' attributes.
{ALC0,AXC0,Bad0} = Attrs,
@@ -904,26 +952,27 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
LC = union(LC1, ALC),
{DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X),
+ {EE, ECallAt} = inter_graph(X, L, LC, XC, CallAt),
+ {ok, {functions, XMod, [DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt,
+ DF1,DF_11,DF_21,DF_31], NoCalls, Unres},
+ DBad++Bad};
+prepare_module(_Mode = modules, XMod, _Unres, Data) ->
+ {X0, I0, Depr} = Data,
+ X1 = xref_utils:xset(X0, [tspec(func)]),
+ I1 = xref_utils:xset(I0, [tspec(func)]),
+ {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1),
+ {ok, {modules, XMod, [X1,I1,DF1,DF_11,DF_21,DF_31]}, DBad}.
- %% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt),
- Self = self(),
- Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
- {EE, ECallAt} =
- xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]),
-
+finish_module({functions, XMod, List, NoCalls, Unres}, S) ->
+ ok = check_module(XMod, S),
[DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
- DF2,DF_12,DF_22,DF_32] =
- pack([DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt,
- DF1,DF_11,DF_21,DF_31]),
-
- %% Foo = [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
- %% DF2,DF_12,DF_22,DF_32],
- %% io:format("{~p, ~p, ~p},~n", [M, pack:lsize(Foo), pack:usize(Foo)]),
+ DF2,DF_12,DF_22,DF_32] = pack(List),
LU = range(LC2),
LPredefined = predefined_funs(LU),
+ M = XMod#xref_mod.name,
MS = xref_utils:xset(M, atom),
T = from_sets({MS,DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,
LC2,XC2,LU,EE2,ECallAt2,Unres,LPredefined,
@@ -934,19 +983,28 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
XMod1 = XMod#xref_mod{data = T, info = Info},
S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
- {ok, [M], DBad++Bad, take_down(S1)};
-do_add_module(S, M, XMod, _Unres, Data) when S#xref.mode =:= modules ->
- {X0, I0, Depr} = Data,
- X1 = xref_utils:xset(X0, [tspec(func)]),
- I1 = xref_utils:xset(I0, [tspec(func)]),
- {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1),
- [X2,I2,DF2,DF_12,DF_22,DF_32] = pack([X1,I1,DF1,DF_11,DF_21,DF_31]),
+ {ok, [M], take_down(S1)};
+finish_module({modules, XMod, List}, S) ->
+ ok = check_module(XMod, S),
+ [X2,I2,DF2,DF_12,DF_22,DF_32] = pack(List),
+ M = XMod#xref_mod.name,
MS = xref_utils:xset(M, atom),
T = from_sets({MS, X2, I2, DF2, DF_12, DF_22, DF_32}),
Info = [],
XMod1 = XMod#xref_mod{data = T, info = Info},
S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
- {ok, [M], DBad, take_down(S1)}.
+ {ok, [M], take_down(S1)}.
+
+check_module(XMod, State) ->
+ M = XMod#xref_mod.name,
+ case dict:find(M, State#xref.modules) of
+ {ok, OldXMod} ->
+ BF2 = module_file(XMod),
+ BF1 = module_file(OldXMod),
+ throw_error({module_clash, {M, BF1, BF2}});
+ error ->
+ ok
+ end.
depr_mod({Depr,Bad0}, X) ->
%% Bad0 are badly formed deprecated attributes.
@@ -992,9 +1050,6 @@ no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) ->
%% Note: this is overwritten in do_set_up():
{no_inter_function_calls, no_elements(EE)}].
-inter_graph(Pid, X, L, LC, XC, CallAt) ->
- Pid ! {self(), inter_graph(X, L, LC, XC, CallAt)}.
-
%% Inter Call Graph.
%inter_graph(_X, _L, _LC, _XC, _CallAt) ->
% {empty_set(), empty_set()};
@@ -1766,10 +1821,6 @@ tpack(T, I, L) ->
message(true, What, Arg) ->
case What of
- reading_beam ->
- io:format("~ts... ", Arg);
- skipped_beam ->
- io:format("skipped (no debug information)~n", Arg);
no_debug_info ->
io:format("Skipping ~ts (no debug information)~n", Arg);
unresolved_summary1 ->
@@ -1791,7 +1842,7 @@ message(true, What, Arg) ->
set_up ->
io:format("Setting up...", Arg);
done ->
- io:format("done~n", Arg);
+ io:format("done reading ~ts~n", Arg);
error ->
io:format("error~n", Arg);
Else ->
diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl
index f69aa70244..b0c168e018 100644
--- a/lib/tools/src/xref_utils.erl
+++ b/lib/tools/src/xref_utils.erl
@@ -47,8 +47,6 @@
-export([options/2]).
--export([subprocess/2]).
-
-export([format_error/1]).
-import(lists, [append/1, delete/2, filter/2, foldl/3, foreach/2,
@@ -512,12 +510,6 @@ find_beam(Culprit) ->
options(Options, Valid) ->
split_options(Options, [], [], [], Valid).
-subprocess(Fun, Opts) ->
- Pid = spawn_opt(Fun, Opts),
- receive
- {Pid, Reply} -> Reply
- end.
-
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({file_error, FileName, Reason}) ->