diff options
-rw-r--r-- | HOWTO/INSTALL.md | 25 | ||||
-rw-r--r-- | erts/emulator/beam/erl_bif_port.c | 6 | ||||
-rw-r--r-- | erts/emulator/test/port_SUITE.erl | 22 | ||||
-rw-r--r-- | lib/asn1/src/asn1ct_check.erl | 2 | ||||
-rw-r--r-- | lib/asn1/test/asn1_SUITE.erl | 16 | ||||
-rw-r--r-- | lib/asn1/test/asn1_SUITE_data/TAGDEFAULT-AUTOMATIC.asn | 25 | ||||
-rw-r--r-- | lib/kernel/doc/src/file.xml | 6 | ||||
-rw-r--r-- | lib/kernel/src/file.erl | 62 | ||||
-rw-r--r-- | lib/kernel/test/file_SUITE.erl | 61 | ||||
-rw-r--r-- | lib/tools/src/cover.erl | 2 | ||||
-rw-r--r-- | lib/tools/test/cover_SUITE_data/b.erl | 8 | ||||
-rw-r--r-- | system/doc/reference_manual/typespec.xml | 3 |
12 files changed, 187 insertions, 51 deletions
diff --git a/HOWTO/INSTALL.md b/HOWTO/INSTALL.md index 7a7e63164c..53b1b8cd8a 100644 --- a/HOWTO/INSTALL.md +++ b/HOWTO/INSTALL.md @@ -409,6 +409,30 @@ Some of the available `configure` options are: If you or your system has special requirements please read the `Makefile` for additional configuration information. +#### Atomic Memory Operations and the VM #### + +The VM with SMP support makes quite a heavy use of atomic memory operations. +An implementation providing native atomic memory operations is therefore very +important when building Erlang/OTP. By default the VM will refuse to build +if native atomic memory operations are not available. + +Erlang/OTP itself provides implementations of native atomic memory operations +that can be used when compiling with a `gcc` compatible compiler on 32-bit +and 64-bit x86, 32-bit and 64-bit SPARC V9, and 32-bit PowerPC. When compiling +with a `gcc` compatible compiler on other architectures, the VM may be able to +make use of native atomic operations using the `__sync_*` primitives, but this +should only be used as a last resort since this wont give you optimal +performance. When compiling on Windows using a MicroSoft Visual C++ compiler +native atomic memory operations are provided by Windows APIs. + +You are recommended to use the native atomic implementation provided by +Erlang/OTP, or the API provided by Windows. If these do not provide native +atomics on your platform, you are recommended to build and install +[libatomic_ops][] before building Erlang/OTP. The `libatomic_ops` library +provides native atomic memory operations for a variety of platforms and +compilers. When building Erlang/OTP you need to inform the build system of +where the `libatomic_ops` library is installed using the +`--with-libatomic_ops=PATH` configure switch. ### Building ### @@ -862,3 +886,4 @@ under the License. [Optional Utilities]: #Optional-Utilities [Building on a Mac]: #Advanced-configuration-and-build-of-ErlangOTP_Building_OS-X-Darwin [Building with wxErlang]: #Advanced-configuration-and-build-of-ErlangOTP_Building_Building-with-wxErlang + [libatomic_ops]: https://github.com/ivmai/libatomic_ops/ diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c index 64bd598ba6..7ce950e090 100644 --- a/erts/emulator/beam/erl_bif_port.c +++ b/erts/emulator/beam/erl_bif_port.c @@ -472,7 +472,7 @@ cleanup_old_port_data(erts_aint_t data) ErtsPortDataHeap *pdhp = (ErtsPortDataHeap *) data; size_t size; ERTS_SMP_DATA_DEPENDENCY_READ_MEMORY_BARRIER; - size = sizeof(ErtsPortDataHeap) + pdhp->hsize*(sizeof(Eterm) - 1); + size = sizeof(ErtsPortDataHeap) + (pdhp->hsize-1)*sizeof(Eterm); erts_schedule_thr_prgr_later_cleanup_op(free_port_data_heap, (void *) pdhp, &pdhp->later_op, @@ -508,7 +508,7 @@ erts_port_data_size(Port *prt) } else { ErtsPortDataHeap *pdhp = (ErtsPortDataHeap *) data; - return (Uint) sizeof(ErtsPortDataHeap) + pdhp->hsize*(sizeof(Eterm)-1); + return (Uint) sizeof(ErtsPortDataHeap) + (pdhp->hsize-1)*sizeof(Eterm); } } @@ -550,7 +550,7 @@ BIF_RETTYPE port_set_data_2(BIF_ALIST_2) hsize = size_object(BIF_ARG_2); pdhp = erts_alloc(ERTS_ALC_T_PORT_DATA_HEAP, - sizeof(ErtsPortDataHeap) + hsize*(sizeof(Eterm)-1)); + sizeof(ErtsPortDataHeap) + (hsize-1)*sizeof(Eterm)); hp = &pdhp->heap[0]; pdhp->off_heap.first = NULL; pdhp->off_heap.overhead = 0; diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl index 8792f25d76..9083545060 100644 --- a/erts/emulator/test/port_SUITE.erl +++ b/erts/emulator/test/port_SUITE.erl @@ -2343,8 +2343,10 @@ port_setget_data(Config) when is_list(Config) -> Port = erlang:open_port({spawn_driver, "echo_drv"}, []), NSched = erlang:system_info(schedulers_online), + HeapData = {1,2,3,<<"A heap binary">>,fun()->"This is fun"end, + list_to_binary(lists:seq(1,100))}, PRs = lists:map(fun(I) -> - spawn_opt(fun() -> port_setget_data_hammer(Port,1) end, + spawn_opt(fun() -> port_setget_data_hammer(Port,HeapData,false,1) end, [monitor, {scheduler, I rem NSched}]) end, lists:seq(1,10)), @@ -2362,13 +2364,17 @@ port_setget_data(Config) when is_list(Config) -> PRs), ok. -port_setget_data_hammer(Port, N) -> +port_setget_data_hammer(Port, HeapData, IsSet0, N) -> Rand = random:uniform(3), - try case Rand of - 1 -> true = erlang:port_set_data(Port, atom); - 2 -> true = erlang:port_set_data(Port, {1,2,3}); - 3 -> erlang:port_get_data(Port) - end + IsSet1 = try case Rand of + 1 -> true = erlang:port_set_data(Port, atom), true; + 2 -> true = erlang:port_set_data(Port, HeapData), true; + 3 -> case erlang:port_get_data(Port) of + atom -> true; + HeapData -> true; + undefined -> false=IsSet0 + end + end catch error:badarg -> true = get(prepare_for_close), @@ -2381,7 +2387,7 @@ port_setget_data_hammer(Port, N) -> after 0 -> ok end, - port_setget_data_hammer(Port, N+1). + port_setget_data_hammer(Port, HeapData, IsSet1, N+1). wait_until(Fun) -> diff --git a/lib/asn1/src/asn1ct_check.erl b/lib/asn1/src/asn1ct_check.erl index 7b33d1e5e7..240f1cbb16 100644 --- a/lib/asn1/src/asn1ct_check.erl +++ b/lib/asn1/src/asn1ct_check.erl @@ -6583,6 +6583,8 @@ merge_tags2([T1= #tag{type='IMPLICIT'}, T2 |Rest], Acc) -> merge_tags2([T1#tag{type=T2#tag.type, form=T2#tag.form}|Rest],Acc); merge_tags2([T1= #tag{type={default,'IMPLICIT'}}, T2 |Rest], Acc) -> merge_tags2([T1#tag{type=T2#tag.type, form=T2#tag.form}|Rest],Acc); +merge_tags2([T1= #tag{type={default,'AUTOMATIC'}}, T2 |Rest], Acc) -> + merge_tags2([T1#tag{type=T2#tag.type, form=T2#tag.form}|Rest],Acc); merge_tags2([H|T],Acc) -> merge_tags2(T, [H|Acc]); merge_tags2([], Acc) -> diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl index 888339a4d2..432197eec0 100644 --- a/lib/asn1/test/asn1_SUITE.erl +++ b/lib/asn1/test/asn1_SUITE.erl @@ -60,7 +60,8 @@ groups() -> {ber, Parallel, [ber_choiceinseq, % Uses 'SOpttest' - ber_optional]}, + ber_optional, + tagdefault_automatic]}, {app_test, [], [{asn1_app_test, all}]}, @@ -659,6 +660,19 @@ ber_optional(Config, Rule, Opts) -> {'C', asn1_NOVALUE, 111, asn1_NOVALUE}}, asn1_test_lib:roundtrip('SOpttest', 'S', V). +tagdefault_automatic(Config) -> + test(Config, fun tagdefault_automatic/3, [ber]). +tagdefault_automatic(Config, Rule, Opts) -> + asn1_test_lib:compile("TAGDEFAULT-AUTOMATIC", Config, [Rule|Opts]), + << 48,8,128,2,100,101,129,2,110,111 >> = + asn1_test_lib:roundtrip_enc('TAGDEFAULT-AUTOMATIC', 'Tagged', {'Tagged', << 100,101 >>, << 110,111 >>}), + << 48,8,128,2,100,101,129,2,110,111 >> = + asn1_test_lib:roundtrip_enc('TAGDEFAULT-AUTOMATIC', 'Untagged', {'Untagged', << 100,101 >>, << 110,111 >>}), + << 48,8,4,2,100,101,130,2,110,111 >> = + asn1_test_lib:roundtrip_enc('TAGDEFAULT-AUTOMATIC', 'Mixed', {'Mixed', << 100,101 >>, << 110,111 >>}), + + ok. + %% records used by test-case default -record('Def1', {bool0, bool1 = asn1_DEFAULT, diff --git a/lib/asn1/test/asn1_SUITE_data/TAGDEFAULT-AUTOMATIC.asn b/lib/asn1/test/asn1_SUITE_data/TAGDEFAULT-AUTOMATIC.asn new file mode 100644 index 0000000000..2fcba1f71e --- /dev/null +++ b/lib/asn1/test/asn1_SUITE_data/TAGDEFAULT-AUTOMATIC.asn @@ -0,0 +1,25 @@ +TAGDEFAULT-AUTOMATIC DEFINITIONS AUTOMATIC TAGS ::= +BEGIN + +EXPORTS + Tagged, + Untagged, + Mixed +; + +Tagged ::= SEQUENCE { + o0 [0] OCTET STRING, + o1 [1] OCTET STRING +} + +Untagged ::= SEQUENCE { + o0 OCTET STRING, + o1 OCTET STRING +} + +Mixed ::= SEQUENCE { + o0 OCTET STRING, + o2 [2] OCTET STRING +} + +END diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index 1c03efe7fd..dcb9640dcf 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -1283,6 +1283,8 @@ or before unix time epoch which is 1970-01-01 00:00 UTC. Default is <c>{time, local}</c>. </p> + <p>If the <c>raw</c> option is set, the file server will not be called + and only informations about local files will be returned.</p> <note> <p> Since file times is stored in posix time on most OS it is @@ -1509,6 +1511,8 @@ the link will be returned in the <c>file_info</c> record and the <c>type</c> field of the record will be set to <c>symlink</c>.</p> + <p>If the <c>raw</c> option is set, the file server will not be called + and only informations about local files will be returned.</p> <p>If <c><anno>Name</anno></c> is not a symbolic link, this function returns exactly the same result as <c>read_file_info/1</c>. On platforms that do not support symbolic links, this function @@ -1847,6 +1851,8 @@ interpret it as universal time and <c>posix</c> must be seconds since or before unix time epoch which is 1970-01-01 00:00 UTC. Default is <c>{time, local}</c>. + <p>If the <c>raw</c> option is set, the file server will not be called + and only informations about local files will be returned.</p> </p> <p>The following fields are used from the record, if they are given.</p> diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl index ee2fb85de2..3d6665a36a 100644 --- a/lib/kernel/src/file.erl +++ b/lib/kernel/src/file.erl @@ -114,7 +114,7 @@ -type sendfile_option() :: {chunk_size, non_neg_integer()} | {use_threads, boolean()}. -type file_info_option() :: {'time', 'local'} | {'time', 'universal'} - | {'time', 'posix'}. + | {'time', 'posix'} | raw. %%% BIFs -export([native_name_encoding/0]). @@ -242,7 +242,19 @@ read_file_info(Name) -> Reason :: posix() | badarg. read_file_info(Name, Opts) when is_list(Opts) -> - check_and_call(read_file_info, [file_name(Name), Opts]). + Args = [file_name(Name), Opts], + case check_args(Args) of + ok -> + case lists:member(raw, Opts) of + true -> + [FileName|_] = Args, + ?PRIM_FILE:read_file_info(FileName, Opts); + false -> + call(read_file_info, Args) + end; + Error -> + Error + end. -spec altname(Name :: name_all()) -> any(). @@ -264,7 +276,19 @@ read_link_info(Name) -> Reason :: posix() | badarg. read_link_info(Name, Opts) when is_list(Opts) -> - check_and_call(read_link_info, [file_name(Name),Opts]). + Args = [file_name(Name), Opts], + case check_args(Args) of + ok -> + case lists:member(raw, Opts) of + true -> + [FileName|_] = Args, + ?PRIM_FILE:read_link_info(FileName, Opts); + false -> + call(read_link_info, Args) + end; + Error -> + Error + end. -spec read_link(Name) -> {ok, Filename} | {error, Reason} when @@ -298,7 +322,19 @@ write_file_info(Name, Info = #file_info{}) -> Reason :: posix() | badarg. write_file_info(Name, Info = #file_info{}, Opts) when is_list(Opts) -> - check_and_call(write_file_info, [file_name(Name), Info, Opts]). + Args = [file_name(Name), Info, Opts], + case check_args(Args) of + ok -> + case lists:member(raw, Opts) of + true -> + [FileName|_] = Args, + ?PRIM_FILE:write_file_info(FileName, Info, Opts); + false -> + call(write_file_info, Args) + end; + Error -> + Error + end. -spec list_dir(Dir) -> {ok, Filenames} | {error, Reason} when Dir :: name_all(), @@ -384,26 +420,12 @@ write_file(Name, Bin, ModeList) when is_list(ModeList) -> %% Obsolete, undocumented, local node only, don't use!. %% XXX to be removed. raw_read_file_info(Name) -> - Args = [file_name(Name)], - case check_args(Args) of - ok -> - [FileName] = Args, - ?PRIM_FILE:read_file_info(FileName); - Error -> - Error - end. + read_file_info(Name, [raw]). %% Obsolete, undocumented, local node only, don't use!. %% XXX to be removed. raw_write_file_info(Name, #file_info{} = Info) -> - Args = [file_name(Name)], - case check_args(Args) of - ok -> - [FileName] = Args, - ?PRIM_FILE:write_file_info(FileName, Info); - Error -> - Error - end. + write_file_info(Name, Info, [raw]). %%%----------------------------------------------------------------- %%% File io server functions. diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl index f6d6cd94ab..56c35678b6 100644 --- a/lib/kernel/test/file_SUITE.erl +++ b/lib/kernel/test/file_SUITE.erl @@ -1236,9 +1236,10 @@ file_info_basic_file(Config) when is_list(Config) -> %% Test that the file has the expected attributes. %% The times are tricky, so we will save them to a separate test case. - ?line {ok,#file_info{size=Size,type=Type,access=Access, - atime=AccessTime,mtime=ModifyTime}} = - ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name, [raw]), + #file_info{size=Size,type=Type,access=Access, + atime=AccessTime,mtime=ModifyTime} = FileInfo, ?line io:format("Access ~p, Modify ~p", [AccessTime, ModifyTime]), ?line Size = 7, ?line Type = regular, @@ -1280,9 +1281,10 @@ file_info_basic_directory(Config) when is_list(Config) -> test_server:timetrap_cancel(Dog). test_directory(Name, ExpectedAccess) -> - ?line {ok,#file_info{size=Size,type=Type,access=Access, - atime=AccessTime,mtime=ModifyTime}} = - ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name, [raw]), + #file_info{size=Size,type=Type,access=Access, + atime=AccessTime,mtime=ModifyTime} = FileInfo, ?line io:format("Testing directory ~s", [Name]), ?line io:format("Directory size is ~p", [Size]), ?line io:format("Access ~p", [Access]), @@ -1307,11 +1309,11 @@ file_info_bad(doc) -> []; file_info_bad(Config) when is_list(Config) -> ?line Dog = test_server:timetrap(test_server:seconds(5)), ?line RootDir = filename:join([?config(priv_dir, Config)]), - ?line {error, enoent} = - ?FILE_MODULE:read_file_info( - filename:join(RootDir, - atom_to_list(?MODULE)++ "_nonexistent")), + FileName = filename:join(RootDir, atom_to_list(?MODULE) ++ "_nonexistent"), + {error,enoent} = ?FILE_MODULE:read_file_info(FileName), + {error,enoent} = ?FILE_MODULE:read_file_info(FileName, [raw]), ?line {error, enoent} = ?FILE_MODULE:read_file_info(""), + {error, enoent} = ?FILE_MODULE:read_file_info("", [raw]), ?line [] = flush(), ?line test_server:timetrap_cancel(Dog), ok. @@ -1346,8 +1348,16 @@ file_info_int(Config) -> ?line io:put_chars(Fd1,"foo"), %% check that the file got a modify date max a few seconds away from now - ?line {ok,#file_info{type=regular,atime=AccTime1,mtime=ModTime1}} = - ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo1} = ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo1Raw} = ?FILE_MODULE:read_file_info(Name, [raw]), + + %% We assert that everything but the size is the same, on some OSs the + %% size may not have been flushed to disc and we do not want to do a + %% sync to force it. + FileInfo1Raw = FileInfo1#file_info{ size = FileInfo1Raw#file_info.size }, + + #file_info{type=regular,atime=AccTime1,mtime=ModTime1} = FileInfo1, + ?line Now = erlang:localtime(), %??? ?line io:format("Now ~p",[Now]), ?line io:format("Open file Acc ~p Mod ~p",[AccTime1,ModTime1]), @@ -1363,9 +1373,10 @@ file_info_int(Config) -> %% close the file, and watch the modify date change ?line ok = ?FILE_MODULE:close(Fd1), - ?line {ok,#file_info{size=Size,type=regular,access=Access, - atime=AccTime2,mtime=ModTime2}} = - ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Name), + {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Name, [raw]), + #file_info{size=Size,type=regular,access=Access, + atime=AccTime2,mtime=ModTime2} = FileInfo2, ?line io:format("Closed file Acc ~p Mod ~p",[AccTime2,ModTime2]), ?line true = time_dist(ModTime1,ModTime2) >= 0, @@ -1374,9 +1385,10 @@ file_info_int(Config) -> ?line Access = read_write, %% Do some directory checking - ?line {ok,#file_info{size=DSize,type=directory,access=DAccess, - atime=AccTime3,mtime=ModTime3}} = - ?FILE_MODULE:read_file_info(RootDir), + {ok,FileInfo3} = ?FILE_MODULE:read_file_info(RootDir), + {ok,FileInfo3} = ?FILE_MODULE:read_file_info(RootDir, [raw]), + #file_info{size=DSize,type=directory,access=DAccess, + atime=AccTime3,mtime=ModTime3} = FileInfo3, %% this dir was modified only a few secs ago ?line io:format("Dir Acc ~p; Mod ~p; Now ~p", [AccTime3, ModTime3, Now]), ?line true = abs(time_dist(Now,ModTime3)) < 5, @@ -1449,6 +1461,12 @@ file_write_file_info(Config) when is_list(Config) -> ?line ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#400}), ?line {error, eacces} = ?FILE_MODULE:write_file(Name1, "hello again"), + %% Same with raw. + ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#600}, [raw]), + ok = ?FILE_MODULE:write_file(Name1, "hello again"), + ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#400}, [raw]), + {error,eacces} = ?FILE_MODULE:write_file(Name1, "hello again"), + %% Write the times again. %% Note: Seconds must be even; see note in file_info_times/1. @@ -2650,7 +2668,9 @@ make_link(Config) when is_list(Config) -> %% since they are not used on symbolic links. ?line {ok, Info} = ?FILE_MODULE:read_link_info(Name), + {ok,Info} = ?FILE_MODULE:read_link_info(Name, [raw]), ?line {ok, Info} = ?FILE_MODULE:read_link_info(Alias), + {ok,Info} = ?FILE_MODULE:read_link_info(Alias, [raw]), ?line #file_info{links = 2, type = regular} = Info, ?line {error, eexist} = ?FILE_MODULE:make_link(Name, Alias), @@ -2670,6 +2690,7 @@ read_link_info_for_non_link(Config) when is_list(Config) -> ?line {ok, #file_info{type=directory}} = ?FILE_MODULE:read_link_info("."), + {ok, #file_info{type=directory}} = ?FILE_MODULE:read_link_info(".", [raw]), ?line [] = flush(), ?line test_server:timetrap_cancel(Dog), @@ -2700,11 +2721,15 @@ symlinks(Config) when is_list(Config) -> {skipped, "Windows user not privileged to create symlinks"}; ok -> ?line {ok, Info1} = ?FILE_MODULE:read_file_info(Name), + {ok,Info1} = ?FILE_MODULE:read_file_info(Name, [raw]), ?line {ok, Info1} = ?FILE_MODULE:read_file_info(Alias), + {ok,Info1} = ?FILE_MODULE:read_file_info(Alias, [raw]), ?line {ok, Info1} = ?FILE_MODULE:read_link_info(Name), + {ok,Info1} = ?FILE_MODULE:read_link_info(Name, [raw]), ?line #file_info{links = 1, type = regular} = Info1, ?line {ok, Info2} = ?FILE_MODULE:read_link_info(Alias), + {ok,Info2} = ?FILE_MODULE:read_link_info(Alias, [raw]), ?line #file_info{links=1, type=symlink} = Info2, ?line {ok, Name} = ?FILE_MODULE:read_link(Alias), {ok, Name} = ?FILE_MODULE:read_link_all(Alias), diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 113fa24bd5..31754015f7 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -1696,6 +1696,8 @@ fix_expr(T, Line, Bump) when is_tuple(T) -> fix_expr(E, _Line, _Bump) -> E. +fix_clauses([], _Line, _Bump) -> + []; fix_clauses(Cs, Line, Bump) -> case bumps_line(lists:last(Cs), Line) of true -> diff --git a/lib/tools/test/cover_SUITE_data/b.erl b/lib/tools/test/cover_SUITE_data/b.erl index 13f39b8cb9..0a418a58d8 100644 --- a/lib/tools/test/cover_SUITE_data/b.erl +++ b/lib/tools/test/cover_SUITE_data/b.erl @@ -1,5 +1,5 @@ -module(b). --export([start/0, loop/0]). +-export([start/0, loop/0, wait/0]). start() -> spawn(?MODULE, loop, []). @@ -12,3 +12,9 @@ loop() -> stop -> done end. + +%% This checks for a bug in expressions which have no +%% "main" clauses (only after and friends) followed by +%% a return value in the same line. +wait() -> + receive after 1000 -> done end, ok. diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml index 321f0a24d5..e4aa2ceda6 100644 --- a/system/doc/reference_manual/typespec.xml +++ b/system/doc/reference_manual/typespec.xml @@ -217,6 +217,9 @@ <cell><c>iolist()</c></cell><cell><c>maybe_improper_list(byte() | binary() | iolist(), binary() | [])</c></cell> </row> <row> + <cell><c>function()</c></cell><cell><c>fun()</c></cell> + </row> + <row> <cell><c>module()</c></cell><cell><c>atom()</c></cell> </row> <row> |