diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd6fbef48c..35f31da43c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for configuring pins and width for sdmmc on ESP32
- Added support for map comprehensions
- Added USB CDC port drivers for ESP32, RP2, and STM32 platforms
+- Added `erlang:term_to_binary/2`, `erlang:is_builtin/3` and `erlang:bitstring_to_list/1`
### Changed
- Updated network type db() to dbm() to reflect the actual representation of the type
diff --git a/libs/estdlib/src/erlang.erl b/libs/estdlib/src/erlang.erl
index 760063cec6..317021f73b 100644
--- a/libs/estdlib/src/erlang.erl
+++ b/libs/estdlib/src/erlang.erl
@@ -52,6 +52,7 @@
erase/0,
erase/1,
function_exported/3,
+ is_builtin/3,
loaded/0,
module_loaded/1,
display/1,
@@ -71,6 +72,7 @@
binary_to_integer/1,
binary_to_integer/2,
binary_to_list/1,
+ bitstring_to_list/1,
atom_to_binary/1,
atom_to_binary/2,
atom_to_list/1,
@@ -122,6 +124,7 @@
garbage_collect/1,
binary_to_term/1,
term_to_binary/1,
+ term_to_binary/2,
split_binary/2,
crc32/1,
crc32/2,
@@ -183,7 +186,8 @@
atomvm_heap_growth_strategy/0,
stacktrace/0,
stacktrace_extrainfo/0,
- raise_stacktrace/0
+ raise_stacktrace/0,
+ term_to_binary_option/0
]).
-type atom_encoding() :: latin1 | utf8 | unicode.
@@ -202,6 +206,12 @@
-type demonitor_option() :: flush | {flush, boolean()} | info | {info, boolean()}.
+-type term_to_binary_option() ::
+ compressed
+ | {compressed, Level :: 0..9}
+ | deterministic
+ | {minor_version, Version :: 0..2}.
+
-type atomvm_heap_growth_strategy() ::
bounded_free
| minimum
@@ -678,6 +688,27 @@ erase(_Key) ->
function_exported(_Module, _Function, _Arity) ->
erlang:nif_error(undefined).
+%%-----------------------------------------------------------------------------
+%% @param Module module name
+%% @param Function function name
+%% @param Arity function arity
+%% @returns `true' if `Module:Function/Arity' is a builtin, `false' otherwise.
+%% @doc Determine whether a function is implemented natively (as a BIF or a
+%% NIF) by AtomVM. As on BEAM, this reflects functions "implemented in
+%% C" rather than in Erlang, but it is answered against AtomVM's own
+%% registry. It therefore differs from BEAM whenever the two virtual
+%% machines disagree on whether a given function is native: for
+%% example `erlang:atom_to_binary/1' is native on AtomVM but
+%% Erlang-implemented on BEAM, while `erlang:md5/1' is a BEAM BIF that
+%% AtomVM implements in Erlang. An arity that no builtin has (including a
+%% negative integer) returns `false'. Raises `badarg' if `Module' or
+%% `Function' is not an atom, or `Arity' is not an integer.
+%% @end
+%%-----------------------------------------------------------------------------
+-spec is_builtin(Module :: module(), Function :: atom(), Arity :: arity()) -> boolean().
+is_builtin(_Module, _Function, _Arity) ->
+ erlang:nif_error(undefined).
+
%%-----------------------------------------------------------------------------
%% @returns list of loaded modules
%% @doc Returns all loaded modules.
@@ -892,6 +923,20 @@ binary_to_integer(_Binary, _Base) ->
binary_to_list(_Binary) ->
erlang:nif_error(undefined).
+%%-----------------------------------------------------------------------------
+%% @param Bitstring Bitstring to convert to list
+%% @returns a list of bytes from the bitstring
+%% @doc Convert a bitstring to a list of bytes.
+%%
+%% Unlike Erlang/OTP, AtomVM only supports byte-aligned bitstrings (binaries),
+%% so the returned list never has a trailing bitstring and this function
+%% behaves like `binary_to_list/1'.
+%% @end
+%%-----------------------------------------------------------------------------
+-spec bitstring_to_list(Bitstring :: bitstring()) -> [byte()].
+bitstring_to_list(_Bitstring) ->
+ erlang:nif_error(undefined).
+
%%-----------------------------------------------------------------------------
%% @param Atom Atom to convert
%% @returns a binary with the atom's name
@@ -1516,10 +1561,8 @@ garbage_collect(_Pid) ->
%%-----------------------------------------------------------------------------
%% @returns A term decoded from passed binary
%% @param Binary binary to decode
-%% @doc Decode a term that was previously encodes with `term_to_binary/1'
+%% @doc Decode a term that was previously encoded with `term_to_binary/1'.
%% This function should be mostly compatible with its Erlang/OTP counterpart.
-%% Unlike modern Erlang/OTP, resources are currently serialized as empty
-%% binaries and cannot be unserialized.
%% @end
%%-----------------------------------------------------------------------------
-spec binary_to_term(Binary :: binary()) -> any().
@@ -1531,14 +1574,42 @@ binary_to_term(_Binary) ->
%% @param Term term to encode
%% @doc Encode a term to a binary that can later be decoded with `binary_to_term/1'.
%% This function should be mostly compatible with its Erlang/OTP counterpart.
-%% Unlike modern Erlang/OTP, resources are currently serialized as empty
-%% binaries.
%% @end
%%-----------------------------------------------------------------------------
-spec term_to_binary(Term :: any()) -> binary().
term_to_binary(_Term) ->
erlang:nif_error(undefined).
+%%-----------------------------------------------------------------------------
+%% @returns A binary encoding passed term.
+%% @param Term term to encode
+%% @param Options encoding options
+%% @doc Encode a term to a binary that can later be decoded with `binary_to_term/1'.
+%%
+%% The following options are accepted:
+%%
+%% - `compressed' and `{compressed, Level}' (`Level' from 0 to 9): accepted
+%% for compatibility but ignored. AtomVM's encoding is always
+%% uncompressed; the resulting binary is still a valid external term that
+%% `binary_to_term/1' can decode.
+%% - `deterministic': accepted for compatibility. AtomVM's encoding is
+%% already deterministic, so this option has no effect.
+%% - `{minor_version, Version}' (`Version' from 0 to 2): accepted for
+%% compatibility. AtomVM always encodes floats using the `NEW_FLOAT_EXT'
+%% representation (minor version 1 and later).
+%%
+%%
+%% Unlike Erlang/OTP, the `local' option is not supported: AtomVM never emits
+%% the node-local `LOCAL_EXT' encoding. Passing `local', or any other unknown
+%% option, raises a `badarg' error.
+%%
+%% This function should be mostly compatible with its Erlang/OTP counterpart.
+%% @end
+%%-----------------------------------------------------------------------------
+-spec term_to_binary(Term :: any(), Options :: [term_to_binary_option()]) -> binary().
+term_to_binary(_Term, _Options) ->
+ erlang:nif_error(undefined).
+
%%-----------------------------------------------------------------------------
%% @returns A tuple with two subbinaries
%% @param Bin binary to split
diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c
index 2e3f70f7eb..185af177d4 100644
--- a/src/libAtomVM/nifs.c
+++ b/src/libAtomVM/nifs.c
@@ -241,6 +241,7 @@ static term nif_erlang_port_to_list(Context *ctx, int argc, term argv[]);
static term nif_erlang_ref_to_list(Context *ctx, int argc, term argv[]);
static term nif_erlang_fun_to_list(Context *ctx, int argc, term argv[]);
static term nif_erlang_function_exported(Context *ctx, int argc, term argv[]);
+static term nif_erlang_is_builtin(Context *ctx, int argc, term argv[]);
static term nif_erlang_garbage_collect(Context *ctx, int argc, term argv[]);
static term nif_erlang_group_leader(Context *ctx, int argc, term argv[]);
static term nif_erlang_get_module_info(Context *ctx, int argc, term argv[]);
@@ -688,6 +689,10 @@ static const struct Nif function_exported_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_erlang_function_exported
};
+static const struct Nif is_builtin_nif = {
+ .base.type = NIFFunctionType,
+ .nif_ptr = nif_erlang_is_builtin
+};
static const struct Nif garbage_collect_nif = {
.base.type = NIFFunctionType,
@@ -3439,10 +3444,48 @@ static term nif_erlang_binary_to_term(Context *ctx, int argc, term argv[])
}
}
+// AtomVM's external term encoding is already deterministic and always uses
+// NEW_FLOAT_EXT (minor_version >= 1). `compressed' is accepted but ignored:
+// the uncompressed encoding is a valid external term that any
+// binary_to_term can read.
+static bool is_valid_term_to_binary_option(Context *ctx, term opt)
+{
+ if (term_is_atom(opt)) {
+ return globalcontext_is_term_equal_to_atom_string(ctx->global, opt, ATOM_STR("\xD", "deterministic"))
+ || globalcontext_is_term_equal_to_atom_string(ctx->global, opt, ATOM_STR("\xA", "compressed"));
+ }
+ if (term_is_tuple(opt) && term_get_tuple_arity(opt) == 2) {
+ term name = term_get_tuple_element(opt, 0);
+ term value = term_get_tuple_element(opt, 1);
+ if (!term_is_integer(value)) {
+ return false;
+ }
+ avm_int_t int_value = term_to_int(value);
+ if (globalcontext_is_term_equal_to_atom_string(ctx->global, name, ATOM_STR("\xD", "minor_version"))) {
+ return int_value >= 0 && int_value <= 2;
+ }
+ if (globalcontext_is_term_equal_to_atom_string(ctx->global, name, ATOM_STR("\xA", "compressed"))) {
+ return int_value >= 0 && int_value <= 9;
+ }
+ }
+ return false;
+}
+
static term nif_erlang_term_to_binary(Context *ctx, int argc, term argv[])
{
- UNUSED(argc);
term t = argv[0];
+ if (argc == 2) {
+ term options = argv[1];
+ while (term_is_nonempty_list(options)) {
+ if (UNLIKELY(!is_valid_term_to_binary_option(ctx, term_get_list_head(options)))) {
+ RAISE_ERROR(BADARG_ATOM);
+ }
+ options = term_get_list_tail(options);
+ }
+ if (UNLIKELY(!term_is_nil(options))) {
+ RAISE_ERROR(BADARG_ATOM);
+ }
+ }
term ret = external_term_to_binary(ctx, t);
if (term_is_invalid_term(ret)) {
RAISE_ERROR(BADARG_ATOM);
@@ -4661,6 +4704,36 @@ static term nif_erlang_function_exported(Context *ctx, int argc, term argv[])
return TRUE_ATOM;
}
+static term nif_erlang_is_builtin(Context *ctx, int argc, term argv[])
+{
+ UNUSED(argc);
+
+ term module = argv[0];
+ term function = argv[1];
+ term arity_term = argv[2];
+
+ VALIDATE_VALUE(module, term_is_atom);
+ VALIDATE_VALUE(function, term_is_atom);
+ VALIDATE_VALUE(arity_term, term_is_integer);
+
+ atom_index_t module_name_ix = term_to_atom_index(module);
+ atom_index_t function_name_ix = term_to_atom_index(function);
+
+ avm_int_t arity = term_to_int(arity_term);
+
+ char mfa[MAX_MFA_NAME_LEN];
+ atom_table_write_mfa(ctx->global->atom_table, mfa, sizeof(mfa), module_name_ix, function_name_ix, arity);
+
+ // A function is a builtin iff it is implemented natively in AtomVM, i.e.
+ // registered as a BIF or a NIF. Unlike function_exported/3, a function
+ // exported by a loaded (Erlang) module is not a builtin.
+ if (bif_registry_get_handler(mfa) != NULL || nifs_get(mfa) != NULL) {
+ return TRUE_ATOM;
+ }
+
+ return FALSE_ATOM;
+}
+
static term nif_erlang_garbage_collect(Context *ctx, int argc, term argv[])
{
if (argc == 0) {
diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf
index c7ce64cf7e..736c579123 100644
--- a/src/libAtomVM/nifs.gperf
+++ b/src/libAtomVM/nifs.gperf
@@ -65,6 +65,7 @@ erlang:binary_to_float/1, &binary_to_float_nif
erlang:binary_to_integer/1, &binary_to_integer_nif
erlang:binary_to_integer/2, &binary_to_integer_nif
erlang:binary_to_list/1, &binary_to_list_nif
+erlang:bitstring_to_list/1, &binary_to_list_nif
erlang:binary_to_existing_atom/1, &binary_to_existing_atom_1_nif
erlang:delete_element/2, &delete_element_nif
erlang:erase/0, &erase_0_nif
@@ -133,6 +134,7 @@ erlang:put/2, &put_nif
erlang:binary_to_term/1, &binary_to_term_nif
erlang:binary_to_term/2, &binary_to_term_nif
erlang:term_to_binary/1, &term_to_binary_nif
+erlang:term_to_binary/2, &term_to_binary_nif
erlang:split_binary/2, &split_binary_nif
erlang:throw/1, &throw_nif
erlang:raise/3, &raise_nif
@@ -142,6 +144,7 @@ erlang:port_to_list/1, &port_to_list_nif
erlang:ref_to_list/1, &ref_to_list_nif
erlang:fun_to_list/1, &fun_to_list_nif
erlang:function_exported/3, &function_exported_nif
+erlang:is_builtin/3, &is_builtin_nif
erlang:!/2, &send_nif
erlang:garbage_collect/0, &garbage_collect_nif
erlang:garbage_collect/1, &garbage_collect_nif
diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt
index a4b0f82b08..5b68127d51 100644
--- a/tests/erlang_tests/CMakeLists.txt
+++ b/tests/erlang_tests/CMakeLists.txt
@@ -234,6 +234,7 @@ compile_erlang(test_abs)
compile_erlang(test_is_process_alive)
compile_erlang(test_is_not_type)
compile_erlang(test_is_bitstring_is_binary)
+compile_erlang(test_is_builtin)
compile_erlang(test_badarith)
compile_erlang(test_badarith2)
compile_erlang(test_badarith3)
@@ -271,6 +272,7 @@ compile_erlang(test_heap_growth)
compile_erlang(test_system_flag)
compile_erlang(test_system_info)
compile_erlang(test_binary_to_term)
+compile_erlang(test_bitstring_to_list)
compile_erlang(test_selective_receive)
compile_erlang(test_timeout_not_integer)
compile_erlang(test_undef)
@@ -793,6 +795,7 @@ set(erlang_test_beams
test_is_process_alive.beam
test_is_not_type.beam
test_is_bitstring_is_binary.beam
+ test_is_builtin.beam
test_badarith.beam
test_badarith2.beam
test_badarith3.beam
@@ -830,6 +833,7 @@ set(erlang_test_beams
test_system_flag.beam
test_system_info.beam
test_binary_to_term.beam
+ test_bitstring_to_list.beam
test_selective_receive.beam
test_timeout_not_integer.beam
test_undef.beam
diff --git a/tests/erlang_tests/test_binary_to_term.erl b/tests/erlang_tests/test_binary_to_term.erl
index 17d507c4a1..80854e2d0b 100644
--- a/tests/erlang_tests/test_binary_to_term.erl
+++ b/tests/erlang_tests/test_binary_to_term.erl
@@ -157,8 +157,30 @@ start() ->
ok = test_safe_option(),
ok = test_invalid_export_fun_encoding(),
ok = test_atom_utf8_ext_node(),
+ ok = test_term_to_binary_options(),
0.
+test_term_to_binary_options() ->
+ T = ?MODULE:id({foo, [1, 2, 3], #{a => <<"b">>}, 3.14}),
+ Plain = erlang:term_to_binary(T),
+ Plain = erlang:term_to_binary(T, []),
+ Plain = erlang:term_to_binary(T, [deterministic]),
+ Plain = erlang:term_to_binary(T, [{minor_version, 2}, deterministic]),
+ T = erlang:binary_to_term(erlang:term_to_binary(T, [{minor_version, 1}])),
+ % compressed output may use a different encoding but must round-trip
+ T = erlang:binary_to_term(erlang:term_to_binary(T, [compressed])),
+ Big = duplicate(200, T, []),
+ Big = erlang:binary_to_term(erlang:term_to_binary(Big, [compressed, deterministic])),
+ Big = erlang:binary_to_term(erlang:term_to_binary(Big, [{compressed, 6}])),
+ ok = expect_badarg(fun() -> erlang:term_to_binary(T, [bad_option]) end),
+ ok = expect_badarg(fun() -> erlang:term_to_binary(T, not_a_list) end),
+ ok = expect_badarg(fun() -> erlang:term_to_binary(T, [{minor_version, 17}]) end),
+ ok = expect_badarg(fun() -> erlang:term_to_binary(T, [{compressed, 42}]) end),
+ ok.
+
+duplicate(0, _T, Acc) -> Acc;
+duplicate(N, T, Acc) -> duplicate(N - 1, T, [T | Acc]).
+
test_reverse(T, Interop) ->
test_reverse(T, Interop, []).
diff --git a/tests/erlang_tests/test_bitstring_to_list.erl b/tests/erlang_tests/test_bitstring_to_list.erl
new file mode 100644
index 0000000000..c499994199
--- /dev/null
+++ b/tests/erlang_tests/test_bitstring_to_list.erl
@@ -0,0 +1,50 @@
+%
+% This file is part of AtomVM.
+%
+% Copyright 2026 Paul Guyot
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+%
+% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
+%
+
+-module(test_bitstring_to_list).
+
+-export([start/0, id/1]).
+
+-define(ID(X), ?MODULE:id(X)).
+
+%% AtomVM does not support non-byte-aligned bitstrings, so only byte-aligned
+%% binaries are exercised. This matches the compiler's use of
+%% erlang:bitstring_to_list/1 (beam_core_to_ssa:pattern_bin/3), which only ever
+%% passes byte-aligned binaries.
+start() ->
+ [1, 2, 3] = erlang:bitstring_to_list(?ID(<<1, 2, 3>>)),
+ [] = erlang:bitstring_to_list(?ID(<<>>)),
+ [$a, $b, $c] = erlang:bitstring_to_list(?ID(<<"abc">>)),
+ Bytes = [0, 1, 2, 127, 128, 200, 254, 255],
+ Bytes = erlang:bitstring_to_list(?ID(<<0, 1, 2, 127, 128, 200, 254, 255>>)),
+ ok = raises_badarg(fun() -> erlang:bitstring_to_list(?ID([1, 2, 3])) end),
+ ok = raises_badarg(fun() -> erlang:bitstring_to_list(?ID(not_a_bitstring)) end),
+ 0.
+
+raises_badarg(Fun) ->
+ try Fun() of
+ Ret -> {unexpected, Ret}
+ catch
+ error:badarg -> ok;
+ C:E -> {unexpected, C, E}
+ end.
+
+id(X) ->
+ X.
diff --git a/tests/erlang_tests/test_is_builtin.erl b/tests/erlang_tests/test_is_builtin.erl
new file mode 100644
index 0000000000..617064c38e
--- /dev/null
+++ b/tests/erlang_tests/test_is_builtin.erl
@@ -0,0 +1,130 @@
+%
+% This file is part of AtomVM.
+%
+% Copyright 2026 Paul Guyot
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+%
+% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
+%
+
+-module(test_is_builtin).
+
+-export([start/0, id/1]).
+
+%% AtomVM's erlang:is_builtin/3 reports whether a function is implemented
+%% natively (as a BIF or NIF) *in AtomVM*, mirroring BEAM's "implemented in C"
+%% semantics but against AtomVM's own registry. It therefore diverges from BEAM
+%% for functions that one VM implements natively and the other implements in
+%% Erlang.
+%%
+%% This test runs on both BEAM and AtomVM: the parity cases must agree on both
+%% VMs, while known_divergences/1 asserts the *expected* per-VM answer. Running
+%% it against BEAM as well keeps the documented divergences honest -- if BEAM
+%% ever changes which functions it implements natively, or if AtomVM gains a
+%% native implementation, the corresponding assertion here will fail and force
+%% the divergence list (and the is_builtin/3 documentation in the erlang
+%% module) to be revisited.
+%%
+%% Arguments are funneled through id/1 so the compiler cannot constant-fold the
+%% calls using the host BEAM's is_builtin/3 at compile time.
+start() ->
+ ok = builtin_in_both(),
+ ok = emulated_in_both(),
+ ok = known_divergences(erlang:system_info(machine)),
+ ok = not_builtin(),
+ ok = bad_arguments(),
+ 0.
+
+%% Functions that are native BIFs on both BEAM and AtomVM.
+builtin_in_both() ->
+ true = ib(erlang, abs, 1),
+ true = ib(erlang, length, 1),
+ true = ib(erlang, is_atom, 1),
+ true = ib(erlang, byte_size, 1),
+ true = ib(erlang, hd, 1),
+ true = ib(erlang, tuple_size, 1),
+ true = ib(erlang, '+', 2),
+ true = ib(erlang, function_exported, 3),
+ true = ib(erlang, is_builtin, 3),
+ %% cross-module native functions
+ true = ib(lists, keyfind, 3),
+ ok.
+
+%% Functions implemented in Erlang (not native) on both BEAM and AtomVM.
+emulated_in_both() ->
+ false = ib(lists, foldl, 3),
+ false = ib(lists, map, 2),
+ false = ib(lists, seq, 2),
+ false = ib(proplists, get_value, 2),
+ ok.
+
+%% Functions where AtomVM and BEAM disagree because one implements them
+%% natively while the other emulates them in Erlang. Each case asserts the
+%% answer *both* VMs are expected to give, so the divergence is verified rather
+%% than merely documented.
+known_divergences(Machine) ->
+ %% erlang:atom_to_binary/1: native in AtomVM, an Erlang wrapper around
+ %% atom_to_binary/2 on BEAM.
+ AtomToBinary1 = ib(erlang, atom_to_binary, 1),
+ %% erlang:md5/1: a native C BIF on BEAM, but on AtomVM it exists only as an
+ %% Erlang function delegating to crypto, so it is not a BIF.
+ Md5 = ib(erlang, md5, 1),
+ %% maps:get/2: a native BIF on BEAM, emulated in Erlang on AtomVM.
+ MapsGet = ib(maps, get, 2),
+ case Machine of
+ "BEAM" ->
+ false = AtomToBinary1,
+ true = Md5,
+ true = MapsGet,
+ ok;
+ _ ->
+ true = AtomToBinary1,
+ false = Md5,
+ false = MapsGet,
+ ok
+ end.
+
+%% Names that are not native functions anywhere.
+not_builtin() ->
+ %% bitsize/1 is not a real function; only bit_size/1 exists.
+ false = ib(erlang, bitsize, 1),
+ %% wrong arity for an existing BIF
+ false = ib(erlang, abs, 2),
+ %% negative arity never matches
+ false = ib(erlang, abs, -1),
+ %% unknown module and unknown function
+ false = ib(no_such_module, no_such_function, 0),
+ false = ib(erlang, definitely_not_a_bif, 0),
+ ok.
+
+%% Type errors raise badarg, matching BEAM.
+bad_arguments() ->
+ ok = expect_badarg(fun() -> ib(123, foo, 0) end),
+ ok = expect_badarg(fun() -> ib(erlang, 123, 0) end),
+ ok = expect_badarg(fun() -> ib(erlang, foo, not_an_integer) end),
+ ok.
+
+ib(Module, Function, Arity) ->
+ erlang:is_builtin(?MODULE:id(Module), ?MODULE:id(Function), ?MODULE:id(Arity)).
+
+expect_badarg(Fun) ->
+ try Fun() of
+ Ret -> {unexpected, Ret}
+ catch
+ error:badarg -> ok;
+ C:E -> {unexpected, C, E}
+ end.
+
+id(X) ->
+ X.
diff --git a/tests/test.c b/tests/test.c
index 342c142fd4..c6287fb543 100644
--- a/tests/test.c
+++ b/tests/test.c
@@ -200,6 +200,7 @@ struct Test tests[] = {
TEST_CASE_EXPECTED(test_is_process_alive, 121),
TEST_CASE_EXPECTED(test_is_not_type, 255),
TEST_CASE(test_is_bitstring_is_binary),
+ TEST_CASE(test_is_builtin),
TEST_CASE_EXPECTED(test_badarith, -87381),
TEST_CASE_EXPECTED(test_badarith2, -87381),
TEST_CASE_EXPECTED(test_badarith3, -1365),
@@ -410,6 +411,7 @@ struct Test tests[] = {
TEST_CASE_EXPECTED(test_ordering_0, 1),
TEST_CASE_EXPECTED(test_ordering_1, 1),
TEST_CASE(test_binary_to_term),
+ TEST_CASE(test_bitstring_to_list),
TEST_CASE(test_selective_receive),
TEST_CASE(test_timeout_not_integer),
TEST_CASE(test_undef),