From 3ba8442140fdd1bee52cea4203730f6e6a488fb0 Mon Sep 17 00:00:00 2001 From: Gary Rennie Date: Fri, 5 Jun 2026 22:25:34 +0100 Subject: [PATCH 1/9] Add support for beam_debug_info and beam_debug_stack to support edb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We skip Core only when beam_debug_info is enabled because OTP’s BEAM debugger metadata is generated from Erlang abstract forms. Normal Elixir compilation does: Elixir AST -> Erlang abstract forms -> Core Erlang -> BEAM EDB/OTP line breakpoints need the DbgB chunk. When we pass Erlang abstract forms directly to compile:noenv_forms/2 with beam_debug_info, OTP emits useful DbgB entries. If we keep the existing Core path: Erlang abstract forms -> Core Erlang -> BEAM and then compile with from_core, OTP still produces a DbgB chunk, but it has no breakpoint entries. So EDB sees no usable executable lines. This is only for debug builds. Normal compilation still uses the existing Core path. --- lib/elixir/lib/code.ex | 10 ++++++ lib/elixir/src/elixir.erl | 2 ++ lib/elixir/src/elixir_erl.erl | 25 +++++++++++--- lib/elixir/src/elixir_erl_compiler.erl | 45 ++++++++++++++++---------- lib/elixir/test/elixir/code_test.exs | 26 +++++++++++++++ lib/elixir/test/elixir/module_test.exs | 20 ++++++++++++ 6 files changed, 106 insertions(+), 22 deletions(-) diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index 5a8204e1eab..174ad9f6b65 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -307,6 +307,8 @@ defmodule Code do | {:dbg_callback, {module(), atom(), list()}} @boolean_compiler_options [ + :beam_debug_info, + :beam_debug_stack, :docs, :debug_info, :ignore_already_consolidated, @@ -1756,6 +1758,14 @@ defmodule Code do via the `:test_elixirc_options` project configuration, as there is typically no need to store debug chunks for test files. + * `:beam_debug_info` - when `true`, emits Erlang/OTP debug metadata used by + BEAM debuggers to set line breakpoints and inspect variables. Defaults to + `false`. + + * `:beam_debug_stack` - when `true`, keeps additional stack information for + BEAM debuggers. It is only useful together with `:beam_debug_info`. + Defaults to `false`. + * `:docs` - when `true`, retains documentation in the compiled module. Defaults to `true`. diff --git a/lib/elixir/src/elixir.erl b/lib/elixir/src/elixir.erl index 9fd968e56c8..0f533279e37 100644 --- a/lib/elixir/src/elixir.erl +++ b/lib/elixir/src/elixir.erl @@ -87,6 +87,8 @@ start(_Type, _Args) -> %% Compiler options {debug_info, true}, + {beam_debug_info, false}, + {beam_debug_stack, false}, {docs, true}, {ignore_already_consolidated, false}, {ignore_module_conflict, false}, diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index b7f401ba688..a17b1b8f7a4 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -526,16 +526,31 @@ load_form(#{file := File, compile_opts := Opts} = Map, Prefix, Forms, Specs, Chu Binary. debug_opts(Map, Specs, Opts) -> - case take_debug_opts(Opts) of + BeamDebugOpts = take_beam_debug_opts(Opts), + + case take_debug_opts(BeamDebugOpts) of {true, Rest} -> [{debug_info, {?MODULE, {elixir_v1, Map, Specs}}} | Rest]; {false, Rest} -> [{debug_info, {?MODULE, none}} | Rest] end. take_debug_opts(Opts) -> - case proplists:get_value(debug_info, Opts) of - true -> {true, proplists:delete(debug_info, Opts)}; - false -> {false, proplists:delete(debug_info, Opts)}; - undefined -> {elixir_config:get(debug_info), Opts} + take_boolean_compiler_opt(debug_info, Opts). + +take_beam_debug_opts(Opts) -> + Opts0 = take_beam_debug_opt(beam_debug_info, Opts), + take_beam_debug_opt(beam_debug_stack, Opts0). + +take_beam_debug_opt(Opt, Opts) -> + case take_boolean_compiler_opt(Opt, Opts) of + {true, Rest} -> [Opt | Rest]; + {false, Rest} -> Rest + end. + +take_boolean_compiler_opt(Opt, Opts) -> + case proplists:get_value(Opt, Opts) of + true -> {true, proplists:delete(Opt, Opts)}; + false -> {false, proplists:delete(Opt, Opts)}; + undefined -> {elixir_config:get(Opt), Opts} end. extra_chunks_opts([], Opts) -> Opts; diff --git a/lib/elixir/src/elixir_erl_compiler.erl b/lib/elixir/src/elixir_erl_compiler.erl index 3f2bff12105..3a693b86afa 100644 --- a/lib/elixir/src/elixir_erl_compiler.erl +++ b/lib/elixir/src/elixir_erl_compiler.erl @@ -73,34 +73,45 @@ erl_to_core(Forms, Opts) -> noenv_forms(Forms, File, Opts) when is_list(Forms), is_list(Opts), is_binary(File) -> Source = elixir_utils:characters_to_list(File), + case lists:member(beam_debug_info, Opts) of + true -> + CompileOpts = [no_spawn_compiler_process, no_auto_import, return, {source, Source} | Opts], + compile_forms(Forms, CompileOpts, File, Opts); + false -> noenv_forms_via_core(Forms, File, Source, Opts) + end. + +noenv_forms_via_core(Forms, File, Source, Opts) -> case erl_to_core(Forms, Opts) of {ok, CoreForms, CoreWarnings} -> format_warnings(Opts, CoreWarnings), CompileOpts = [no_spawn_compiler_process, from_core, no_core_prepare, no_auto_import, return, {source, Source} | Opts], - case compile:noenv_forms(CoreForms, CompileOpts) of - {ok, Module, Binary, Warnings} when is_binary(Binary) -> - format_warnings(Opts, Warnings), - {Module, Binary}; + compile_forms(CoreForms, CompileOpts, File, Opts); - {ok, Module, _, _} -> - incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); + {error, CoreErrors, CoreWarnings} -> + format_warnings(Opts, CoreWarnings), + format_errors(CoreErrors) + end. - {ok, Module, _} -> - incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); +compile_forms(Forms, CompileOpts, File, Opts) -> + case compile:noenv_forms(Forms, CompileOpts) of + {ok, Module, Binary, Warnings} when is_binary(Binary) -> + format_warnings(Opts, Warnings), + {Module, Binary}; - {error, Errors, Warnings} -> - format_warnings(Opts, Warnings), - format_errors(Errors); + {ok, Module, _, _} -> + incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); - _ -> - incompatible_options("could not compile module", [], File) - end; + {ok, Module, _} -> + incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); - {error, CoreErrors, CoreWarnings} -> - format_warnings(Opts, CoreWarnings), - format_errors(CoreErrors) + {error, Errors, Warnings} -> + format_warnings(Opts, Warnings), + format_errors(Errors); + + _ -> + incompatible_options("could not compile module", [], File) end. incompatible_options(Prefix, Args, File) -> diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 036c516401b..51583d70698 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -917,6 +917,32 @@ defmodule Code.SyncTest do Code.put_compiler_option(:module_definition, :compiled) end + test "uses beam_debug_info compiler option" do + module = CodeTest.ConfiguredBeamDebugInfo + previous = Code.compiler_options(beam_debug_info: true) + + try do + assert [{^module, binary}] = + Code.compile_string(""" + defmodule CodeTest.ConfiguredBeamDebugInfo do + def sample(value) do + doubled = value * 2 + doubled + 1 + end + end + """) + + assert {:ok, {_, [{~c"DbgB", <<_version::32, entries::32, _::binary>>}]}} = + :beam_lib.chunks(binary, [~c"DbgB"]) + + assert entries > 0 + after + Code.compiler_options(previous) + :code.purge(module) + :code.delete(module) + end + end + test "prepend_path" do path = Path.join(__DIR__, "fixtures") true = Code.prepend_path(path) diff --git a/lib/elixir/test/elixir/module_test.exs b/lib/elixir/test/elixir/module_test.exs index 485e64ab980..c9a316b27b0 100644 --- a/lib/elixir/test/elixir/module_test.exs +++ b/lib/elixir/test/elixir/module_test.exs @@ -426,6 +426,26 @@ defmodule ModuleTest do assert map.module == ModuleCreateDebugInfoTrue end + test "uses the beam_debug_info chunk when explicitly set to true" do + contents = + quote do + @compile :beam_debug_info + + def sample(value) do + doubled = value * 2 + doubled + 1 + end + end + + {:module, ModuleCreateBeamDebugInfo, binary, _} = + Module.create(ModuleCreateBeamDebugInfo, contents, __ENV__) + + {:ok, {_, [{~c"DbgB", <<_Version::32, entries::32, _::binary>>}]}} = + :beam_lib.chunks(binary, [~c"DbgB"]) + + assert entries > 0 + end + test "uses the debug_info chunk even if debug_info is set to false" do {:module, ModuleCreateNoDebugInfo, binary, _} = Module.create(ModuleCreateNoDebugInfo, quote(do: @compile({:debug_info, false})), __ENV__) From a4fd833774c8f725dde7418141227ac09dffd5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 6 Jun 2026 10:51:36 +0200 Subject: [PATCH 2/9] Refactor noenv_forms to handle Erlang Abstract Format --- lib/elixir/src/elixir_erl_compiler.erl | 35 +++++++++++++------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/elixir/src/elixir_erl_compiler.erl b/lib/elixir/src/elixir_erl_compiler.erl index 3a693b86afa..fc930902694 100644 --- a/lib/elixir/src/elixir_erl_compiler.erl +++ b/lib/elixir/src/elixir_erl_compiler.erl @@ -73,28 +73,27 @@ erl_to_core(Forms, Opts) -> noenv_forms(Forms, File, Opts) when is_list(Forms), is_list(Opts), is_binary(File) -> Source = elixir_utils:characters_to_list(File), + %% beam_debug_info has to go through Erlang Abstract Format case lists:member(beam_debug_info, Opts) of true -> - CompileOpts = [no_spawn_compiler_process, no_auto_import, return, {source, Source} | Opts], - compile_forms(Forms, CompileOpts, File, Opts); - false -> noenv_forms_via_core(Forms, File, Source, Opts) - end. - -noenv_forms_via_core(Forms, File, Source, Opts) -> - case erl_to_core(Forms, Opts) of - {ok, CoreForms, CoreWarnings} -> - format_warnings(Opts, CoreWarnings), - CompileOpts = [no_spawn_compiler_process, from_core, no_core_prepare, - no_auto_import, return, {source, Source} | Opts], - - compile_forms(CoreForms, CompileOpts, File, Opts); - - {error, CoreErrors, CoreWarnings} -> - format_warnings(Opts, CoreWarnings), - format_errors(CoreErrors) + CompileOpts = [no_spawn_compiler_process, no_auto_import, return, + {source, Source} | Opts], + noenv_forms(Forms, CompileOpts, File, Opts); + false -> + case erl_to_core(Forms, Opts) of + {ok, CoreForms, CoreWarnings} -> + format_warnings(Opts, CoreWarnings), + CompileOpts = [no_spawn_compiler_process, from_core, no_core_prepare, + no_auto_import, return, {source, Source} | Opts], + noenv_forms(CoreForms, CompileOpts, File, Opts); + + {error, CoreErrors, CoreWarnings} -> + format_warnings(Opts, CoreWarnings), + format_errors(CoreErrors) + end end. -compile_forms(Forms, CompileOpts, File, Opts) -> +noenv_forms(Forms, CompileOpts, File, Opts) -> case compile:noenv_forms(Forms, CompileOpts) of {ok, Module, Binary, Warnings} when is_binary(Binary) -> format_warnings(Opts, Warnings), From e04d9c77205826db4c06d07a18fcb3fd4adbb467 Mon Sep 17 00:00:00 2001 From: Gary Rennie Date: Sat, 6 Jun 2026 10:34:22 +0100 Subject: [PATCH 3/9] Refactor beam_debug compile path elixir compiler --- lib/elixir/src/elixir_erl_compiler.erl | 76 ++++++++++++++------------ 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/elixir/src/elixir_erl_compiler.erl b/lib/elixir/src/elixir_erl_compiler.erl index fc930902694..52b57dfe011 100644 --- a/lib/elixir/src/elixir_erl_compiler.erl +++ b/lib/elixir/src/elixir_erl_compiler.erl @@ -60,57 +60,61 @@ env_compiler_options() -> erl_to_core(Forms, Opts) -> %% TODO: Remove parse transform handling on Elixir v2.0 - case [M || {parse_transform, M} <- Opts] of - [] -> + case requires_abstract_format(Opts, false) of + false -> v3_core:module(Forms, Opts); - _ -> + + beam_debug_info -> + {ok, DbgForms} = sys_coverage:beam_debug_info(Forms), + v3_core:module(DbgForms, Opts); + + parse_transform -> case compile:noenv_forms(Forms, [no_spawn_compiler_process, to_core0, return, no_auto_import | Opts]) of {ok, _Module, Core, Warnings} -> {ok, Core, Warnings}; {error, Errors, Warnings} -> {error, Errors, Warnings} end end. +requires_abstract_format([{parse_transform, _} | _], _Default) -> + parse_transform; +requires_abstract_format([beam_debug_info | Tail], _Default) -> + requires_abstract_format(Tail, beam_debug_info); +requires_abstract_format([_ | Tail], Default) -> + requires_abstract_format(Tail, Default); +requires_abstract_format([], Default) -> + Default. + noenv_forms(Forms, File, Opts) when is_list(Forms), is_list(Opts), is_binary(File) -> Source = elixir_utils:characters_to_list(File), - %% beam_debug_info has to go through Erlang Abstract Format - case lists:member(beam_debug_info, Opts) of - true -> - CompileOpts = [no_spawn_compiler_process, no_auto_import, return, - {source, Source} | Opts], - noenv_forms(Forms, CompileOpts, File, Opts); - false -> - case erl_to_core(Forms, Opts) of - {ok, CoreForms, CoreWarnings} -> - format_warnings(Opts, CoreWarnings), - CompileOpts = [no_spawn_compiler_process, from_core, no_core_prepare, - no_auto_import, return, {source, Source} | Opts], - noenv_forms(CoreForms, CompileOpts, File, Opts); - - {error, CoreErrors, CoreWarnings} -> - format_warnings(Opts, CoreWarnings), - format_errors(CoreErrors) - end - end. + case erl_to_core(Forms, Opts) of + {ok, CoreForms, CoreWarnings} -> + format_warnings(Opts, CoreWarnings), + CompileOpts = [no_spawn_compiler_process, from_core, no_core_prepare, + no_auto_import, return, {source, Source} | Opts], + + case compile:noenv_forms(CoreForms, CompileOpts) of + {ok, Module, Binary, Warnings} when is_binary(Binary) -> + format_warnings(Opts, Warnings), + {Module, Binary}; -noenv_forms(Forms, CompileOpts, File, Opts) -> - case compile:noenv_forms(Forms, CompileOpts) of - {ok, Module, Binary, Warnings} when is_binary(Binary) -> - format_warnings(Opts, Warnings), - {Module, Binary}; + {ok, Module, _, _} -> + incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); - {ok, Module, _, _} -> - incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); + {ok, Module, _} -> + incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); - {ok, Module, _} -> - incompatible_options("could not compile module ~ts", [elixir_aliases:inspect(Module)], File); + {error, Errors, Warnings} -> + format_warnings(Opts, Warnings), + format_errors(Errors); - {error, Errors, Warnings} -> - format_warnings(Opts, Warnings), - format_errors(Errors); + _ -> + incompatible_options("could not compile module", [], File) + end; - _ -> - incompatible_options("could not compile module", [], File) + {error, CoreErrors, CoreWarnings} -> + format_warnings(Opts, CoreWarnings), + format_errors(CoreErrors) end. incompatible_options(Prefix, Args, File) -> From ac1ef8cce128e762b081f11456510591e0486b21 Mon Sep 17 00:00:00 2001 From: Gary Rennie Date: Sat, 6 Jun 2026 10:44:49 +0100 Subject: [PATCH 4/9] Move beam_debug compile options into a single list --- lib/elixir/lib/code.ex | 16 +++++----------- lib/elixir/src/elixir.erl | 3 +-- lib/elixir/src/elixir_erl.erl | 28 +++++++--------------------- lib/elixir/test/elixir/code_test.exs | 4 ++-- 4 files changed, 15 insertions(+), 36 deletions(-) diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index 174ad9f6b65..803bfae72d5 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -307,8 +307,6 @@ defmodule Code do | {:dbg_callback, {module(), atom(), list()}} @boolean_compiler_options [ - :beam_debug_info, - :beam_debug_stack, :docs, :debug_info, :ignore_already_consolidated, @@ -316,7 +314,7 @@ defmodule Code do :relative_paths ] - @list_compiler_options [:tracers, :parser_options] + @list_compiler_options [:tracers, :parser_options, :erlc_options] @available_compiler_options @boolean_compiler_options ++ @list_compiler_options ++ @@ -1758,17 +1756,13 @@ defmodule Code do via the `:test_elixirc_options` project configuration, as there is typically no need to store debug chunks for test files. - * `:beam_debug_info` - when `true`, emits Erlang/OTP debug metadata used by - BEAM debuggers to set line breakpoints and inspect variables. Defaults to - `false`. - - * `:beam_debug_stack` - when `true`, keeps additional stack information for - BEAM debuggers. It is only useful together with `:beam_debug_info`. - Defaults to `false`. - * `:docs` - when `true`, retains documentation in the compiled module. Defaults to `true`. + * `:erlc_options` - a list of Erlang compiler options. For example, + `erlc_options: [:beam_debug_info, :beam_debug_stack]` emits Erlang/OTP + debug metadata for BEAM debuggers. Defaults to `[]`. + * `:ignore_already_consolidated` (since v1.10.0) - when `true`, does not warn when a protocol has already been consolidated and a new implementation is added. Defaults to `false`. diff --git a/lib/elixir/src/elixir.erl b/lib/elixir/src/elixir.erl index 0f533279e37..82610489902 100644 --- a/lib/elixir/src/elixir.erl +++ b/lib/elixir/src/elixir.erl @@ -87,9 +87,8 @@ start(_Type, _Args) -> %% Compiler options {debug_info, true}, - {beam_debug_info, false}, - {beam_debug_stack, false}, {docs, true}, + {erlc_options, []}, {ignore_already_consolidated, false}, {ignore_module_conflict, false}, {infer_signatures, [elixir]}, diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index a17b1b8f7a4..fc491eecfc5 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -521,36 +521,22 @@ attributes_form(Line, Attributes, Forms) -> % Loading forms load_form(#{file := File, compile_opts := Opts} = Map, Prefix, Forms, Specs, Chunks) -> - CompileOpts = extra_chunks_opts(Chunks, debug_opts(Map, Specs, Opts)), + Opts0 = Opts ++ elixir_config:get(erlc_options), + CompileOpts = extra_chunks_opts(Chunks, debug_opts(Map, Specs, Opts0)), {_, Binary} = elixir_erl_compiler:noenv_forms(Prefix ++ Specs ++ Forms, File, CompileOpts), Binary. debug_opts(Map, Specs, Opts) -> - BeamDebugOpts = take_beam_debug_opts(Opts), - - case take_debug_opts(BeamDebugOpts) of + case take_debug_opts(Opts) of {true, Rest} -> [{debug_info, {?MODULE, {elixir_v1, Map, Specs}}} | Rest]; {false, Rest} -> [{debug_info, {?MODULE, none}} | Rest] end. take_debug_opts(Opts) -> - take_boolean_compiler_opt(debug_info, Opts). - -take_beam_debug_opts(Opts) -> - Opts0 = take_beam_debug_opt(beam_debug_info, Opts), - take_beam_debug_opt(beam_debug_stack, Opts0). - -take_beam_debug_opt(Opt, Opts) -> - case take_boolean_compiler_opt(Opt, Opts) of - {true, Rest} -> [Opt | Rest]; - {false, Rest} -> Rest - end. - -take_boolean_compiler_opt(Opt, Opts) -> - case proplists:get_value(Opt, Opts) of - true -> {true, proplists:delete(Opt, Opts)}; - false -> {false, proplists:delete(Opt, Opts)}; - undefined -> {elixir_config:get(Opt), Opts} + case proplists:get_value(debug_info, Opts) of + true -> {true, proplists:delete(debug_info, Opts)}; + false -> {false, proplists:delete(debug_info, Opts)}; + undefined -> {elixir_config:get(debug_info), Opts} end. extra_chunks_opts([], Opts) -> Opts; diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 51583d70698..7a13396ee97 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -917,9 +917,9 @@ defmodule Code.SyncTest do Code.put_compiler_option(:module_definition, :compiled) end - test "uses beam_debug_info compiler option" do + test "uses erlc_options compiler option" do module = CodeTest.ConfiguredBeamDebugInfo - previous = Code.compiler_options(beam_debug_info: true) + previous = Code.compiler_options(erlc_options: [:beam_debug_info]) try do assert [{^module, binary}] = From f995c28f7631c3aef5e833852d4570b17ef35d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 6 Jun 2026 18:45:11 +0200 Subject: [PATCH 5/9] Apply suggestion from @josevalim --- lib/elixir/test/elixir/module_test.exs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/elixir/test/elixir/module_test.exs b/lib/elixir/test/elixir/module_test.exs index c9a316b27b0..485e64ab980 100644 --- a/lib/elixir/test/elixir/module_test.exs +++ b/lib/elixir/test/elixir/module_test.exs @@ -426,26 +426,6 @@ defmodule ModuleTest do assert map.module == ModuleCreateDebugInfoTrue end - test "uses the beam_debug_info chunk when explicitly set to true" do - contents = - quote do - @compile :beam_debug_info - - def sample(value) do - doubled = value * 2 - doubled + 1 - end - end - - {:module, ModuleCreateBeamDebugInfo, binary, _} = - Module.create(ModuleCreateBeamDebugInfo, contents, __ENV__) - - {:ok, {_, [{~c"DbgB", <<_Version::32, entries::32, _::binary>>}]}} = - :beam_lib.chunks(binary, [~c"DbgB"]) - - assert entries > 0 - end - test "uses the debug_info chunk even if debug_info is set to false" do {:module, ModuleCreateNoDebugInfo, binary, _} = Module.create(ModuleCreateNoDebugInfo, quote(do: @compile({:debug_info, false})), __ENV__) From b6ff4c3c16cbdd01a47cad6fb9c25f684a6b7f1b Mon Sep 17 00:00:00 2001 From: Gary Rennie Date: Sat, 6 Jun 2026 22:51:02 +0100 Subject: [PATCH 6/9] Move compile options to elixir_module --- lib/elixir/src/elixir_erl.erl | 3 +-- lib/elixir/src/elixir_module.erl | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index fc491eecfc5..b7f401ba688 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -521,8 +521,7 @@ attributes_form(Line, Attributes, Forms) -> % Loading forms load_form(#{file := File, compile_opts := Opts} = Map, Prefix, Forms, Specs, Chunks) -> - Opts0 = Opts ++ elixir_config:get(erlc_options), - CompileOpts = extra_chunks_opts(Chunks, debug_opts(Map, Specs, Opts0)), + CompileOpts = extra_chunks_opts(Chunks, debug_opts(Map, Specs, Opts)), {_, Binary} = elixir_erl_compiler:noenv_forms(Prefix ++ Specs ++ Forms, File, CompileOpts), Binary. diff --git a/lib/elixir/src/elixir_module.erl b/lib/elixir/src/elixir_module.erl index 419d361fe9c..ae6c0941c48 100644 --- a/lib/elixir/src/elixir_module.erl +++ b/lib/elixir/src/elixir_module.erl @@ -205,7 +205,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) -> 'Elixir.Module.Types':infer(Module, File, Attributes, AllDefinitions, UsedPrivate, E, CheckerInfo) end, - RawCompileOpts = bag_lookup_element(DataBag, {accumulate, compile}, 2), + RawCompileOpts = bag_lookup_element(DataBag, {accumulate, compile}, 2) ++ elixir_config:get(erlc_options), CompileOpts = validate_compile_opts(RawCompileOpts, AllDefinitions, Unreachable, Line, E), Impls = bag_lookup_element(DataBag, impls, 2), From 8140f9f4596ae755e4c3ea3b4e127fc392ac8b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 7 Jun 2026 09:50:15 +0200 Subject: [PATCH 7/9] Apply suggestion from @josevalim --- lib/elixir/lib/code.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index 803bfae72d5..97c5931c88d 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -1759,7 +1759,7 @@ defmodule Code do * `:docs` - when `true`, retains documentation in the compiled module. Defaults to `true`. - * `:erlc_options` - a list of Erlang compiler options. For example, + * `:erlc_options` (since v1.21.0) - a list of Erlang compiler options. For example, `erlc_options: [:beam_debug_info, :beam_debug_stack]` emits Erlang/OTP debug metadata for BEAM debuggers. Defaults to `[]`. From 72e71f3032cb7759d34b931b504b4497e404aeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 7 Jun 2026 09:54:40 +0200 Subject: [PATCH 8/9] Apply suggestion from @josevalim --- lib/elixir/test/elixir/code_test.exs | 48 +++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 7a13396ee97..3d0604b70db 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -917,29 +917,31 @@ defmodule Code.SyncTest do Code.put_compiler_option(:module_definition, :compiled) end - test "uses erlc_options compiler option" do - module = CodeTest.ConfiguredBeamDebugInfo - previous = Code.compiler_options(erlc_options: [:beam_debug_info]) - - try do - assert [{^module, binary}] = - Code.compile_string(""" - defmodule CodeTest.ConfiguredBeamDebugInfo do - def sample(value) do - doubled = value * 2 - doubled + 1 - end - end - """) - - assert {:ok, {_, [{~c"DbgB", <<_version::32, entries::32, _::binary>>}]}} = - :beam_lib.chunks(binary, [~c"DbgB"]) - - assert entries > 0 - after - Code.compiler_options(previous) - :code.purge(module) - :code.delete(module) + if System.otp_release() >= "29" do + test "uses erlc_options compiler option" do + module = CodeTest.ConfiguredBeamDebugInfo + previous = Code.compiler_options(erlc_options: [:beam_debug_info]) + + try do + assert [{^module, binary}] = + Code.compile_string(""" + defmodule CodeTest.ConfiguredBeamDebugInfo do + def sample(value) do + doubled = value * 2 + doubled + 1 + end + end + """) + + assert {:ok, {_, [{~c"DbgB", <<_version::32, entries::32, _::binary>>}]}} = + :beam_lib.chunks(binary, [~c"DbgB"]) + + assert entries > 0 + after + Code.compiler_options(previous) + :code.purge(module) + :code.delete(module) + end end end From de21234a0c9a31d4a9046f81176f06a2ff32eece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 7 Jun 2026 10:08:23 +0200 Subject: [PATCH 9/9] Update code_test.exs --- lib/elixir/test/elixir/code_test.exs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 3d0604b70db..2f40e2dfe21 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -921,21 +921,21 @@ defmodule Code.SyncTest do test "uses erlc_options compiler option" do module = CodeTest.ConfiguredBeamDebugInfo previous = Code.compiler_options(erlc_options: [:beam_debug_info]) - + try do assert [{^module, binary}] = - Code.compile_string(""" - defmodule CodeTest.ConfiguredBeamDebugInfo do - def sample(value) do - doubled = value * 2 - doubled + 1 - end - end - """) - + Code.compile_string(""" + defmodule CodeTest.ConfiguredBeamDebugInfo do + def sample(value) do + doubled = value * 2 + doubled + 1 + end + end + """) + assert {:ok, {_, [{~c"DbgB", <<_version::32, entries::32, _::binary>>}]}} = - :beam_lib.chunks(binary, [~c"DbgB"]) - + :beam_lib.chunks(binary, [~c"DbgB"]) + assert entries > 0 after Code.compiler_options(previous)