src: introduce new constructors
[nit.git] / src / modelize_property.nit
index 04e022f..db51cbd 100644 (file)
@@ -68,6 +68,11 @@ redef class ModelBuilder
                process_default_constructors(nclassdef)
        end
 
+       # the root init of the Object class
+       # Is usually implicitly defined
+       # Then explicit or implicit definitions of root-init are attached to it
+       var the_root_init_mmethod: nullable MMethod
+
        # Introduce or inherit default constructor
        # This is the last part of `build_properties`.
        private fun process_default_constructors(nclassdef: AClassdef)
@@ -77,27 +82,54 @@ redef class ModelBuilder
                # Are we a refinement
                if not mclassdef.is_intro then return
 
+               var mmodule = nclassdef.mclassdef.mmodule
+
+               # Look for the init in Object, or create it
+               if mclassdef.mclass.name == "Object" and the_root_init_mmethod == null then
+                       # Create the implicit root-init method
+                       var mprop = new MMethod(mclassdef, "init", mclassdef.mclass.visibility)
+                       mprop.is_root_init = true
+                       var mpropdef = new MMethodDef(mclassdef, mprop, nclassdef.location)
+                       var mparameters = new Array[MParameter]
+                       var msignature = new MSignature(mparameters, null)
+                       mpropdef.msignature = msignature
+                       mpropdef.new_msignature = msignature
+                       mprop.is_init = true
+                       nclassdef.mfree_init = mpropdef
+                       self.toolcontext.info("{mclassdef} gets a free empty constructor {mpropdef}{msignature}", 3)
+                       the_root_init_mmethod = mprop
+                       return
+               end
+
                # Is the class forbid constructors?
                if not mclassdef.mclass.kind.need_init then return
 
                # Is there already a constructor defined?
+               var defined_init: nullable MMethodDef = null
                for mpropdef in mclassdef.mpropdefs do
                        if not mpropdef isa MMethodDef then continue
-                       if mpropdef.mproperty.is_init then return
+                       if not mpropdef.mproperty.is_init then continue
+                       if mpropdef.mproperty.is_root_init then
+                               assert defined_init == null
+                               defined_init = mpropdef
+                       else
+                               # An explicit old-style init, so return
+                               return
+                       end
                end
 
                if not nclassdef isa AStdClassdef then return
 
-               var mmodule = mclassdef.mmodule
-               # Do we inherit for a constructor?
-               var combine = new Array[MMethod]
-               var inhc: nullable MClass = null
-               for st in mclassdef.supertypes do
+               # Do we inherit a old-style constructor?
+               var combine = new Array[MMethod] # old-style constructors without arguments
+               var inhc: nullable MClass = null # single super-class with a constructor with arguments
+               if defined_init == null then for st in mclassdef.supertypes do
                        var c = st.mclass
                        if not c.kind.need_init then continue
                        st = st.anchor_to(mmodule, mclassdef.bound_mtype)
                        var candidate = self.try_get_mproperty_by_name2(nclassdef, mmodule, st, "init").as(nullable MMethod)
                        if candidate != null then
+                               if candidate.is_root_init then continue
                                if candidate.intro.msignature != null then
                                        if candidate.intro.msignature.arity == 0 then
                                                combine.add(candidate)
@@ -117,6 +149,7 @@ redef class ModelBuilder
 
                # Collect undefined attributes
                var mparameters = new Array[MParameter]
+               var initializers = new Array[MProperty]
                var anode: nullable ANode = null
                for npropdef in nclassdef.n_propdefs do
                        if npropdef isa AAttrPropdef then
@@ -135,6 +168,14 @@ redef class ModelBuilder
                                if ret_type == null then return
                                var mparameter = new MParameter(paramname, ret_type, false)
                                mparameters.add(mparameter)
+                               var msetter = npropdef.mwritepropdef
+                               if msetter == null then
+                                       # No setter, it is a old-style attribute, so just add it
+                                       initializers.add(npropdef.mpropdef.mproperty)
+                               else
+                                       # Add the setter to the list
+                                       initializers.add(msetter.mproperty)
+                               end
                                if anode == null then anode = npropdef
                        end
                end
@@ -148,15 +189,14 @@ redef class ModelBuilder
 
                        # TODO: actively inherit the consturctor
                        self.toolcontext.info("{mclassdef} inherits all constructors from {inhc}", 3)
-                       mclassdef.mclass.inherit_init_from = inhc
-                       return
+                       #mclassdef.mclass.inherit_init_from = inhc
+                       #return
                end
 
                if not combine.is_empty and inhc != null then
                        self.error(nclassdef, "Error: Cannot provide a defaut constructor: conflict for {combine.join(", ")} and {inhc}")
                        return
                end
-
                if not combine.is_empty then
                        if mparameters.is_empty and combine.length == 1 then
                                # No need to create a local init, the inherited one is enough
@@ -166,13 +206,75 @@ redef class ModelBuilder
                                return
                        end
                        nclassdef.super_inits = combine
+                       var mprop = new MMethod(mclassdef, "init", mclassdef.mclass.visibility)
+                       var mpropdef = new MMethodDef(mclassdef, mprop, nclassdef.location)
+                       var msignature = new MSignature(mparameters, null)
+                       mpropdef.msignature = msignature
+                       mprop.is_init = true
+                       nclassdef.mfree_init = mpropdef
+                       self.toolcontext.info("{mclassdef} gets a free empty constructor {mpropdef}{msignature}", 3)
+                       return
+               end
+
+               if the_root_init_mmethod == null then return
+
+               # Look for nost-specific new-stype init definitions
+               var spropdefs = the_root_init_mmethod.lookup_super_definitions(mclassdef.mmodule, mclassdef.bound_mtype)
+               if spropdefs.is_empty then
+                       toolcontext.fatal_error(nclassdef.location, "Fatal error: {mclassdef} does not specialize {the_root_init_mmethod.intro_mclassdef}. Possible duplication of the root class `Object`?")
+               end
+
+               # Search the longest-one and checks for conflict
+               var longest = spropdefs.first
+               if spropdefs.length > 1 then
+                       # Check for conflict in the order of initializers
+                       # Each initializer list must me a prefix of the longest list
+                       # part 1. find the longest list
+                       for spd in spropdefs do
+                               if spd.initializers.length > longest.initializers.length then longest = spd
+                       end
+                       # part 2. compare
+                       for spd in spropdefs do
+                               var i = 0
+                               for p in spd.initializers do
+                                       if p != longest.initializers[i] then
+                                               self.error(nclassdef, "Error: conflict for inherited inits {spd}({spd.initializers.join(", ")}) and {longest}({longest.initializers.join(", ")})")
+                                               return
+                                       end
+                                       i += 1
+                               end
+                       end
+               end
+
+               # Can we just inherit?
+               if spropdefs.length == 1 and mparameters.is_empty and defined_init == null then
+                       self.toolcontext.info("{mclassdef} inherits the basic constructor {longest}", 3)
+                       return
+               end
+
+               # Combine the inherited list to what is collected
+               if longest.initializers.length > 0 then
+                       mparameters.prepend longest.new_msignature.mparameters
+                       initializers.prepend longest.initializers
+               end
+
+               # If we already have a basic init definition, then setup its initializers
+               if defined_init != null then
+                       defined_init.initializers.add_all(initializers)
+                       var msignature = new MSignature(mparameters, null)
+                       defined_init.new_msignature = msignature
+                       self.toolcontext.info("{mclassdef} extends its basic constructor signature to {defined_init}{msignature}", 3)
+                       return
                end
 
-               var mprop = new MMethod(mclassdef, "init", mclassdef.mclass.visibility)
+               # Else create the local implicit basic init definition
+               var mprop = the_root_init_mmethod.as(not null)
                var mpropdef = new MMethodDef(mclassdef, mprop, nclassdef.location)
+               mpropdef.has_supercall = true
+               mpropdef.initializers.add_all(initializers)
                var msignature = new MSignature(mparameters, null)
-               mpropdef.msignature = msignature
-               mprop.is_init = true
+               mpropdef.new_msignature = msignature
+               mpropdef.msignature = new MSignature(new Array[MParameter], null) # always an empty real signature
                nclassdef.mfree_init = mpropdef
                self.toolcontext.info("{mclassdef} gets a free constructor for attributes {mpropdef}{msignature}", 3)
        end
@@ -187,7 +289,7 @@ redef class ModelBuilder
                # It is a case-by case
                var vis_type: nullable MVisibility = null # The own visibility of the type
                var mmodule_type: nullable MModule = null # The origial module of the type
-               if mtype isa MNullableType then mtype = mtype.mtype
+               mtype = mtype.as_notnullable
                if mtype isa MClassType then
                        vis_type = mtype.mclass.visibility
                        mmodule_type = mtype.mclass.intro.mmodule
@@ -645,10 +747,20 @@ redef class AAttrPropdef
        # Is the node tagged `noinit`?
        var noinit = false
 
+       # Is the node taggeg lazy?
+       var is_lazy = false
+
+       # The guard associated to a lasy attribute.
+       # Because some engines does not have a working `isset`,
+       # this additionnal attribute is used to guard the lazy initialization.
+       # TODO: to remove once isset is correctly implemented
+       var mlazypropdef: nullable MAttributeDef
+
        # The associated getter (read accessor) if any
        var mreadpropdef: nullable MMethodDef writable
        # The associated setter (write accessor) if any
        var mwritepropdef: nullable MMethodDef writable
+
        redef fun build_property(modelbuilder, mclassdef)
        do
                var mclass = mclassdef.mclass
@@ -718,15 +830,44 @@ redef class AAttrPropdef
                        modelbuilder.mpropdef2npropdef[mreadpropdef] = self
                        mreadpropdef.mdoc = mpropdef.mdoc
 
+                       var atlazy = self.get_single_annotation("lazy", modelbuilder)
+                       if atlazy != null then
+                               if n_expr == null then
+                                       modelbuilder.error(atlazy, "Error: a lazy attribute needs a value")
+                               end
+                               is_lazy = true
+                               var mlazyprop = new MAttribute(mclassdef, "lazy _" + name, none_visibility)
+                               var mlazypropdef = new MAttributeDef(mclassdef, mlazyprop, self.location)
+                               self.mlazypropdef = mlazypropdef
+                       end
+
+                       var atreadonly = self.get_single_annotation("readonly", modelbuilder)
+                       if atreadonly != null then
+                               if n_expr == null then
+                                       modelbuilder.error(atreadonly, "Error: a readonly attribute needs a value")
+                               end
+                               # No setter, so just leave
+                               return
+                       end
+
                        var writename = name + "="
                        var nwritable = self.n_writable
+                       var atwritable = self.get_single_annotation("writable", modelbuilder)
+                       if atwritable != null then
+                               if not atwritable.n_args.is_empty then
+                                       writename = atwritable.arg_as_id(modelbuilder) or else writename
+                               end
+                       end
                        var mwriteprop = modelbuilder.try_get_mproperty_by_name(nid2, mclassdef, writename).as(nullable MMethod)
                        var nwkwredef: nullable Token = null
                        if nwritable != null then nwkwredef = nwritable.n_kwredef
+                       if atwritable != null then nwkwredef = atwritable.n_kwredef
                        if mwriteprop == null then
                                var mvisibility
                                if nwritable != null then
                                        mvisibility = new_property_visibility(modelbuilder, mclassdef, nwritable.n_visibility)
+                               else if atwritable != null then
+                                       mvisibility = new_property_visibility(modelbuilder, mclassdef, atwritable.n_visibility)
                                else
                                        mvisibility = private_visibility
                                end
@@ -736,6 +877,8 @@ redef class AAttrPropdef
                                if not self.check_redef_keyword(modelbuilder, mclassdef, nwkwredef or else n_kwredef, true, mwriteprop) then return
                                if nwritable != null then
                                        check_redef_property_visibility(modelbuilder, nwritable.n_visibility, mwriteprop)
+                               else if atwritable != null then
+                                       check_redef_property_visibility(modelbuilder, atwritable.n_visibility, mwriteprop)
                                end
                        end
                        mclassdef.mprop2npropdef[mwriteprop] = self
@@ -820,7 +963,7 @@ redef class AAttrPropdef
                        mreadpropdef.msignature = msignature
                end
 
-               var msritepropdef = self.mwritepropdef
+               var mwritepropdef = self.mwritepropdef
                if mwritepropdef != null then
                        var name: String
                        if n_id != null then
@@ -832,6 +975,11 @@ redef class AAttrPropdef
                        var msignature = new MSignature([mparameter], null)
                        mwritepropdef.msignature = msignature
                end
+
+               var mlazypropdef = self.mlazypropdef
+               if mlazypropdef != null then
+                       mlazypropdef.static_mtype = modelbuilder.model.get_mclasses_by_name("Bool").first.mclass_type
+               end
        end
 
        redef fun check_signature(modelbuilder)
@@ -955,6 +1103,11 @@ redef class ATypePropdef
                self.mpropdef = mpropdef
                modelbuilder.mpropdef2npropdef[mpropdef] = self
                set_doc(mpropdef)
+
+               var atfixed = get_single_annotation("fixed", modelbuilder)
+               if atfixed != null then
+                       mpropdef.is_fixed = true
+               end
        end
 
        redef fun build_signature(modelbuilder)
@@ -983,25 +1136,44 @@ redef class ATypePropdef
 
                modelbuilder.check_visibility(n_type, bound, mpropdef)
 
-               # Fast case: the bound is not a formal type
-               if not bound isa MVirtualType then return
-
                var mclassdef = mpropdef.mclassdef
                var mmodule = mclassdef.mmodule
                var anchor = mclassdef.bound_mtype
 
-               # Slow case: progress on each resolution until: (i) we loop, or (ii) we found a non formal type
-               var seen = [self.mpropdef.mproperty.mvirtualtype]
-               loop
-                       if seen.has(bound) then
+               # Check circularity
+               if bound isa MVirtualType then
+                       # Slow case: progress on each resolution until: (i) we loop, or (ii) we found a non formal type
+                       var seen = [self.mpropdef.mproperty.mvirtualtype]
+                       loop
+                               if seen.has(bound) then
+                                       seen.add(bound)
+                                       modelbuilder.error(self, "Error: circularity of virtual type definition: {seen.join(" -> ")}")
+                                       return
+                               end
                                seen.add(bound)
-                               modelbuilder.error(self, "Error: circularity of virtual type definition: {seen.join(" -> ")}")
-                               return
+                               var next = bound.lookup_bound(mmodule, anchor)
+                               if not next isa MVirtualType then break
+                               bound = next
+                       end
+               end
+
+               # Check redefinitions
+               bound = mpropdef.bound.as(not null)
+               for p in mpropdef.mproperty.lookup_super_definitions(mmodule, anchor) do
+                       var supbound = p.bound.as(not null)
+                       if p.is_fixed then
+                               modelbuilder.error(self, "Redef Error: Virtual type {mpropdef.mproperty} is fixed in super-class {p.mclassdef.mclass}")
+                               break
+                       end
+                       if p.mclassdef.mclass == mclassdef.mclass then
+                               # Still a warning to pass existing bad code
+                               modelbuilder.warning(n_type, "Redef Error: a virtual type cannot be refined.")
+                               break
+                       end
+                       if not bound.is_subtype(mmodule, anchor, supbound) then
+                               modelbuilder.error(n_type, "Redef Error: Wrong bound type. Found {bound}, expected a subtype of {supbound}, as in {p}.")
+                               break
                        end
-                       seen.add(bound)
-                       var next = bound.lookup_bound(mmodule, anchor)
-                       if not next isa MVirtualType then break
-                       bound = next
                end
        end
 end