1091c474aa69b7f0bfc97897d03250d1a351091e
[nit.git] / src / frontend / serialization_phase.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
4 # Copyright 2013 Guillaume Auger <jeho@resist.ca>
5 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18
19 # Phase generating methods to serialize Nit objects to different formats
20 module serialization_phase
21
22 private import parser_util
23 import modelize
24 private import annotation
25 intrude import literal
26
27 redef class ToolContext
28
29 # Apply the annotation `serialize_as`
30 var serialization_phase_rename: Phase = new SerializationPhaseRename(self, null)
31
32 # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
33 var serialization_phase_pre_model: Phase = new SerializationPhasePreModel(self,
34 [serialization_phase_rename])
35
36 # The second phase of the serialization
37 var serialization_phase_post_model: Phase = new SerializationPhasePostModel(self,
38 [modelize_property_phase, serialization_phase_pre_model])
39 end
40
41 redef class ANode
42 # Is this node annotated to be made serializable?
43 private fun is_serialize: Bool do return false
44
45 # Is this node annotated to not be made serializable?
46 private fun is_noserialize: Bool do return false
47 end
48
49 redef class ADefinition
50
51 redef fun is_serialize do
52 return get_annotations("serialize").not_empty or
53 get_annotations("auto_serializable").not_empty
54 end
55
56 redef fun is_noserialize do
57 return get_annotations("noserialize").not_empty
58 end
59 end
60
61 private class SerializationPhaseRename
62 super Phase
63
64 redef fun process_annotated_node(node, nat)
65 do
66 var text = nat.n_atid.n_id.text
67 if text != "serialize_as" then return
68
69 if not node isa AAttrPropdef then
70 toolcontext.error(node.location,
71 "Syntax Error: annotation `{text}` applies only to attributes.")
72 return
73 end
74
75 # Can't use `arg_as_string` because it needs the literal phase
76 var args = nat.n_args
77 if args.length != 1 or not args.first isa AStringFormExpr then
78 toolcontext.error(node.location,
79 "Syntax Error: annotation `{text}` expects a single string literal as argument.")
80 return
81 end
82
83 var t = args.first.collect_text
84 var val = t.substring(1, t.length-2)
85 node.serialize_name = val
86 end
87 end
88
89 private class SerializationPhasePreModel
90 super Phase
91
92 redef fun process_annotated_node(node, nat)
93 do
94 # Skip if we are not interested
95 var text = nat.n_atid.n_id.text
96 var serialize = text == "auto_serializable" or text == "serialize"
97 var noserialize = text == "noserialize"
98 if not (serialize or noserialize) then return
99
100 # Check legality of annotation
101 if node isa AModuledecl then
102 if noserialize then toolcontext.error(node.location, "Syntax Error: superfluous use of `{text}`, by default a module is `{text}`")
103 return
104 else if not (node isa AStdClassdef or node isa AAttrPropdef) then
105 toolcontext.error(node.location,
106 "Syntax Error: only a class, a module or an attribute can be annotated with `{text}`.")
107 return
108 else if serialize and node.is_noserialize then
109 toolcontext.error(node.location,
110 "Syntax Error: an entity cannot be both `{text}` and `noserialize`.")
111 return
112 else if node.as(Prod).get_annotations(text).length > 1 then
113 toolcontext.warning(node.location, "useless-{text}",
114 "Warning: duplicated annotation `{text}`.")
115 end
116
117 # Check the `serialize` state of the parent
118 if not node isa AModuledecl then
119 var up_serialize = false
120 var up: nullable ANode = node
121 while up != null do
122 up = up.parent
123 if up == null then
124 break
125 else if up.is_serialize then
126 up_serialize = true
127 break
128 else if up.is_noserialize then
129 break
130 end
131 end
132
133 # Check for useless double declarations
134 if serialize and up_serialize then
135 toolcontext.warning(node.location, "useless-serialize",
136 "Warning: superfluous use of `{text}`.")
137 else if noserialize and not up_serialize then
138 toolcontext.warning(node.location, "useless-noserialize",
139 "Warning: superfluous use of `{text}`.")
140 end
141 end
142 end
143
144 redef fun process_nclassdef(nclassdef)
145 do
146 if not nclassdef isa AStdClassdef then return
147
148 var serialize_by_default = nclassdef.how_serialize
149
150 if serialize_by_default != null then
151
152 # Add `super Serializable`
153 var sc = toolcontext.parse_superclass("Serializable")
154 sc.location = nclassdef.location
155 nclassdef.n_propdefs.add sc
156
157 # Add services
158 var per_attribute = not serialize_by_default
159 generate_serialization_method(nclassdef, per_attribute)
160 generate_deserialization_init(nclassdef)
161 end
162 end
163
164 redef fun process_nmodule(nmodule)
165 do
166 # Clear the cache of constructors to review before adding to it
167 nmodule.inits_to_retype.clear
168
169 # collect all classes
170 var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
171 if not auto_serializable_nclassdefs.is_empty then
172 generate_deserialization_method(nmodule, auto_serializable_nclassdefs)
173 end
174 end
175
176 # Implement `core_serialize_to` on `nclassdef`
177 #
178 # Are attributes serialized on demand `per_attribute` with `serialize`?
179 # Otherwise they are serialized by default, and we check instead for `noserialize`.
180 fun generate_serialization_method(nclassdef: AClassdef, per_attribute: Bool)
181 do
182 var npropdefs = nclassdef.n_propdefs
183
184 # Do not insert a `core_serialize_to` if it already exists
185 for npropdef in npropdefs do
186 if npropdef isa AMethPropdef then
187 var methid = npropdef.n_methid
188 if methid != null and methid.collect_text == "core_serialize_to" then
189 return
190 end
191 end
192 end
193
194 var code = new Array[String]
195 code.add "redef fun core_serialize_to(v)"
196 code.add "do"
197 code.add " super"
198
199 for attribute in npropdefs do if attribute isa AAttrPropdef then
200
201 # Is `attribute` to be skipped?
202 if (per_attribute and not attribute.is_serialize) or
203 attribute.is_noserialize then continue
204
205 code.add " v.serialize_attribute(\"{attribute.serialize_name}\", {attribute.name})"
206 end
207
208 code.add "end"
209
210 # Create method Node and add it to the AST
211 npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
212 end
213
214 # Add an empty constructor to the automated nclassdef
215 #
216 # Will be filled by `SerializationPhasePostModel`.
217 fun generate_deserialization_init(nclassdef: AClassdef)
218 do
219 var npropdefs = nclassdef.n_propdefs
220
221 # Do not insert a `from_deserializer` if it already exists
222 for npropdef in npropdefs do
223 if npropdef isa AMethPropdef then
224 var methid = npropdef.n_methid
225 if methid != null and methid.collect_text == "from_deserializer" then
226 return
227 end
228 end
229 end
230
231 var code = """
232 redef init from_deserializer(v: Deserializer) do abort"""
233
234 var npropdef = toolcontext.parse_propdef(code).as(AMethPropdef)
235 npropdefs.add npropdef
236 nclassdef.parent.as(AModule).inits_to_retype.add npropdef
237 end
238
239 # Add an empty `Deserializer::deserialize_class_intern`
240 #
241 # Will be filled by `SerializationPhasePostModel`.
242 fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
243 do
244 var code = new Array[String]
245
246 var deserializer_nclassdef = nmodule.deserializer_nclassdef
247 var deserializer_npropdef
248 if deserializer_nclassdef == null then
249 # create the class
250 code.add "redef class Deserializer"
251 deserializer_npropdef = null
252 else
253 deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
254 end
255
256 if deserializer_npropdef == null then
257 # create the property
258 code.add " redef fun deserialize_class_intern(name) do abort"
259 else
260 toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
261 return
262 end
263
264 if deserializer_nclassdef == null then
265 code.add "end"
266 nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
267 else
268 deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
269 end
270 end
271 end
272
273 private class SerializationPhasePostModel
274 super Phase
275
276 # Fill the deserialization init `from_deserializer` and `Deserializer.deserialize_class_intern`
277 redef fun process_nmodule(nmodule)
278 do
279 for npropdef in nmodule.inits_to_retype do
280 var nclassdef = npropdef.parent
281 assert nclassdef isa AStdClassdef
282
283 var serialize_by_default = nclassdef.how_serialize
284 assert serialize_by_default != null
285
286 var per_attribute = not serialize_by_default
287 fill_deserialization_init(nclassdef, npropdef, per_attribute)
288 end
289
290 # collect all classes
291 var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
292 if not auto_serializable_nclassdefs.is_empty then
293 fill_deserialization_method(nmodule, auto_serializable_nclassdefs)
294 end
295 end
296
297 # Fill the constructor to the generated `init_npropdef` of `nclassdef`
298 fun fill_deserialization_init(nclassdef: AClassdef, init_npropdef: AMethPropdef, per_attribute: Bool)
299 do
300 var code = new Array[String]
301 code.add """
302 redef init from_deserializer(v: Deserializer)
303 do
304 super
305 v.notify_of_creation self
306 """
307
308 for attribute in nclassdef.n_propdefs do
309 if not attribute isa AAttrPropdef then continue
310
311 # Is `attribute` to be skipped?
312 if (per_attribute and not attribute.is_serialize) or
313 attribute.is_noserialize then continue
314
315 var mtype = attribute.mtype
316 if mtype == null then continue
317 var type_name = mtype.to_s
318 var name = attribute.name
319
320 var resolved_type_name = type_name
321 var mclassdef = nclassdef.mclassdef
322 if mclassdef != null then
323 var bound_mtype = mclassdef.bound_mtype
324 var resolved_mtype = mtype.resolve_for(bound_mtype, bound_mtype, mclassdef.mmodule, true)
325 resolved_type_name = resolved_mtype.name
326
327 # TODO Use something like `V.class_name` to get the precise runtime type of virtual types.
328 # We currently use the upper bound of virtual types as static type in generated code
329 # for type suggestion and to prevent loading unexected types.
330 # This leaves a security issue when, for example, `DefaultMap::default_value`
331 # is bound to `nullable Object` and would allow any object to be loaded.
332 end
333
334 if type_name == "nullable Object" then
335 # Don't type check
336 code.add """
337 self.{{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
338 """
339 else
340 code.add """
341 var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
342 if v.deserialize_attribute_missing then
343 """
344 # What to do when an attribute is missing?
345 if attribute.has_value then
346 # Leave it to the default value
347 else if mtype isa MNullableType then
348 code.add """
349 self.{{{name}}} = null"""
350 else code.add """
351 v.errors.add new Error("Deserialization Error: attribute `{class_name}::{{{name}}}` missing from JSON object")"""
352
353 code.add """
354 else if not {{{name}}} isa {{{type_name}}} then
355 v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{resolved_type_name}}}")
356 if v.keep_going == false then return
357 else
358 self.{{{name}}} = {{{name}}}
359 end
360 """
361 end
362 end
363
364 code.add "end"
365
366 # Replace the body of the constructor
367 var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
368 init_npropdef.n_block = npropdef.n_block
369
370 # Run the literal phase on the generated code
371 var v = new LiteralVisitor(toolcontext)
372 v.enter_visit(npropdef.n_block)
373 end
374
375 # Fill the abstract serialization service
376 fun fill_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
377 do
378 var deserializer_nclassdef = nmodule.deserializer_nclassdef
379 if deserializer_nclassdef == null then return
380 var deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
381 if deserializer_npropdef == null then return
382
383 # Collect local types expected to be deserialized
384 var types_to_deserialize = new Set[String]
385
386 ## Local serializable standard class without parameters
387 for nclassdef in nclassdefs do
388 var mclass = nclassdef.mclass
389 if mclass == null then continue
390
391 if mclass.arity == 0 and mclass.kind == concrete_kind then
392 types_to_deserialize.add mclass.name
393 end
394 end
395
396 ## Static parametized types on serializable attributes
397 for nclassdef in nmodule.n_classdefs do
398 if not nclassdef isa AStdClassdef then continue
399
400 for attribute in nclassdef.n_propdefs do
401 if not attribute isa AAttrPropdef then continue
402
403 var serialize_by_default = nclassdef.how_serialize
404 if serialize_by_default == null then continue
405 var per_attribute = not serialize_by_default
406
407 # Is `attribute` to be skipped?
408 if (per_attribute and not attribute.is_serialize) or
409 attribute.is_noserialize then continue
410
411 var mtype = attribute.mtype
412 if mtype == null then continue
413 if mtype isa MNullableType then mtype = mtype.mtype
414
415 if mtype isa MClassType and mtype.mclass.arity > 0 and
416 mtype.mclass.kind == concrete_kind and not mtype.need_anchor then
417
418 # Check is a `Serializable`
419 var mmodule = nmodule.mmodule
420 if mmodule == null then continue
421
422 var greaters = mtype.mclass.in_hierarchy(mmodule).greaters
423 var is_serializable = false
424 for sup in greaters do if sup.name == "Serializable" then
425 is_serializable = true
426 break
427 end
428
429 if is_serializable then types_to_deserialize.add mtype.to_s
430 end
431 end
432 end
433
434 # Build implementation code
435 var code = new Array[String]
436 code.add "redef fun deserialize_class_intern(name)"
437 code.add "do"
438
439 for name in types_to_deserialize do
440 code.add " if name == \"{name}\" then return new {name}.from_deserializer(self)"
441 end
442
443 code.add " return super"
444 code.add "end"
445
446 # Replace the body of the constructor
447 var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
448 deserializer_npropdef.n_block = npropdef.n_block
449
450 # Run the literal phase on the generated code
451 var v = new LiteralVisitor(toolcontext)
452 v.enter_visit(npropdef.n_block)
453 end
454 end
455
456 redef class AAttrPropdef
457 private fun name: String do return n_id2.text
458
459 # Name of this attribute in the serialized format
460 private var serialize_name: String = name is lazy
461 end
462
463 redef class AType
464 private fun type_name: String
465 do
466 var name = n_qid.n_id.text
467
468 if n_kwnullable != null then name = "nullable {name}"
469
470 var types = n_types
471 if not types.is_empty then
472 var params = new Array[String]
473 for t in types do params.add(t.type_name)
474 return "{name}[{params.join(", ")}]"
475 else return name
476 end
477 end
478
479 redef class AModule
480 private fun deserializer_nclassdef: nullable AStdClassdef
481 do
482 for nclassdef in n_classdefs do
483 if not nclassdef isa AStdClassdef then continue
484 var n_qid = nclassdef.n_qid
485 if n_qid != null and n_qid.n_id.text == "Deserializer" then return nclassdef
486 end
487
488 return null
489 end
490
491 private var inits_to_retype = new Array[AMethPropdef]
492
493 redef fun is_serialize
494 do
495 var n_moduledecl = n_moduledecl
496 return n_moduledecl != null and n_moduledecl.is_serialize
497 end
498
499 # `AStdClassdef` marked as serializable, itself or one of theur attribute
500 private var auto_serializable_nclassdefs: Array[AStdClassdef] is lazy do
501 var array = new Array[AStdClassdef]
502 for nclassdef in n_classdefs do
503 if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
504 array.add nclassdef
505 end
506 end
507 return array
508 end
509 end
510
511 redef class AStdClassdef
512 private fun deserializer_npropdef: nullable AMethPropdef
513 do
514 for npropdef in n_propdefs do if npropdef isa AMethPropdef then
515 var id = npropdef.n_methid
516 if id isa AIdMethid and id.n_id.text == "deserialize_class_intern" then
517 return npropdef
518 end
519 end
520
521 return null
522 end
523
524 # Is this classed marked `serialize`? in part or fully?
525 #
526 # This method returns 3 possible values:
527 # * `null`, this class is not to be serialized.
528 # * `true`, the attributes of this class are to be serialized by default.
529 # * `false`, the attributes of this class are to be serialized on demand only.
530 fun how_serialize: nullable Bool
531 do
532 # Is there a declaration on the classdef or the module?
533 var serialize = is_serialize
534
535 if not serialize and not is_noserialize then
536 # Is the module marked serialize?
537 serialize = parent.as(AModule).is_serialize
538 end
539
540 if serialize then return true
541
542 if not serialize then
543 # Is there an attribute marked serialize?
544 for npropdef in n_propdefs do
545 if npropdef.is_serialize then
546 return false
547 end
548 end
549 end
550
551 return null
552 end
553 end