frontend: extract code generation modules from the strictly frontend
[nit.git] / src / frontend / serialization_code_gen_phase.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Phase generating methods (code) to serialize Nit objects
16 module serialization_code_gen_phase
17
18 intrude import serialization_model_phase
19
20 redef class ToolContext
21
22 # The second phase of the serialization
23 var serialization_phase_post_model: Phase = new SerializationPhasePostModel(self,
24 [modelize_property_phase, serialization_phase_pre_model])
25 end
26
27 private class SerializationPhasePostModel
28 super Phase
29
30 # Fill the deserialization init `from_deserializer` and `Deserializer.deserialize_class_intern`
31 redef fun process_nmodule(nmodule)
32 do
33 for npropdef in nmodule.inits_to_retype do
34 var nclassdef = npropdef.parent
35 assert nclassdef isa AStdClassdef
36
37 var serialize_by_default = nclassdef.how_serialize
38 assert serialize_by_default != null
39
40 var per_attribute = not serialize_by_default
41 fill_deserialization_init(nclassdef, npropdef, per_attribute)
42 end
43
44 # collect all classes
45 var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
46 if not auto_serializable_nclassdefs.is_empty then
47 fill_deserialization_method(nmodule, auto_serializable_nclassdefs)
48 end
49 end
50
51 # Fill the constructor to the generated `init_npropdef` of `nclassdef`
52 fun fill_deserialization_init(nclassdef: AClassdef, init_npropdef: AMethPropdef, per_attribute: Bool)
53 do
54 var code = new Array[String]
55 code.add """
56 redef init from_deserializer(v: Deserializer)
57 do
58 super
59 v.notify_of_creation self
60 """
61
62 for attribute in nclassdef.n_propdefs do
63 if not attribute isa AAttrPropdef then continue
64
65 # Is `attribute` to be skipped?
66 if (per_attribute and not attribute.is_serialize) or
67 attribute.is_noserialize then continue
68
69 var mtype = attribute.mtype
70 if mtype == null then continue
71 var type_name = mtype.to_s
72 var name = attribute.name
73
74 var resolved_type_name = type_name
75 var mclassdef = nclassdef.mclassdef
76 if mclassdef != null then
77 var bound_mtype = mclassdef.bound_mtype
78 var resolved_mtype = mtype.resolve_for(bound_mtype, bound_mtype, mclassdef.mmodule, true)
79 resolved_type_name = resolved_mtype.name
80
81 # TODO Use something like `V.class_name` to get the precise runtime type of virtual types.
82 # We currently use the upper bound of virtual types as static type in generated code
83 # for type suggestion and to prevent loading unexected types.
84 # This leaves a security issue when, for example, `DefaultMap::default_value`
85 # is bound to `nullable Object` and would allow any object to be loaded.
86 end
87
88 if type_name == "nullable Object" then
89 # Don't type check
90 code.add """
91 self.{{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
92 """
93 else
94 code.add """
95 var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
96 if v.deserialize_attribute_missing then
97 """
98 # What to do when an attribute is missing?
99 if attribute.has_value then
100 # Leave it to the default value
101 else if mtype isa MNullableType then
102 code.add """
103 self.{{{name}}} = null"""
104 else code.add """
105 v.errors.add new Error("Deserialization Error: attribute `{class_name}::{{{name}}}` missing from JSON object")"""
106
107 code.add """
108 else if not {{{name}}} isa {{{type_name}}} then
109 v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{resolved_type_name}}}")
110 if v.keep_going == false then return
111 else
112 self.{{{name}}} = {{{name}}}
113 end
114 """
115 end
116 end
117
118 code.add "end"
119
120 # Replace the body of the constructor
121 var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
122 init_npropdef.n_block = npropdef.n_block
123
124 # Run the literal phase on the generated code
125 var v = new LiteralVisitor(toolcontext)
126 v.enter_visit(npropdef.n_block)
127 end
128
129 # Fill the abstract serialization service
130 fun fill_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
131 do
132 var deserializer_nclassdef = nmodule.deserializer_nclassdef
133 if deserializer_nclassdef == null then return
134 var deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
135 if deserializer_npropdef == null then return
136
137 # Collect local types expected to be deserialized
138 var types_to_deserialize = new Set[String]
139
140 ## Local serializable standard class without parameters
141 for nclassdef in nclassdefs do
142 var mclass = nclassdef.mclass
143 if mclass == null then continue
144
145 if mclass.arity == 0 and mclass.kind == concrete_kind then
146 types_to_deserialize.add mclass.name
147 end
148 end
149
150 ## Static parametized types on serializable attributes
151 for nclassdef in nmodule.n_classdefs do
152 if not nclassdef isa AStdClassdef then continue
153
154 for attribute in nclassdef.n_propdefs do
155 if not attribute isa AAttrPropdef then continue
156
157 var serialize_by_default = nclassdef.how_serialize
158 if serialize_by_default == null then continue
159 var per_attribute = not serialize_by_default
160
161 # Is `attribute` to be skipped?
162 if (per_attribute and not attribute.is_serialize) or
163 attribute.is_noserialize then continue
164
165 var mtype = attribute.mtype
166 if mtype == null then continue
167 if mtype isa MNullableType then mtype = mtype.mtype
168
169 if mtype isa MClassType and mtype.mclass.arity > 0 and
170 mtype.mclass.kind == concrete_kind and not mtype.need_anchor then
171
172 # Check is a `Serializable`
173 var mmodule = nmodule.mmodule
174 if mmodule == null then continue
175
176 var greaters = mtype.mclass.in_hierarchy(mmodule).greaters
177 var is_serializable = false
178 for sup in greaters do if sup.name == "Serializable" then
179 is_serializable = true
180 break
181 end
182
183 if is_serializable then types_to_deserialize.add mtype.to_s
184 end
185 end
186 end
187
188 # Build implementation code
189 var code = new Array[String]
190 code.add "redef fun deserialize_class_intern(name)"
191 code.add "do"
192
193 for name in types_to_deserialize do
194 code.add " if name == \"{name}\" then return new {name}.from_deserializer(self)"
195 end
196
197 code.add " return super"
198 code.add "end"
199
200 # Replace the body of the constructor
201 var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
202 deserializer_npropdef.n_block = npropdef.n_block
203
204 # Run the literal phase on the generated code
205 var v = new LiteralVisitor(toolcontext)
206 v.enter_visit(npropdef.n_block)
207 end
208 end