frontent/serialization: fix warnings
[nit.git] / src / frontend / serialization_phase.nit
index 8d576ba..d15f490 100644 (file)
@@ -24,8 +24,13 @@ import modelize
 private import annotation
 
 redef class ToolContext
+
+       # Apply the annotation `serialize_as`
+       var serialization_phase_rename: Phase = new SerializationPhaseRename(self, null)
+
        # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
-       var serialization_phase_pre_model: Phase = new SerializationPhasePreModel(self, null)
+       var serialization_phase_pre_model: Phase = new SerializationPhasePreModel(self,
+               [serialization_phase_rename])
 
        # The second phase of the serialization
        var serialization_phase_post_model: Phase = new SerializationPhasePostModel(self,
@@ -56,7 +61,34 @@ redef class ADefinition
        end
 end
 
-# TODO add annotations on attributes (volatile, sensitive or do_not_serialize?)
+private class SerializationPhaseRename
+       super Phase
+
+       redef fun process_annotated_node(node, nat)
+       do
+               var text = nat.n_atid.n_id.text
+               if text != "serialize_as" then return
+
+               if not node isa AAttrPropdef then
+                       toolcontext.error(node.location,
+                               "Syntax Error: annotation `{text}` applies only to attributes.")
+                       return
+               end
+
+               # Can't use `arg_as_string` because it needs the literal phase
+               var args = nat.n_args
+               if args.length != 1 or not args.first isa AStringFormExpr then
+                       toolcontext.error(node.location,
+                               "Syntax Error: annotation `{text}` expects a single string literal as argument.")
+                       return
+               end
+
+               var t = args.first.collect_text
+               var val = t.substring(1, t.length-2)
+               node.serialize_name = val
+       end
+end
+
 private class SerializationPhasePreModel
        super Phase
 
@@ -89,7 +121,7 @@ private class SerializationPhasePreModel
                if not node isa AModuledecl then
                        var up_serialize = false
                        var up: nullable ANode = node
-                       loop
+                       while up != null do
                                up = up.parent
                                if up == null then
                                        break
@@ -116,33 +148,17 @@ private class SerializationPhasePreModel
        do
                if not nclassdef isa AStdClassdef then return
 
-               # Is there a declaration on the classdef or the module?
-               var serialize = nclassdef.is_serialize
+               var serialize_by_default = nclassdef.how_serialize
 
-               if not serialize and not nclassdef.is_noserialize then
-                       # Is the module marked serialize?
-                       serialize = nclassdef.parent.as(AModule).is_serialize
-               end
+               if serialize_by_default != null then
 
-               var per_attribute = false
-               if not serialize then
-                       # Is there an attribute marked serialize?
-                       for npropdef in nclassdef.n_propdefs do
-                               if npropdef.is_serialize then
-                                       serialize = true
-                                       per_attribute = true
-                                       break
-                               end
-                       end
-               end
-
-               if serialize then
                        # Add `super Serializable`
                        var sc = toolcontext.parse_superclass("Serializable")
                        sc.location = nclassdef.location
                        nclassdef.n_propdefs.add sc
 
                        # Add services
+                       var per_attribute = not serialize_by_default
                        generate_serialization_method(nclassdef, per_attribute)
                        generate_deserialization_init(nclassdef, per_attribute)
                end
@@ -156,7 +172,7 @@ private class SerializationPhasePreModel
                # collect all classes
                var auto_serializable_nclassdefs = new Array[AStdClassdef]
                for nclassdef in nmodule.n_classdefs do
-                       if nclassdef isa AStdClassdef and nclassdef.is_serialize then
+                       if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
                                auto_serializable_nclassdefs.add nclassdef
                        end
                end
@@ -181,8 +197,7 @@ private class SerializationPhasePreModel
                        if (per_attribute and not attribute.is_serialize) or
                                attribute.is_noserialize then continue
 
-                       var name = attribute.name
-                       code.add "      v.serialize_attribute(\"{name}\", {name})"
+                       code.add "      v.serialize_attribute(\"{attribute.serialize_name}\", {attribute.name})"
                end
 
                code.add "end"
@@ -196,11 +211,23 @@ private class SerializationPhasePreModel
        do
                var npropdefs = nclassdef.n_propdefs
 
+               # Do not insert a `from_deserializer` if it already exists
+               for npropdef in npropdefs do
+                       if npropdef isa AMethPropdef then
+                               var methid = npropdef.n_methid
+                               if methid != null and methid.collect_text == "from_deserializer" then
+                                       return
+                               end
+                       end
+               end
+
                var code = new Array[String]
-               code.add "redef init from_deserializer(v: Deserializer)"
-               code.add "do"
-               code.add "      super"
-               code.add "      v.notify_of_creation self"
+               code.add """
+redef init from_deserializer(v: Deserializer)
+do
+       super
+       v.notify_of_creation self
+"""
 
                for attribute in npropdefs do if attribute isa AAttrPropdef then
 
@@ -210,18 +237,34 @@ private class SerializationPhasePreModel
 
                        var n_type = attribute.n_type
                        var type_name
+                       var type_name_pretty
                        if n_type == null then
-                               # Use a place holder, we will replace it with the infered type after the model phases
+                               # Use a place holder, we will replace it with the inferred type after the model phases
                                type_name = toolcontext.place_holder_type_name
+                               type_name_pretty = "Unknown type"
                        else
                                type_name = n_type.type_name
+                               type_name_pretty = type_name
                        end
                        var name = attribute.name
 
-                       code.add ""
-                       code.add "\tvar {name} = v.deserialize_attribute(\"{name}\")"
-                       code.add "\tassert {name} isa {type_name} else print \"Unsupported type for `\{class_name\}::{name}`, got '\{{name}.class_name\}'; expected {type_name}\""
-                       code.add "\tself.{name} = {name}"
+                       if type_name == "nullable Object" then
+                               # Don't type check
+                               code.add """
+       var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+"""
+                       else code.add """
+       var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+       if not {{{name}}} isa {{{type_name}}} then
+               # Check if it was a subjectent error
+               v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name_pretty}}}")
+
+               # Clear subjacent error
+               if v.keep_going == false then return
+       else
+               self.{{{name}}} = {{{name}}}
+       end
+"""
                end
 
                code.add "end"
@@ -248,17 +291,19 @@ private class SerializationPhasePreModel
 
                if deserializer_npropdef == null then
                        # create the property
-                       code.add "      redef fun deserialize_class(name)"
+                       code.add "      redef fun deserialize_class_intern(name)"
                        code.add "      do"
                else
-                       toolcontext.error(deserializer_npropdef.location, "Error: you cannot define `Deserializer::deserialize_class` in a module where you use `auto_serializable`.")
+                       toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
                        return
                end
 
                for nclassdef in nclassdefs do
-                       var name = nclassdef.n_id.text
+                       var n_qid = nclassdef.n_qid
+                       if n_qid == null then continue
+                       var name = n_qid.n_id.text
                        if nclassdef.n_formaldefs.is_empty and
-                               not nclassdef.n_classkind isa AAbstractClasskind then
+                          nclassdef.n_classkind isa AConcreteClasskind then
 
                                code.add "              if name == \"{name}\" then return new {name}.from_deserializer(self)"
                        end
@@ -318,16 +363,16 @@ redef class AIsaExpr
 end
 
 redef class AAttrPropdef
-       private fun name: String
-       do
-               return n_id2.text
-       end
+       private fun name: String do return n_id2.text
+
+       # Name of this attribute in the serialized format
+       private var serialize_name: String = name is lazy
 end
 
 redef class AType
        private fun type_name: String
        do
-               var name = n_id.text
+               var name = n_qid.n_id.text
 
                if n_kwnullable != null then name = "nullable {name}"
 
@@ -344,9 +389,9 @@ redef class AModule
        private fun deserializer_nclassdef: nullable AStdClassdef
        do
                for nclassdef in n_classdefs do
-                       if nclassdef isa AStdClassdef and nclassdef.n_id.text == "Deserialization" then
-                               return nclassdef
-                       end
+                       if not nclassdef isa AStdClassdef then continue
+                       var n_qid = nclassdef.n_qid
+                       if n_qid != null and n_qid.n_id.text == "Deserializer" then return nclassdef
                end
 
                return null
@@ -354,7 +399,11 @@ redef class AModule
 
        private var inits_to_retype = new Array[AMethPropdef]
 
-       redef fun is_serialize do return n_moduledecl != null and n_moduledecl.is_serialize
+       redef fun is_serialize
+       do
+               var n_moduledecl = n_moduledecl
+               return n_moduledecl != null and n_moduledecl.is_serialize
+       end
 end
 
 redef class AStdClassdef
@@ -362,11 +411,41 @@ redef class AStdClassdef
        do
                for npropdef in n_propdefs do if npropdef isa AMethPropdef then
                        var id = npropdef.n_methid
-                       if id isa AIdMethid and id.n_id.text == "deserialize_class" then
+                       if id isa AIdMethid and id.n_id.text == "deserialize_class_intern" then
                                return npropdef
                        end
                end
 
                return null
        end
+
+       # Is this classed marked `serialize`? in part or fully?
+       #
+       # This method returns 3 possible values:
+       # * `null`, this class is not to be serialized.
+       # * `true`, the attributes of this class are to be serialized by default.
+       # * `false`, the attributes of this class are to be serialized on demand only.
+       fun how_serialize: nullable Bool
+       do
+               # Is there a declaration on the classdef or the module?
+               var serialize = is_serialize
+
+               if not serialize and not is_noserialize then
+                       # Is the module marked serialize?
+                       serialize = parent.as(AModule).is_serialize
+               end
+
+               if serialize then return true
+
+               if not serialize then
+                       # Is there an attribute marked serialize?
+                       for npropdef in n_propdefs do
+                               if npropdef.is_serialize then
+                                       return false
+                               end
+                       end
+               end
+
+               return null
+       end
 end