frontend: use a prettier "Unknown type" when the type is not in the AST
[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
26 redef class ToolContext
27
28 # Apply the annotation `serialize_as`
29 var serialization_phase_rename: Phase = new SerializationPhaseRename(self, null)
30
31 # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
32 var serialization_phase_pre_model: Phase = new SerializationPhasePreModel(self,
33 [serialization_phase_rename])
34
35 # The second phase of the serialization
36 var serialization_phase_post_model: Phase = new SerializationPhasePostModel(self,
37 [modelize_class_phase, serialization_phase_pre_model])
38
39 private fun place_holder_type_name: String do return "PlaceHolderTypeWhichShouldNotExist"
40 end
41
42 redef class ANode
43 # Is this node annotated to be made serializable?
44 private fun is_serialize: Bool do return false
45
46 # Is this node annotated to not be made serializable?
47 private fun is_noserialize: Bool do return false
48
49 private fun accept_precise_type_visitor(v: PreciseTypeVisitor) do visit_all(v)
50 end
51
52 redef class ADefinition
53
54 redef fun is_serialize do
55 return get_annotations("serialize").not_empty or
56 get_annotations("auto_serializable").not_empty
57 end
58
59 redef fun is_noserialize do
60 return get_annotations("noserialize").not_empty
61 end
62 end
63
64 private class SerializationPhaseRename
65 super Phase
66
67 redef fun process_annotated_node(node, nat)
68 do
69 var text = nat.n_atid.n_id.text
70 if text != "serialize_as" then return
71
72 if not node isa AAttrPropdef then
73 toolcontext.error(node.location,
74 "Syntax Error: annotation `{text}` applies only to attributes.")
75 return
76 end
77
78 # Can't use `arg_as_string` because it needs the literal phase
79 var args = nat.n_args
80 if args.length != 1 or not args.first isa AStringFormExpr then
81 toolcontext.error(node.location,
82 "Syntax Error: annotation `{text}` expects a single string literal as argument.")
83 return
84 end
85
86 var t = args.first.collect_text
87 var val = t.substring(1, t.length-2)
88 node.serialize_name = val
89 end
90 end
91
92 private class SerializationPhasePreModel
93 super Phase
94
95 redef fun process_annotated_node(node, nat)
96 do
97 # Skip if we are not interested
98 var text = nat.n_atid.n_id.text
99 var serialize = text == "auto_serializable" or text == "serialize"
100 var noserialize = text == "noserialize"
101 if not (serialize or noserialize) then return
102
103 # Check legality of annotation
104 if node isa AModuledecl then
105 if noserialize then toolcontext.error(node.location, "Syntax Error: superfluous use of `{text}`, by default a module is `{text}`")
106 return
107 else if not (node isa AStdClassdef or node isa AAttrPropdef) then
108 toolcontext.error(node.location,
109 "Syntax Error: only a class, a module or an attribute can be annotated with `{text}`.")
110 return
111 else if serialize and node.is_noserialize then
112 toolcontext.error(node.location,
113 "Syntax Error: an entity cannot be both `{text}` and `noserialize`.")
114 return
115 else if node.as(Prod).get_annotations(text).length > 1 then
116 toolcontext.warning(node.location, "useless-{text}",
117 "Warning: duplicated annotation `{text}`.")
118 end
119
120 # Check the `serialize` state of the parent
121 if not node isa AModuledecl then
122 var up_serialize = false
123 var up: nullable ANode = node
124 loop
125 up = up.parent
126 if up == null then
127 break
128 else if up.is_serialize then
129 up_serialize = true
130 break
131 else if up.is_noserialize then
132 break
133 end
134 end
135
136 # Check for useless double declarations
137 if serialize and up_serialize then
138 toolcontext.warning(node.location, "useless-serialize",
139 "Warning: superfluous use of `{text}`.")
140 else if noserialize and not up_serialize then
141 toolcontext.warning(node.location, "useless-noserialize",
142 "Warning: superfluous use of `{text}`.")
143 end
144 end
145 end
146
147 redef fun process_nclassdef(nclassdef)
148 do
149 if not nclassdef isa AStdClassdef then return
150
151 var serialize_by_default = nclassdef.how_serialize
152
153 if serialize_by_default != null then
154
155 # Add `super Serializable`
156 var sc = toolcontext.parse_superclass("Serializable")
157 sc.location = nclassdef.location
158 nclassdef.n_propdefs.add sc
159
160 # Add services
161 var per_attribute = not serialize_by_default
162 generate_serialization_method(nclassdef, per_attribute)
163 generate_deserialization_init(nclassdef, per_attribute)
164 end
165 end
166
167 redef fun process_nmodule(nmodule)
168 do
169 # Clear the cache of constructors to review before adding to it
170 nmodule.inits_to_retype.clear
171
172 # collect all classes
173 var auto_serializable_nclassdefs = new Array[AStdClassdef]
174 for nclassdef in nmodule.n_classdefs do
175 if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
176 auto_serializable_nclassdefs.add nclassdef
177 end
178 end
179
180 if not auto_serializable_nclassdefs.is_empty then
181 generate_deserialization_method(nmodule, auto_serializable_nclassdefs)
182 end
183 end
184
185 fun generate_serialization_method(nclassdef: AClassdef, per_attribute: Bool)
186 do
187 var npropdefs = nclassdef.n_propdefs
188
189 var code = new Array[String]
190 code.add "redef fun core_serialize_to(v)"
191 code.add "do"
192 code.add " super"
193
194 for attribute in npropdefs do if attribute isa AAttrPropdef then
195
196 # Is `attribute` to be skipped?
197 if (per_attribute and not attribute.is_serialize) or
198 attribute.is_noserialize then continue
199
200 code.add " v.serialize_attribute(\"{attribute.serialize_name}\", {attribute.name})"
201 end
202
203 code.add "end"
204
205 # Create method Node and add it to the AST
206 npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
207 end
208
209 # Add a constructor to the automated nclassdef
210 fun generate_deserialization_init(nclassdef: AClassdef, per_attribute: Bool)
211 do
212 var npropdefs = nclassdef.n_propdefs
213
214 # Do not insert a `from_deserializer` if it already exists
215 for npropdef in npropdefs do
216 if npropdef isa AMethPropdef then
217 var methid = npropdef.n_methid
218 if methid != null and methid.collect_text == "from_deserializer" then
219 return
220 end
221 end
222 end
223
224 var code = new Array[String]
225 code.add """
226 redef init from_deserializer(v: Deserializer)
227 do
228 super
229 v.notify_of_creation self
230 """
231
232 for attribute in npropdefs do if attribute isa AAttrPropdef then
233
234 # Is `attribute` to be skipped?
235 if (per_attribute and not attribute.is_serialize) or
236 attribute.is_noserialize then continue
237
238 var n_type = attribute.n_type
239 var type_name
240 var type_name_pretty
241 if n_type == null then
242 # Use a place holder, we will replace it with the inferred type after the model phases
243 type_name = toolcontext.place_holder_type_name
244 type_name_pretty = "Unknown type"
245 else
246 type_name = n_type.type_name
247 type_name_pretty = type_name
248 end
249 var name = attribute.name
250
251 if type_name == "nullable Object" then
252 # Don't type check
253 code.add """
254 var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
255 """
256 else code.add """
257 var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
258 if not {{{name}}} isa {{{type_name}}} then
259 # Check if it was a subjectent error
260 v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name_pretty}}}")
261
262 # Clear subjacent error
263 if v.keep_going == false then return
264 else
265 self.{{{name}}} = {{{name}}}
266 end
267 """
268 end
269
270 code.add "end"
271
272 var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
273 npropdefs.add npropdef
274 nclassdef.parent.as(AModule).inits_to_retype.add npropdef
275 end
276
277 # Added to the abstract serialization service
278 fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
279 do
280 var code = new Array[String]
281
282 var deserializer_nclassdef = nmodule.deserializer_nclassdef
283 var deserializer_npropdef
284 if deserializer_nclassdef == null then
285 # create the class
286 code.add "redef class Deserializer"
287 deserializer_npropdef = null
288 else
289 deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
290 end
291
292 if deserializer_npropdef == null then
293 # create the property
294 code.add " redef fun deserialize_class_intern(name)"
295 code.add " do"
296 else
297 toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
298 return
299 end
300
301 for nclassdef in nclassdefs do
302 var name = nclassdef.n_qid.n_id.text
303 if nclassdef.n_formaldefs.is_empty and
304 nclassdef.n_classkind isa AConcreteClasskind then
305
306 code.add " if name == \"{name}\" then return new {name}.from_deserializer(self)"
307 end
308 end
309
310 code.add " return super"
311 code.add " end"
312
313 if deserializer_nclassdef == null then
314 code.add "end"
315 nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
316 else
317 deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
318 end
319 end
320 end
321
322 private class SerializationPhasePostModel
323 super Phase
324
325 redef fun process_nmodule(nmodule)
326 do
327 for npropdef in nmodule.inits_to_retype do
328 var mpropdef = npropdef.mpropdef
329 if mpropdef == null then continue # skip error
330 var v = new PreciseTypeVisitor(npropdef, mpropdef.mclassdef, toolcontext)
331 npropdef.accept_precise_type_visitor v
332 end
333 end
334 end
335
336 # Visitor on generated constructors to replace the expected type of deserialized attributes
337 private class PreciseTypeVisitor
338 super Visitor
339
340 var npropdef: AMethPropdef
341 var mclassdef: MClassDef
342 var toolcontext: ToolContext
343
344 redef fun visit(n) do n.accept_precise_type_visitor(self)
345 end
346
347 redef class AIsaExpr
348 redef fun accept_precise_type_visitor(v)
349 do
350 if n_type.collect_text != v.toolcontext.place_holder_type_name then return
351
352 var attr_name = "_" + n_expr.collect_text
353 for mattrdef in v.mclassdef.mpropdefs do
354 if mattrdef isa MAttributeDef and mattrdef.name == attr_name then
355 var new_ntype = v.toolcontext.parse_something(mattrdef.static_mtype.to_s)
356 n_type.replace_with new_ntype
357 break
358 end
359 end
360 end
361 end
362
363 redef class AAttrPropdef
364 private fun name: String do return n_id2.text
365
366 # Name of this attribute in the serialized format
367 private var serialize_name: String = name is lazy
368 end
369
370 redef class AType
371 private fun type_name: String
372 do
373 var name = n_qid.n_id.text
374
375 if n_kwnullable != null then name = "nullable {name}"
376
377 var types = n_types
378 if not types.is_empty then
379 var params = new Array[String]
380 for t in types do params.add(t.type_name)
381 return "{name}[{params.join(", ")}]"
382 else return name
383 end
384 end
385
386 redef class AModule
387 private fun deserializer_nclassdef: nullable AStdClassdef
388 do
389 for nclassdef in n_classdefs do
390 if nclassdef isa AStdClassdef and nclassdef.n_qid.n_id.text == "Deserializer" then
391 return nclassdef
392 end
393 end
394
395 return null
396 end
397
398 private var inits_to_retype = new Array[AMethPropdef]
399
400 redef fun is_serialize do return n_moduledecl != null and n_moduledecl.is_serialize
401 end
402
403 redef class AStdClassdef
404 private fun deserializer_npropdef: nullable AMethPropdef
405 do
406 for npropdef in n_propdefs do if npropdef isa AMethPropdef then
407 var id = npropdef.n_methid
408 if id isa AIdMethid and id.n_id.text == "deserialize_class_intern" then
409 return npropdef
410 end
411 end
412
413 return null
414 end
415
416 # Is this classed marked `serialize`? in part or fully?
417 #
418 # This method returns 3 possible values:
419 # * `null`, this class is not to be serialized.
420 # * `true`, the attributes of this class are to be serialized by default.
421 # * `false`, the attributes of this class are to be serialized on demand only.
422 fun how_serialize: nullable Bool
423 do
424 # Is there a declaration on the classdef or the module?
425 var serialize = is_serialize
426
427 if not serialize and not is_noserialize then
428 # Is the module marked serialize?
429 serialize = parent.as(AModule).is_serialize
430 end
431
432 if serialize then return true
433
434 if not serialize then
435 # Is there an attribute marked serialize?
436 for npropdef in n_propdefs do
437 if npropdef.is_serialize then
438 return false
439 end
440 end
441 end
442
443 return null
444 end
445 end