model: `new` factories have a return type.
[nit.git] / src / neo.nit
index f7ab770..f9d3c9a 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Save and load `Model` from/to Neo4j base.
+# Save and load a `Model` to/from a Neo4j graph.
 #
 # Nit models are composed by MEntities.
-# This module creates NeoNode for each MEntity found in a `Model` and save them into Neo4j database.
+# This module creates NeoNode for each MEntity found in a `Model` and save them
+# into Neo4j database.
 #
-# see `Neo4jClient`.
+# SEE: `Neo4jClient`
 #
 # NeoNodes can also be translated back to MEntities to rebuild a Nit `Model`.
 #
-# Structure of the nit `Model` in base:
+# Structure of the nit `Model` in the graph:
+#
+# Note : Any null or empty attribute will not be saved in the database.
+#
+# For any `MEntity` (in addition to specific data):
+#
+# * labels: model name (`model_name`) and `MEntity`.
+# * `name`: short (unqualified) name.
+# * `mdoc`: JSON array representing the associated Markdown documentation
+# (one item by line).
+#
+# Note : All nodes described here are MEntities.
 #
 # `MProject`
 #
-# * labels: `model_name`, `MEntity`, `MProject`
-# * `(:MProject)-[:ROOT]->(:MGroup)`
+# * labels: `MProject`, `model_name` and `MEntity`.
+# * `(:MProject)-[:ROOT]->(:MGroup)`: root of the group tree.
 #
 # `MGroup`
 #
-# * labels: `model_name`, `MEntity`, `MGroup`
-# * `(:MGroup)-[:PROJECT]->(:MProject)`
-# * `(:MGroup)-[:PARENT]->(:MGroup)`
+# * labels: `MGroup`, `model_name` and `MEntity`.
+# * `full_name`: fully qualified name.
+# * `(:MGroup)-[:PROJECT]->(:MProject)`: associated project.
+# * `(:MGroup)-[:PARENT]->(:MGroup)`: parent group. Does not exist for the root
+# group.
+# * `(:MGroup)-[:DECLARES]->(:MModule)`: modules that are direct children of
+# this group.
+# * `(:MGroup)-[:NESTS]->(:MGroup)`: nested groups that are direct children of
+# this group.
 #
 # `MModule`
 #
-# * labels: `model_name`, `MEntity`, `MModule`
-# * `(:MModule)-[:IMPORTS]->(:MModule)`
-# * `(:MModule)-[:INTRODUCES]->(:MClass)`
-# * `(:MModule)-[:DEFINES]->(:MClassDef)`
+# * labels: `MModule`, `model_name` and `MEntity`.
+# * `full_name`: fully qualified name.
+# * `location`: origin of the definition. SEE: `Location.to_s`
+# * `(:MModule)-[:IMPORTS]->(:MModule)`: modules that are imported directly.
+# * `(:MModule)-[:INTRODUCES]->(:MClass)`: all by classes introduced by this
+# module.
+# * `(:MModule)-[:DEFINES]->(:MClassDef)`: all class definitons contained in
+# this module.
 #
 # `MClass`
 #
-# * labels: `model_name`, `MEntity`, `MClass`
-# * `(:MClass)-[:CLASSTYPE]->(:MClassType)`
+# * labels: `MClass`, `model_name` and `MEntity`.
+# * `full_name`: fully qualified name.
+# * `kind`: kind of the class (`interface`, `abstract class`, etc.)
+# * `visibility`: visibility of the class.
+# * `parameter_names`: JSON array listing the name of each formal generic
+# parameter (in order of declaration).
+# * `(:MClass)-[:CLASSTYPE]->(:MClassType)`: SEE: `MClass.mclass_type`
+#
+# Arguments in the `CLASSTYPE` are named following the `parameter_names`
+# attribute of the `MClassDef` that introduces the class. A class definition
+# introduces a class if and only if it has this class as `MCLASS` and
+# has `is_intro` set to `true`.
 #
 # `MClassDef`
 #
-# * labels: `model_name`, `MEntity`, `MClassDef`
-# * `(:MClassDef)-[:BOUNDTYPE]->(:MClassType)`
-# * `(:MClassDef)-[:MCLASS]->(:MClass)`
-# * `(:MClassDef)-[:INTRODUCES]->(:MProperty)`
-# * `(:MClassDef)-[:DECLARES]->(:MPropDef)`
+# * labels: `MClassDef`, `model_name` and `MEntity`.
+# * `is_intro`: Does this definition introduce the class?
+# * `location`: origin of the definition. SEE: `Location.to_s`
+# * `(:MClassDef)-[:BOUNDTYPE]->(:MClassType)`: bounded type associated to the
+# classdef.
+# * `(:MClassDef)-[:MCLASS]->(:MClass)`: associated `MClass`.
+# * `(:MClassDef)-[:INTRODUCES]->(:MProperty)`: all properties introduced by
+# the classdef.
+# * `(:MClassDef)-[:DECLARES]->(:MPropDef)`: all property definitions in the
+# classdef (introductions and redefinitions).
+# * `(:MClassDef)-[:INHERITS]->(:MClassType)`: all declared super-types
 #
 # `MProperty`
 #
-# * labels: `model_name`, `MEntity`, `MProperty`
-# * `(:MProperty)-[:INTRO_CLASSDEF]->(:MClassDef)`
-#
-# MProperties can also have labels `MMethod`, `MAttribute`, `MVirtualTypeProp`.
+# * labels: `MProperty`, `model_name` and `MEntity`. Must also have `MMethod`,
+# `MAttribute` or `MVirtualTypeProp`, depending on the class of the represented
+# entity.
+# * `full_name`: fully qualified name.
+# * `visibility`: visibility of the property.
+# * `is_init`: Indicates if the property is a constructor. Exists only if the
+# node is a `MMethod`.
+# * `(:MProperty)-[:INTRO_CLASSDEF]->(:MClassDef)`: classdef that introduces
+# the property.
 #
 # `MPropDef`
 #
-# * labels: `model_name`, `MEntity`, `MPropDef`
-# * `(:MPropDef)-[:DEFINES]->(:MProperty)`
+# * labels: `MPropDef`, `model_name` and `MEntity`. Must also have `MMethodDef`,
+# `MAttributeDef` or `MVirtualTypeDef`, depending on the class of the
+# represented entity.
+# * `is_intro`: Does this definition introduce the property?
+# * `location`: origin of the definition. SEE: `Location.to_s`.
+# * `(:MPropDef)-[:DEFINES]->(:MProperty)`: associated property.
 #
-# MPropdefs can also have labels `MMethodDef`, `MAttributeDef`, `MVirtualTypeDef`.
+# Additional attributes and relationship for `MMethodDef`:
 #
-# `MMethodDef` are linked to a `MSignature`:
+# * `is_abstract`: Is the method definition abstract?
+# * `is_intern`: Is the method definition intern?
+# * `is_extern`: Is the method definition extern?
+# * `(:MMethodDef)-[:SIGNATURE]->(:MSignature)`: signature attached to the
+# property definition.
 #
-# * `(:MMethodDef)-[:SIGNATURE]->(:MSignature)`
+# Additional relationship for `MAttributeDef`:
 #
-# `MVirtualTypeDef` are linked to a `MType` (its `bound`):
+# * `(:MAttributeDef)-[:TYPE]->(:MType)`: static type of the attribute,
+# if specified.
 #
-# * `(:MVirtualTypeDef)-[:BOUND]->(:MType)`
+# Additional relationship for `MVirtualTypeDef`:
+#
+# * `(:MVirtualTypeDef)-[:BOUND]->(:MType)`: type to which the virtual type
+# is bound in this definition. Exists only if this definition bound the virtual
+# type to an effective type.
 #
 # `MType`
 #
-# * labels: `model_name`, `MEntity`, `MType`
+# * labels: `MType`, `model_name` and `MEntity`. Must also have `MClassType`,
+# `MNullableType`, `MVirtualType` or `MSignature`, depending on the class of
+# the represented entity.
+#
+# Additional label and relationships for `MClassType`:
 #
-# MTypes can also have labels `MClassType`, `MGenericType`, `MNullableType`, `MVirtualType`
-# and `MSignature`.
+# * If it is a `MGenericType`, also has the `MGenericType` label.
+# * `(:MClassType)-[:CLASS]->(:MClass)`: SEE: `MClassType.mclass`
+# * `(:MClassType)-[:ARGUMENT]->(:MType)`: type arguments.
 #
-# `MClassType` and `MGenericType` both point to a `MClass` and have type `arguments`:
+# Arguments are named following the `parameter_names` attribute of the
+# `MClass` referred by `CLASS`.
 #
-# * `(:MClassType)-[:CLASS]->(:MClass)`
-# * `(:MClassType)-[:ARGUMENT]->(:MType)`
+# Additional relationship for `MVirtualType`:
 #
-# `MVirtualType` points to its introducing `MProperty`:
+# * `(:MVirtualType)-[:PROPERTY]->(:MProperty)`: associated property that
+# determines the type (usually a `MVirtualTypeProp`).
 #
-# * `(:MVirtualType)-[:PROPERTY]->(:MVirtualTypeDef)`
+# Additional attribute and relationship for `MParameterType`:
 #
-# `MParameterType` points to its introducing `MClass`:
+# * `rank`: position of the parameter (0 for the first parameter).
+# * `(:MParameterType)-[:CLASS]->(:MClass)`: generic class where the parameter
+# belong.
 #
-# * `(:MParameterType)-[:CLASS]->(:MClass)`
+# Additional relationship for `MNullableType`:
 #
-# `MNullableType` points to its non-nullable `MType`:
+# * `(:MNullableType)-[:TYPE]->(:MType)`: base type of the nullable type.
 #
-# * `(:MNullableType)-[:TYPE]->(:MType)`
+# Additional attribute and relationships for `MSignature`:
 #
-# `MSignature` can have `parameters` and a `return_mtype`:
+# * `parameter_names`: JSON array representing the list of the parameter names.
+# * `(:MSignature)-[:PARAMETER]->(:MParameter)`: parameters.
+# * `(:MSignature)-[:RETURNTYPE]->(:MType)`: return type. Does not exist for
+# procedures.
 #
-# * `(:MSignature)-[:PARAMETER]->(:MParameter)`
-# * `(:MSignature)-[:RETURNTYPE]->(:MType)`
+# In order to maintain the correct parameters order, each `MSignature` node
+# contains an array of parameter names corresponding to the parameter order in
+# the signature.
+#
+# For example, if the source code contains:
+#
+#     fun foo(a: A, b: B, c: C)
+#
+# The `MSignature` node will contain a property
+# `parameter_names = ["a", "b", "c"]` so the MSignature can be reconstructed
+# with the parameters in the correct order.
 #
 # `MParameter`
 #
-# * labels: `model_name`, `MEntity`, `MParameter`
-# * `(:MParameter)-[:TYPE]->(:MType)`
+# * labels: `MParameter`, `model_name` and `MEntity`.
+# * `is_vararg`: Is the parameter a vararg?
+# * `rank`: position of the parameter (0 for the first parameter).
+# * `(:MParameter)-[:TYPE]->(:MType)`: static type of the parameter.
+#
+# MParameters are also ranked by their position in the corresponding signature.
+# Rank 0 for the first parameter, 1 for the next one and etc.
 module neo
 
 import model
@@ -369,7 +450,6 @@ class NeoModel
                node.labels.add "MModule"
                node["full_name"] = mmodule.full_name
                node["location"] = mmodule.location.to_s
-               var mgroup = mmodule.mgroup
                for parent in mmodule.in_importation.direct_greaters do
                        node.out_edges.add(new NeoEdge(node, "IMPORTS", to_node(parent)))
                end
@@ -410,10 +490,14 @@ class NeoModel
        private fun mclass_node(mclass: MClass): NeoNode do
                var node = make_node(mclass)
                node.labels.add "MClass"
-               node["arity"] = mclass.arity
                node["full_name"] = mclass.full_name
                node["kind"] = mclass.kind.to_s
                node["visibility"] = mclass.visibility.to_s
+               if not mclass.mparameters.is_empty then
+                       var parameter_names = new Array[String]
+                       for p in mclass.mparameters do parameter_names.add(p.name)
+                       node["parameter_names"] = new JsonArray.from(parameter_names)
+               end
                node.out_edges.add(new NeoEdge(node, "CLASSTYPE", to_node(mclass.mclass_type)))
                return node
        end
@@ -426,10 +510,15 @@ class NeoModel
                assert node.labels.has("MClass")
                var mmodule = to_mmodule(model, node.in_nodes("INTRODUCES").first)
                var name = node["name"].to_s
-               var arity = node["arity"].to_s.to_i
                var kind = to_kind(node["kind"].to_s)
                var visibility = to_visibility(node["visibility"].to_s)
-               var mclass = new MClass(mmodule, name, arity, kind, visibility)
+               var parameter_names = new Array[String]
+               if node.has_key("parameter_names") then
+                       for e in node["parameter_names"].as(JsonArray) do
+                               parameter_names.add e.to_s
+                       end
+               end
+               var mclass = new MClass(mmodule, name, parameter_names, kind, visibility)
                mentities[node] = mclass
                set_doc(node, mclass)
                return mclass
@@ -441,9 +530,6 @@ class NeoModel
                node.labels.add "MClassDef"
                node["is_intro"] = mclassdef.is_intro
                node["location"] = mclassdef.location.to_s
-               if not mclassdef.parameter_names.is_empty then
-                       node["parameter_names"] = new JsonArray.from(mclassdef.parameter_names)
-               end
                node.out_edges.add(new NeoEdge(node, "BOUNDTYPE", to_node(mclassdef.bound_mtype)))
                node.out_edges.add(new NeoEdge(node, "MCLASS", to_node(mclassdef.mclass)))
                for mproperty in mclassdef.intro_mproperties do
@@ -467,13 +553,7 @@ class NeoModel
                var mmodule = to_mmodule(model, node.in_nodes("DEFINES").first)
                var mtype = to_mtype(model, node.out_nodes("BOUNDTYPE").first).as(MClassType)
                var location = to_location(node["location"].to_s)
-               var parameter_names = new Array[String]
-               if node.has_key("parameter_names") then
-                       for e in node["parameter_names"].as(JsonArray) do
-                               parameter_names.add e.to_s
-                       end
-               end
-               var mclassdef = new MClassDef(mmodule, mtype, location, parameter_names)
+               var mclassdef = new MClassDef(mmodule, mtype, location)
                mentities[node] = mclassdef
                set_doc(node, mclassdef)
                var supertypes = new Array[MClassType]
@@ -556,6 +636,10 @@ class NeoModel
                        end
                else if mpropdef isa MAttributeDef then
                        node.labels.add "MAttributeDef"
+                       var static_mtype = mpropdef.static_mtype
+                       if static_mtype != null then
+                               node.out_edges.add(new NeoEdge(node, "TYPE", to_node(static_mtype)))
+                       end
                else if mpropdef isa MVirtualTypeDef then
                        node.labels.add "MVirtualTypeDef"
                        var bound = mpropdef.bound
@@ -586,6 +670,8 @@ class NeoModel
                else if node.labels.has("MAttributeDef") then
                        mpropdef = new MAttributeDef(mclassdef, mproperty.as(MAttribute), location)
                        mentities[node] = mpropdef
+                       var static_mtype = node.out_nodes("TYPE")
+                       if not static_mtype.is_empty then mpropdef.static_mtype = to_mtype(model, static_mtype.first)
                else if node.labels.has("MVirtualTypeDef") then
                        mpropdef = new MVirtualTypeDef(mclassdef, mproperty.as(MVirtualTypeProp), location)
                        mentities[node] = mpropdef
@@ -625,9 +711,15 @@ class NeoModel
                        node.out_edges.add(new NeoEdge(node, "TYPE", to_node(mtype.mtype)))
                else if mtype isa MSignature then
                        node.labels.add "MSignature"
+                       var names = new JsonArray
+                       var rank = 0
                        for mparameter in mtype.mparameters do
-                               node.out_edges.add(new NeoEdge(node, "PARAMETER", to_node(mparameter)))
+                               names.add mparameter.name
+                               var pnode = to_node(mparameter)
+                               pnode["rank"] = rank
+                               node.out_edges.add(new NeoEdge(node, "PARAMETER", pnode))
                        end
+                       if not names.is_empty then node["parameter_names"] = names
                        var return_mtype = mtype.return_mtype
                        if return_mtype != null then
                                node.out_edges.add(new NeoEdge(node, "RETURNTYPE", to_node(return_mtype)))
@@ -654,23 +746,35 @@ class NeoModel
                else if node.labels.has("MParameterType") then
                        var mclass = to_mclass(model, node.out_nodes("CLASS").first)
                        var rank = node["rank"].to_s.to_i
-                       var mtype = new MParameterType(mclass, rank)
+                       var mtype = mclass.mparameters[rank]
                        mentities[node] = mtype
                        return  mtype
                else if node.labels.has("MNullableType") then
                        var intype = to_mtype(model, node.out_nodes("TYPE").first)
-                       var mtype = new MNullableType(intype)
+                       var mtype = intype.as_nullable
                        mentities[node] = mtype
                        return mtype
                else if node.labels.has("MVirtualType") then
                        var mproperty = to_mproperty(model, node.out_nodes("PROPERTY").first)
-                       var mtype = new MVirtualType(mproperty)
+                       assert mproperty isa MVirtualTypeProp
+                       var mtype = mproperty.mvirtualtype
                        mentities[node] = mtype
                        return mtype
                else if node.labels.has("MSignature") then
-                       var mparameters = new Array[MParameter]
+                       # Get all param nodes
+                       var mparam_nodes = new HashMap[String, MParameter]
                        for pnode in node.out_nodes("PARAMETER") do
-                               mparameters.add to_mparameter(model, pnode)
+                               var mparam = to_mparameter(model, pnode)
+                               mparam_nodes[mparam.name] = mparam
+                       end
+                       # Load params in the good order
+                       var mparam_names = node["parameter_names"]
+                       var mparameters = new Array[MParameter]
+                       if mparam_names isa JsonArray then
+                               for mparam_name in mparam_names do
+                                       var mparam = mparam_nodes[mparam_name.to_s]
+                                       mparameters.add mparam
+                               end
                        end
                        var return_mtype: nullable MType = null
                        var ret_nodes = node.out_nodes("RETURNTYPE")