From: Jean Privat Date: Mon, 6 Feb 2017 13:39:02 +0000 (-0500) Subject: Merge: nitc: inject importation X-Git-Url: http://nitlanguage.org?hp=eb56644e6254978f32bb7d46a06aa9ac2e83a5be Merge: nitc: inject importation Extends the nitc code so a phase can inject new submodules. The main new method the this PR is `ModelBuilder::inject_module_subimportation` that must be used during the analysis on a module. Up to now, the module hierarchy was fixed and built by the loader before any phases are run. The basic way was: 1. load the main module, 2. load its imported modules recursively and build the hierarchy, 3. run the phases on all the loaded modules from the most general to the most specific (top-down) Now the phases can also extends the module hierarchy while running some phases. This cause the following changes: * `run_phase` use a work-list (instead of a simple loop) * new modules can pop up even for the main module of a program, so a fictive main module is always created * conditional_importations is extended to be used to by inject_module_subimportation. Pull-Request: #2357 Reviewed-by: Alexandre Terrasa Reviewed-by: Romain Chanoir Reviewed-by: Alexis Laferrière Reviewed-by: Jean-Christophe Beaupré --- diff --git a/src/loader.nit b/src/loader.nit index ac07e71..32749aa 100644 --- a/src/loader.nit +++ b/src/loader.nit @@ -1045,7 +1045,7 @@ redef class ModelBuilder # (and `build_module_importation` that calls it). # # TODO (when the loader will be rewritten): use a better representation and move up rules in the model. - private var conditional_importations = new Array[SequenceRead[MModule]] + var conditional_importations = new Array[SequenceRead[MModule]] # Extends the current importations according to imported rules about conditional importation fun apply_conditional_importations(mmodule: MModule) @@ -1061,7 +1061,7 @@ redef class ModelBuilder for i in [1..ci.length[ do var m = ci[i] # Is imported? - if not mmodule.in_importation.greaters.has(m) then continue label + if mmodule == m or not mmodule.in_importation.greaters.has(m) then continue label end # Still here? It means that all conditions modules are loaded and imported diff --git a/src/mixin.nit b/src/mixin.nit index 508fc67..1e39970 100644 --- a/src/mixin.nit +++ b/src/mixin.nit @@ -46,10 +46,12 @@ redef class ToolContext 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 diff --git a/src/model/mmodule.nit b/src/model/mmodule.nit index 4cdd340..44eaffb 100644 --- a/src/model/mmodule.nit +++ b/src/model/mmodule.nit @@ -251,15 +251,13 @@ class MModule # Is `self` a unit test module used by `nitunit`? var is_test_suite: Bool = false is writable - # Get the first non `is_fictive` module greater than self - fun first_real_mmodule: MModule - do - var mmodule = self - while mmodule.is_fictive do - mmodule = mmodule.in_importation.direct_greaters.first - end - return mmodule - end + # Get the non-`is_fictive` module on which `self` is based on. + # + # On non-fictive module, this returns `self`. + # On fictive modules, this is used to refer the module which `self` is based on. + # + # This attribute should be set when a fictive module is created. See `is_fictive`. + var first_real_mmodule: MModule = self is writable redef fun parent_concern do return mgroup end diff --git a/src/modelbuilder.nit b/src/modelbuilder.nit index 671fcd0..b442704 100644 --- a/src/modelbuilder.nit +++ b/src/modelbuilder.nit @@ -38,14 +38,17 @@ redef class ToolContext do assert not mmodules.is_empty var mainmodule - if mmodules.length == 1 then + # 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 - # 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.set_imported_mmodules(mmodules) - modelbuilder.apply_conditional_importations(mainmodule) + # Or else run phases on it modelbuilder.run_phases end return mainmodule @@ -104,4 +107,22 @@ redef class ModelBuilder end end + # Load module `filename` and add it as a conditional importation of `mmodule`. + # + # This means that current (and future) submodules of `module` will also import `filename`. + fun inject_module_subimportation(mmodule: MModule, filename: String) + do + var am = load_module(filename) + if am == null then return # forward error + var mm = am.mmodule + if mm == null then return # forward error + # Add the new module before the existing submodules in the hierarchy + for subm in mmodule.in_importation.direct_smallers do + subm.set_imported_mmodules([mm]) + end + # Register the new module as a conditional_importations for future submodules + conditional_importations.add([mm, mmodule]) + # Register the new amodule to be processed by `run_phases` + toolcontext.todo_nmodules.unshift am + end end diff --git a/src/phase.nit b/src/phase.nit index c94dcdc..2d400c9 100644 --- a/src/phase.nit +++ b/src/phase.nit @@ -86,6 +86,11 @@ redef class ToolContext # Set of already analyzed modules. private var phased_modules = new HashSet[AModule] + # List of module to process according to `run_phases` + # + # This allow some new modules to be found and added while analysing the code. + var todo_nmodules: Sequence[AModule] + # Run all registered phases on a set of modules fun run_phases(nmodules: Collection[AModule]) do @@ -99,7 +104,11 @@ redef class ToolContext self.info(" registered phases: {phase}", 2) end - for nmodule in nmodules do + var todo_nmodules = nmodules.to_a + self.todo_nmodules = todo_nmodules + + while not todo_nmodules.is_empty do + var nmodule = todo_nmodules.shift if phased_modules.has(nmodule) then continue phased_modules.add nmodule diff --git a/tests/sav/nitmetrics_args1.res b/tests/sav/nitmetrics_args1.res index 2eb19e5..b7fdbd8 100644 --- a/tests/sav/nitmetrics_args1.res +++ b/tests/sav/nitmetrics_args1.res @@ -89,41 +89,45 @@ total: 0 --- Poset metrics --- ## Module importation hierarchy -Number of nodes: 1 -Number of edges: 1 (1.00 per node) -Number of direct edges: 0 (0.00 per node) +Number of nodes: 2 +Number of edges: 3 (1.50 per node) +Number of direct edges: 1 (0.50 per node) Distribution of greaters - population: 1 + population: 2 minimum value: 1 - maximum value: 1 - total value: 1 - average value: 1.00 + maximum value: 2 + total value: 3 + average value: 1.50 distribution: - <=1: sub-population=1 (100.00%); cumulated value=1 (100.00%) + <=1: sub-population=1 (50.00%); cumulated value=1 (33.33%) + <=2: sub-population=1 (50.00%); cumulated value=2 (66.66%) Distribution of direct greaters - population: 1 + population: 2 minimum value: 0 - maximum value: 0 - total value: 0 - average value: 0.00 + maximum value: 1 + total value: 1 + average value: 0.50 distribution: - <=0: sub-population=1 (100.00%); cumulated value=0 (na%) + <=0: sub-population=1 (50.00%); cumulated value=0 (0.00%) + <=1: sub-population=1 (50.00%); cumulated value=1 (100.00%) Distribution of smallers - population: 1 + population: 2 minimum value: 1 - maximum value: 1 - total value: 1 - average value: 1.00 + maximum value: 2 + total value: 3 + average value: 1.50 distribution: - <=1: sub-population=1 (100.00%); cumulated value=1 (100.00%) + <=1: sub-population=1 (50.00%); cumulated value=1 (33.33%) + <=2: sub-population=1 (50.00%); cumulated value=2 (66.66%) Distribution of direct smallers - population: 1 + population: 2 minimum value: 0 - maximum value: 0 - total value: 0 - average value: 0.00 + maximum value: 1 + total value: 1 + average value: 0.50 distribution: - <=0: sub-population=1 (100.00%); cumulated value=0 (na%) + <=0: sub-population=1 (50.00%); cumulated value=0 (0.00%) + <=1: sub-population=1 (50.00%); cumulated value=1 (100.00%) ## Classdef hierarchy Number of nodes: 7 Number of edges: 13 (1.85 per node) @@ -205,7 +209,7 @@ Distribution of direct smallers <=0: sub-population=6 (85.71%); cumulated value=0 (0.00%) <=8: sub-population=1 (14.28%); cumulated value=6 (100.00%) --- Metrics of refinement usage --- -Number of modules: 1 +Number of modules: 2 Number of classes: 7 Number of interface kind: 1 (14.28%) @@ -417,17 +421,17 @@ generating module_hierarchy.dot std: 0.0 sum: 0 mnoc: number of child modules - avg: 0.0 - max: base_simple3 (0) - min: base_simple3 (0) + avg: 1.0 + max: base_simple3 (1) + min: base_simple3 (1) std: 0.0 - sum: 0 + sum: 1 mnod: number of descendant modules - avg: 0.0 - max: base_simple3 (0) - min: base_simple3 (0) + avg: 1.0 + max: base_simple3 (1) + min: base_simple3 (1) std: 0.0 - sum: 0 + sum: 1 mdit: depth in module tree avg: 0.0 max: base_simple3 (0) @@ -479,17 +483,17 @@ generating module_hierarchy.dot std: 0.0 sum: 0 mnoc: number of child modules - avg: 0.0 - max: base_simple3 (0) - min: base_simple3 (0) + avg: 1.0 + max: base_simple3 (1) + min: base_simple3 (1) std: 0.0 - sum: 0 + sum: 1 mnod: number of descendant modules - avg: 0.0 - max: base_simple3 (0) - min: base_simple3 (0) + avg: 1.0 + max: base_simple3 (1) + min: base_simple3 (1) std: 0.0 - sum: 0 + sum: 1 mdit: depth in module tree avg: 0.0 max: base_simple3 (0) diff --git a/tests/sav/test_test_phase_args1.res b/tests/sav/test_test_phase_args1.res index 03cf44d..aa04d7f 100644 --- a/tests/sav/test_test_phase_args1.res +++ b/tests/sav/test_test_phase_args1.res @@ -1,6 +1,6 @@ It works I have 1 packages -I have 1 modules +I have 2 modules I have 7 classes For 7 definitions of classes I have 15 methods