From a9090dfc73b4c82a966e4ffcc930190142f37bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Thu, 24 Sep 2026 12:50:21 -0400 Subject: [PATCH 1/3] mixin: move the `-D` CLI option to its own module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows tools to import only the `-m` option without `-D`. Signed-off-by: Alexis Laferrière --- src/compiler/abstract_compiler.nit | 1 + src/define.nit | 103 ++++++++++++++++++++++++++ src/interpreter/naive_interpreter.nit | 1 + src/mixin.nit | 78 +------------------ 4 files changed, 108 insertions(+), 75 deletions(-) create mode 100644 src/define.nit diff --git a/src/compiler/abstract_compiler.nit b/src/compiler/abstract_compiler.nit index 31e1e6dca5..e04910b706 100644 --- a/src/compiler/abstract_compiler.nit +++ b/src/compiler/abstract_compiler.nit @@ -23,6 +23,7 @@ import platform import c_tools private import annotation import mixin +import define import counter import pkgconfig private import explain_assert_api diff --git a/src/define.nit b/src/define.nit new file mode 100644 index 0000000000..d7ee14fb70 --- /dev/null +++ b/src/define.nit @@ -0,0 +1,103 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# 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. + +# Top-level function refinements at link-time. +module define + +import mixin + +redef class ToolContext + # --define + var opt_defines = new OptionArray("Define a specific property", "-D", "--define") + + redef init + do + super + option_context.add_option(opt_defines) + end + + redef fun make_main_module(mmodules) + do + var mainmodule = super + + var defines = opt_defines.value + if not defines.is_empty then + var location = mainmodule.location + var model = mainmodule.model + + # Create a fictive module if needed + if mainmodule == mmodules.first then + mainmodule = new MModule(model, null, mainmodule.name + "-d", location) + mainmodule.set_imported_mmodules(mmodules) + mainmodule.is_fictive = true + mainmodule.first_real_mmodule = mmodules.first + end + + var recv = mainmodule.sys_type + assert recv != null + var mclassdef = new MClassDef(mainmodule, recv, location) + mclassdef.add_in_hierarchy + + for define in defines do + var spl = define.split_once_on('=') + var name = spl.first + var val = null + if spl.length > 1 then val = spl[1] + var prop = mainmodule.try_get_primitive_method(name, recv.mclass) + if prop == null then + error(null, "Error: --define: no top-level function `{name}`.") + continue + end + var ret = prop.intro.msignature.return_mtype + var v + if ret == null then + error(null, "Error: --define: method `{prop}` is not a function.") + continue + else if ret.to_s == "Bool" then + if val == null or val == "true" then + v = true + else if val == "false" then + v = false + else + error(null, "Error: --define: method `{prop}` needs a Bool.") + continue + end + else if ret.to_s == "Int" then + if val != null and val.is_numeric then + v = val.to_i + else + error(null, "Error: --define: method `{prop}` needs a Int.") + continue + end + else if ret.to_s == "String" then + if val != null then + v = val + else + error(null, "Error: --define: method `{prop}` needs a String.") + continue + end + else + error(null, "Error: --define: method `{prop}` returns an unsupported type `{ret}`.") + continue + end + var pd = new MMethodDef(mclassdef, prop, location) + pd.msignature = prop.intro.msignature + pd.constant_value = v + end + check_errors + end + + return mainmodule + end +end diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index e5adfe68f6..468a2e29f5 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -21,6 +21,7 @@ import literal import semantize private import parser::tables import mixin +import define private import model::serialize_model private import frontend::explain_assert_api diff --git a/src/mixin.nit b/src/mixin.nit index 1e39970417..3a9f5af3ef 100644 --- a/src/mixin.nit +++ b/src/mixin.nit @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Loading and additional module refinements at link-time. +# Loading of additional modules at link-time. # # Used to factorize some code used by the engines. module mixin @@ -22,13 +22,11 @@ import modelbuilder redef class ToolContext # --mixin var opt_mixins = new OptionArray("Additional module to mix-in", "-m", "--mixin") - # --define - var opt_defines = new OptionArray("Define a specific property", "-D", "--define") redef init do super - option_context.add_option(opt_mixins, opt_defines) + option_context.add_option(opt_mixins) end redef fun make_main_module(mmodules) @@ -38,76 +36,6 @@ redef class ToolContext mmodules.add_all modelbuilder.parse(opt_mixins.value) modelbuilder.run_phases end - - var mainmodule = super - - var defines = opt_defines.value - if not defines.is_empty then - var location = mainmodule.location - var model = mainmodule.model - - # Create a fictive module if needed - if mainmodule == mmodules.first then - mainmodule = new MModule(model, null, mainmodule.name + "-d", location) - mainmodule.set_imported_mmodules(mmodules) - mainmodule.is_fictive = true - mainmodule.first_real_mmodule = mmodules.first - end - - var recv = mainmodule.sys_type - assert recv != null - var mclassdef = new MClassDef(mainmodule, recv, location) - mclassdef.add_in_hierarchy - - for define in defines do - var spl = define.split_once_on('=') - var name = spl.first - var val = null - if spl.length > 1 then val = spl[1] - var prop = mainmodule.try_get_primitive_method(name, recv.mclass) - if prop == null then - error(null, "Error: --define: no top-level function `{name}`.") - continue - end - var ret = prop.intro.msignature.return_mtype - var v - if ret == null then - error(null, "Error: --define: method `{prop}` is not a function.") - continue - else if ret.to_s == "Bool" then - if val == null or val == "true" then - v = true - else if val == "false" then - v = false - else - error(null, "Error: --define: method `{prop}` needs a Bool.") - continue - end - else if ret.to_s == "Int" then - if val != null and val.is_numeric then - v = val.to_i - else - error(null, "Error: --define: method `{prop}` needs a Int.") - continue - end - else if ret.to_s == "String" then - if val != null then - v = val - else - error(null, "Error: --define: method `{prop}` needs a String.") - continue - end - else - error(null, "Error: --define: method `{prop}` returns an unsupported type `{ret}`.") - continue - end - var pd = new MMethodDef(mclassdef, prop, location) - pd.msignature = prop.intro.msignature - pd.constant_value = v - end - check_errors - end - - return mainmodule + return super end end From f0cda947ccc56161bf8574cc516c951f9b89fb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Thu, 24 Sep 2026 12:50:35 -0400 Subject: [PATCH 2/3] modelbuilder: extract the logic building the fictive main module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract `combine_main_module` out of `make_main_module` to be used from nitls. This service builds the main module of a program, importing all its modules and applying the conditional importations. The service `make_main_module` remains to be shared by the different engines and run the phases on the newly created fictive main module. Signed-off-by: Alexis Laferrière --- src/modelbuilder.nit | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/modelbuilder.nit b/src/modelbuilder.nit index b44270432a..2f679eaf84 100644 --- a/src/modelbuilder.nit +++ b/src/modelbuilder.nit @@ -36,21 +36,11 @@ redef class ToolContext # Combine module to make a single one if required. fun make_main_module(mmodules: Array[MModule]): MModule do - assert not mmodules.is_empty - var mainmodule - # We need a main module, so we build it by importing all modules - mainmodule = new MModule(modelbuilder.model, null, mmodules.first.name + "-m", new Location(mmodules.first.location.file, 0, 0, 0, 0)) - mainmodule.is_fictive = true - mainmodule.first_real_mmodule = mmodules.first.first_real_mmodule - mainmodule.set_imported_mmodules(mmodules) - modelbuilder.apply_conditional_importations(mainmodule) - if mainmodule.in_importation.direct_greaters.length == 1 and mainmodule.in_importation.direct_greaters.first == mmodules.first then - # Drop the fictive module if not needed - mainmodule = mmodules.first - else - # Or else run phases on it - modelbuilder.run_phases - end + var mainmodule = modelbuilder.combine_main_module(mmodules) + + # Run phases on the fictive module if we created one + if mainmodule.is_fictive then modelbuilder.run_phases + return mainmodule end @@ -89,6 +79,27 @@ end redef class ModelBuilder + # Combine `mmodules` into the main module of a program and apply conditional importations + # + # Create a fictive module to import all the modules, unless the first one already + # imports the others, then we can reuse it. + fun combine_main_module(mmodules: Array[MModule]): MModule + do + assert not mmodules.is_empty + var mainmodule + # We need a main module, so we build it by importing all modules + mainmodule = new MModule(model, null, mmodules.first.name + "-m", new Location(mmodules.first.location.file, 0, 0, 0, 0)) + mainmodule.is_fictive = true + mainmodule.first_real_mmodule = mmodules.first.first_real_mmodule + mainmodule.set_imported_mmodules(mmodules) + apply_conditional_importations(mainmodule) + if mainmodule.in_importation.direct_greaters.length == 1 and mainmodule.in_importation.direct_greaters.first == mmodules.first then + # Drop the fictive module if not needed + mainmodule = mmodules.first + end + return mainmodule + end + # Run phases on all loaded modules fun run_phases do From 3b7beddabda293d69f74e48f9148ebc2fb8dd3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Wed, 4 Nov 2026 12:19:12 -0500 Subject: [PATCH 3/3] nitls: accept the -m CLI option and apply conditional imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without knowledge of -m, nitls would parse it as a module name and fail silently when used to build the dependencies of a Makefile. The module name after the `-m` was used as a target module but wouldn't trigger conditional imports as it was treated as a different entrypoint. Allow nitls to support conditional imports by making it aware of `-m` and have it build the fictive main module when needed. Signed-off-by: Alexis Laferrière --- src/nitls.nit | 7 ++++++- tests/nitls.args | 5 +++++ tests/sav/nitls_args10.res | 1 + tests/sav/nitls_args15.res | 2 ++ tests/sav/nitls_args16.res | 5 +++++ tests/sav/nitls_args17.res | 3 +++ tests/sav/nitls_args18.res | 4 ++++ tests/sav/nitls_args19.res | 4 ++++ 8 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/sav/nitls_args15.res create mode 100644 tests/sav/nitls_args16.res create mode 100644 tests/sav/nitls_args17.res create mode 100644 tests/sav/nitls_args18.res create mode 100644 tests/sav/nitls_args19.res diff --git a/src/nitls.nit b/src/nitls.nit index b716b02c4a..60be1fee0c 100644 --- a/src/nitls.nit +++ b/src/nitls.nit @@ -18,6 +18,7 @@ module nitls import modelbuilder +import mixin import loader_header_only import ordered_tree import console @@ -154,9 +155,10 @@ if sum == 0 then end var mmodules = mb.scan_full(files) +var mixins = mb.scan_full(tc.opt_mixins.value) # Load modules to get more informations -for mmodule in mmodules do +for mmodule in mmodules + mixins do if not opt_paths.value or opt_depends.value then var ast = mmodule.parse(mb) if ast != null and opt_depends.value then @@ -167,6 +169,9 @@ end #tc.check_errors if opt_depends.value then + # Apply the conditional importations + for mmodule in mmodules do mb.combine_main_module([mmodule] + mixins) + # Extends the list of module with the loaded ones mmodules = mb.parsed_modules.to_a end diff --git a/tests/nitls.args b/tests/nitls.args index 28b5957d38..f34e2789ec 100644 --- a/tests/nitls.args +++ b/tests/nitls.args @@ -12,3 +12,8 @@ test_prog/game/excluded.nit test_prog/game/excluded_dir/more.nit -t --no-color -M nitls_header_redef.nit -M error_mod_unk.nit --no-color -M nitls_error_header.nit +-M base_module_conditional.nit +-M base_module_conditional_client.nit +-M base_module_conditional.nit module_2.nit +-M base_module_conditional.nit -m module_2.nit +-M base_module_conditional.nit module_0.nit -m module_2.nit diff --git a/tests/sav/nitls_args10.res b/tests/sav/nitls_args10.res index a5ab79683f..ce78089855 100644 --- a/tests/sav/nitls_args10.res +++ b/tests/sav/nitls_args10.res @@ -1,2 +1,3 @@ base_simple.nit +base_simple_import.nit nitls_header_annotations.nit diff --git a/tests/sav/nitls_args15.res b/tests/sav/nitls_args15.res new file mode 100644 index 0000000000..c8869d728a --- /dev/null +++ b/tests/sav/nitls_args15.res @@ -0,0 +1,2 @@ +base_module_conditional.nit +module_0.nit diff --git a/tests/sav/nitls_args16.res b/tests/sav/nitls_args16.res new file mode 100644 index 0000000000..972462f7cf --- /dev/null +++ b/tests/sav/nitls_args16.res @@ -0,0 +1,5 @@ +base_module_conditional.nit +base_module_conditional_client.nit +module_0.nit +module_1.nit +module_2.nit diff --git a/tests/sav/nitls_args17.res b/tests/sav/nitls_args17.res new file mode 100644 index 0000000000..928f1f27fc --- /dev/null +++ b/tests/sav/nitls_args17.res @@ -0,0 +1,3 @@ +base_module_conditional.nit +module_0.nit +module_2.nit diff --git a/tests/sav/nitls_args18.res b/tests/sav/nitls_args18.res new file mode 100644 index 0000000000..41f89a5a42 --- /dev/null +++ b/tests/sav/nitls_args18.res @@ -0,0 +1,4 @@ +base_module_conditional.nit +module_0.nit +module_1.nit +module_2.nit diff --git a/tests/sav/nitls_args19.res b/tests/sav/nitls_args19.res new file mode 100644 index 0000000000..41f89a5a42 --- /dev/null +++ b/tests/sav/nitls_args19.res @@ -0,0 +1,4 @@ +base_module_conditional.nit +module_0.nit +module_1.nit +module_2.nit