modelbuilder_base: Define ANode as nullable
[nit.git] / src / modelbuilder_base.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012 Jean Privat <jean@pryen.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Load nit source files and build the associated model
18 #
19 # FIXME better doc
20 #
21 # FIXME split this module into submodules
22 # FIXME add missing error checks
23 module modelbuilder_base
24
25 import model
26 import toolcontext
27 import parser
28
29 private import more_collections
30
31 ###
32
33 redef class ToolContext
34
35 # The modelbuilder 1-to-1 associated with the toolcontext
36 fun modelbuilder: ModelBuilder do return modelbuilder_real.as(not null)
37
38 private var modelbuilder_real: nullable ModelBuilder = null
39
40 end
41
42 # A model builder knows how to load nit source files and build the associated model
43 class ModelBuilder
44 # The model where new modules, classes and properties are added
45 var model: Model
46
47 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
48 var toolcontext: ToolContext
49
50 # Instantiate a modelbuilder for a model and a toolcontext
51 # Important, the options of the toolcontext must be correctly set (parse_option already called)
52 init
53 do
54 assert toolcontext.modelbuilder_real == null
55 toolcontext.modelbuilder_real = self
56 end
57
58 # Return a class named `name` visible by the module `mmodule`.
59 # Visibility in modules is correctly handled.
60 # If no such a class exists, then null is returned.
61 # If more than one class exists, then an error on `anode` is displayed and null is returned.
62 # FIXME: add a way to handle class name conflict
63 fun try_get_mclass_by_name(anode: nullable ANode, mmodule: MModule, name: String): nullable MClass
64 do
65 var classes = model.get_mclasses_by_name(name)
66 if classes == null then
67 return null
68 end
69
70 var res: nullable MClass = null
71 for mclass in classes do
72 if not mmodule.in_importation <= mclass.intro_mmodule then continue
73 if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
74 if res == null then
75 res = mclass
76 else
77 error(anode, "Error: ambiguous class name `{name}`; conflict between `{mclass.full_name}` and `{res.full_name}`.")
78 return null
79 end
80 end
81 return res
82 end
83
84 # Return a class identified by `qid` visible by the module `mmodule`.
85 # Visibility in modules and qualified names are correctly handled.
86 #
87 # If more than one class exists, then null is silently returned.
88 # It is up to the caller to post-analysis the result and display a correct error message.
89 # The method `class_not_found` can be used to display such a message.
90 fun try_get_mclass_by_qid(qid: AQclassid, mmodule: MModule): nullable MClass
91 do
92 var name = qid.n_id.text
93
94 var classes = model.get_mclasses_by_name(name)
95 if classes == null then
96 return null
97 end
98
99 var res: nullable MClass = null
100 for mclass in classes do
101 if not mmodule.in_importation <= mclass.intro_mmodule then continue
102 if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
103 if not qid.accept(mclass) then continue
104 if res == null then
105 res = mclass
106 else
107 return null
108 end
109 end
110
111 return res
112 end
113
114 # Like `try_get_mclass_by_name` but display an error message when the class is not found
115 fun get_mclass_by_name(node: nullable ANode, mmodule: MModule, name: String): nullable MClass
116 do
117 var mclass = try_get_mclass_by_name(node, mmodule, name)
118 if mclass == null then
119 error(node, "Type Error: missing primitive class `{name}'.")
120 end
121 return mclass
122 end
123
124 # Return a property named `name` on the type `mtype` visible in the module `mmodule`.
125 # Visibility in modules is correctly handled.
126 # Protected properties are returned (it is up to the caller to check and reject protected properties).
127 # If no such a property exists, then null is returned.
128 # If more than one property exists, then an error on `anode` is displayed and null is returned.
129 # FIXME: add a way to handle property name conflict
130 fun try_get_mproperty_by_name2(anode: nullable ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty
131 do
132 var props = self.model.get_mproperties_by_name(name)
133 if props == null then
134 return null
135 end
136
137 var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
138 if cache != null then return cache
139
140 var res: nullable MProperty = null
141 var ress: nullable Array[MProperty] = null
142 for mprop in props do
143 if not mtype.has_mproperty(mmodule, mprop) then continue
144 if not mmodule.is_visible(mprop.intro_mclassdef.mmodule, mprop.visibility) then continue
145
146 # new-factories are invisible outside of the class
147 if mprop isa MMethod and mprop.is_new and (not mtype isa MClassType or mprop.intro_mclassdef.mclass != mtype.mclass) then
148 continue
149 end
150
151 if res == null then
152 res = mprop
153 continue
154 end
155
156 # Two global properties?
157 # First, special case for init, keep the most specific ones
158 if res isa MMethod and mprop isa MMethod and res.is_init and mprop.is_init then
159 var restype = res.intro_mclassdef.bound_mtype
160 var mproptype = mprop.intro_mclassdef.bound_mtype
161 if mproptype.is_subtype(mmodule, null, restype) then
162 # found a most specific constructor, so keep it
163 res = mprop
164 continue
165 end
166 end
167
168 # Ok, just keep all prop in the ress table
169 if ress == null then
170 ress = new Array[MProperty]
171 ress.add(res)
172 end
173 ress.add(mprop)
174 end
175
176 # There is conflict?
177 if ress != null and res isa MMethod and res.is_init then
178 # special case forinit again
179 var restype = res.intro_mclassdef.bound_mtype
180 var ress2 = new Array[MProperty]
181 for mprop in ress do
182 var mproptype = mprop.intro_mclassdef.bound_mtype
183 if not restype.is_subtype(mmodule, null, mproptype) then
184 ress2.add(mprop)
185 else if not mprop isa MMethod or not mprop.is_init then
186 ress2.add(mprop)
187 end
188 end
189 if ress2.is_empty then
190 ress = null
191 else
192 ress = ress2
193 ress.add(res)
194 end
195 end
196
197 if ress != null then
198 assert ress.length > 1
199 var s = new Array[String]
200 for mprop in ress do s.add mprop.full_name
201 self.error(anode, "Error: ambiguous property name `{name}` for `{mtype}`; conflict between {s.join(" and ")}.")
202 end
203
204 self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
205 return res
206 end
207
208 private var try_get_mproperty_by_name2_cache = new HashMap3[MModule, MType, String, nullable MProperty]
209
210
211 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
212 fun try_get_mproperty_by_name(anode: nullable ANode, mclassdef: MClassDef, name: String): nullable MProperty
213 do
214 return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
215 end
216
217 # Helper function to display an error on a node.
218 # Alias for `self.toolcontext.error(n.hot_location, text)`
219 #
220 # This automatically sets `n.is_broken` to true.
221 fun error(n: nullable ANode, text: String)
222 do
223 var l = null
224 if n != null then
225 l = n.hot_location
226 n.is_broken = true
227 end
228 self.toolcontext.error(l, text)
229 end
230
231 # Helper function to display a warning on a node.
232 # Alias for: `self.toolcontext.warning(n.hot_location, text)`
233 fun warning(n: nullable ANode, tag, text: String)
234 do
235 var l = null
236 if n != null then l = n.hot_location
237 self.toolcontext.warning(l, tag, text)
238 end
239
240 # Helper function to display an advice on a node.
241 # Alias for: `self.toolcontext.advice(n.hot_location, text)`
242 fun advice(n: nullable ANode, tag, text: String)
243 do
244 var l = null
245 if n != null then l = n.hot_location
246 self.toolcontext.advice(l, tag, text)
247 end
248
249 # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
250 fun force_get_primitive_method(n: nullable ANode, name: String, recv: MClass, mmodule: MModule): MMethod
251 do
252 var res = mmodule.try_get_primitive_method(name, recv)
253 if res == null then
254 var l = null
255 if n != null then l = n.hot_location
256 self.toolcontext.fatal_error(l, "Fatal Error: `{recv}` must have a property named `{name}`.")
257 abort
258 end
259 return res
260 end
261
262 # Return the static type associated to the node `ntype`.
263 #
264 # `mclassdef` is the context where the call is made (used to understand
265 # formal types).
266 # In case of problem, an error is displayed on `ntype` and null is returned.
267 #
268 # Same as `resolve_mtype_unchecked3`, but get the context (module, class and
269 # anchor) from `mclassdef`.
270 #
271 # SEE: `resolve_mtype`
272 # SEE: `resolve_mtype3_unchecked`
273 #
274 # FIXME: Find a better name for this method.
275 fun resolve_mtype_unchecked(mclassdef: MClassDef, ntype: AType, with_virtual: Bool): nullable MType
276 do
277 return resolve_mtype3_unchecked(
278 mclassdef.mmodule,
279 mclassdef.mclass,
280 mclassdef.bound_mtype,
281 ntype,
282 with_virtual
283 )
284 end
285
286 # Return the static type associated to the node `ntype`.
287 #
288 # `mmodule`, `mclass` and `anchor` compose the context where the call is
289 # made (used to understand formal types).
290 # In case of problem, an error is displayed on `ntype` and null is returned.
291 #
292 # Note: The “3” is for 3 contextual parameters.
293 #
294 # SEE: `resolve_mtype`
295 # SEE: `resolve_mtype_unchecked`
296 #
297 # FIXME: Find a better name for this method.
298 fun resolve_mtype3_unchecked(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, ntype: AType, with_virtual: Bool): nullable MType
299 do
300 var qid = ntype.n_qid
301 var name = qid.n_id.text
302 var res: MType
303
304 # Check virtual type
305 if anchor != null and with_virtual then
306 var prop = try_get_mproperty_by_name2(ntype, mmodule, anchor, name).as(nullable MVirtualTypeProp)
307 if prop != null then
308 if not ntype.n_types.is_empty then
309 error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
310 end
311 res = prop.mvirtualtype
312 if ntype.n_kwnullable != null then res = res.as_nullable
313 ntype.mtype = res
314 return res
315 end
316 end
317
318 # Check parameter type
319 if mclass != null then
320 for p in mclass.mparameters do
321 if p.name != name then continue
322
323 if not ntype.n_types.is_empty then
324 error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
325 end
326
327 res = p
328 if ntype.n_kwnullable != null then res = res.as_nullable
329 ntype.mtype = res
330 return res
331 end
332 end
333
334 # Check class
335 var found_class = try_get_mclass_by_qid(qid, mmodule)
336 if found_class != null then
337 var arity = ntype.n_types.length
338 if arity != found_class.arity then
339 if arity == 0 then
340 error(ntype, "Type Error: `{found_class.signature_to_s}` is a generic class.")
341 else if found_class.arity == 0 then
342 error(ntype, "Type Error: `{name}` is not a generic class.")
343 else
344 error(ntype, "Type Error: expected {found_class.arity} formal argument(s) for `{found_class.signature_to_s}`; got {arity}.")
345 end
346 return null
347 end
348 if arity == 0 then
349 res = found_class.mclass_type
350 if ntype.n_kwnullable != null then res = res.as_nullable
351 ntype.mtype = res
352 return res
353 else
354 var mtypes = new Array[MType]
355 for nt in ntype.n_types do
356 var mt = resolve_mtype3_unchecked(mmodule, mclass, anchor, nt, with_virtual)
357 if mt == null then return null # Forward error
358 mtypes.add(mt)
359 end
360 res = found_class.get_mtype(mtypes)
361 if ntype.n_kwnullable != null then res = res.as_nullable
362 ntype.mtype = res
363 return res
364 end
365 end
366
367 # If everything fail, then give up with class by proposing things.
368 #
369 # TODO Give hints on formal types (param and virtual)
370 class_not_found(qid, mmodule)
371 ntype.is_broken = true
372 return null
373 end
374
375 # Print an error and suggest hints when the class identified by `qid` in `mmodule` is not found.
376 #
377 # This just print error messages.
378 fun class_not_found(qid: AQclassid, mmodule: MModule)
379 do
380 var name = qid.n_id.text
381 var qname = qid.full_name
382
383 if bad_class_names[mmodule].has(qname) then
384 error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
385 return
386 end
387 bad_class_names[mmodule].add(qname)
388
389 var all_classes = model.get_mclasses_by_name(name)
390 var hints = new Array[String]
391
392 # Look for conflicting classes.
393 if all_classes != null then for c in all_classes do
394 if not mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
395 if not qid.accept(c) then continue
396 hints.add "`{c.full_name}`"
397 end
398 if hints.length > 1 then
399 error(qid, "Error: ambiguous class name `{qname}` in module `{mmodule}`. Conflicts are between {hints.join(",", " and ")}.")
400 return
401 end
402 hints.clear
403
404 # Look for imported but invisible classes.
405 if all_classes != null then for c in all_classes do
406 if not mmodule.in_importation <= c.intro_mmodule then continue
407 if mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
408 if not qid.accept(c) then continue
409 error(qid, "Error: class `{c.full_name}` not visible in module `{mmodule}`.")
410 return
411 end
412
413 # Look for not imported but known classes from importable modules
414 if all_classes != null then for c in all_classes do
415 if mmodule.in_importation <= c.intro_mmodule then continue
416 if c.intro_mmodule.in_importation <= mmodule then continue
417 if c.visibility <= private_visibility then continue
418 if not qid.accept(c) then continue
419 hints.add "`{c.intro_mmodule.full_name}`"
420 end
421 if hints.not_empty then
422 error(qid, "Error: class `{qname}` not found in module `{mmodule}`. Maybe import {hints.join(",", " or ")}?")
423 return
424 end
425
426 # Look for classes with an approximative name.
427 var bests = new BestDistance[MClass](qname.length - name.length / 2) # limit up to 50% name change
428 for c in model.mclasses do
429 if not mmodule.in_importation <= c.intro_mmodule then continue
430 if not mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
431 var d = qname.levenshtein_distance(c.name)
432 bests.update(d, c)
433 d = qname.levenshtein_distance(c.full_name)
434 bests.update(d, c)
435 end
436 if bests.best_items.not_empty then
437 for c in bests.best_items do hints.add "`{c.full_name}`"
438 error(qid, "Error: class `{qname}` not found in module `{mmodule}`. Did you mean {hints.join(",", " or ")}?")
439 return
440 end
441
442 error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
443 end
444
445 # List of already reported bad class names.
446 # Used to not perform and repeat hints again and again.
447 private var bad_class_names = new MultiHashMap[MModule, String]
448
449 # Return the static type associated to the node `ntype`.
450 #
451 # `mclassdef` is the context where the call is made (used to understand
452 # formal types).
453 # In case of problem, an error is displayed on `ntype` and null is returned.
454 #
455 # Same as `resolve_mtype3`, but get the context (module, class and ) from
456 # `mclassdef`.
457 #
458 # SEE: `resolve_mtype3`
459 # SEE: `resolve_mtype_unchecked`
460 #
461 # FIXME: Find a better name for this method.
462 fun resolve_mtype(mclassdef: MClassDef, ntype: AType): nullable MType
463 do
464 return resolve_mtype3(
465 mclassdef.mmodule,
466 mclassdef.mclass,
467 mclassdef.bound_mtype,
468 ntype
469 )
470 end
471
472 # Return the static type associated to the node `ntype`.
473 #
474 # `mmodule`, `mclass` and `anchor` compose the context where the call is
475 # made (used to understand formal types).
476 # In case of problem, an error is displayed on `ntype` and null is returned.
477 #
478 # Note: The “3” is for 3 contextual parameters.
479 #
480 # SEE: `resolve_mtype`
481 # SEE: `resolve_mtype_unchecked`
482 #
483 # FIXME: Find a better name for this method.
484 fun resolve_mtype3(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, ntype: AType): nullable MType
485 do
486 var mtype = ntype.mtype
487 if mtype == null then mtype = resolve_mtype3_unchecked(mmodule, mclass, anchor, ntype, true)
488 if mtype == null then return null # Forward error
489
490 if ntype.checked_mtype then return mtype
491 if mtype isa MGenericType then
492 var found_class = mtype.mclass
493 for i in [0..found_class.arity[ do
494 var intro = found_class.try_intro
495 if intro == null then return null # skip error
496 var bound = intro.bound_mtype.arguments[i]
497 var nt = ntype.n_types[i]
498 var mt = resolve_mtype3(mmodule, mclass, anchor, nt)
499 if mt == null then return null # forward error
500 if not check_subtype(nt, mmodule, anchor, mt, bound) then
501 error(nt, "Type Error: expected `{bound}`, got `{mt}`.")
502 return null
503 end
504 end
505 end
506 ntype.checked_mtype = true
507 return mtype
508 end
509
510 # Check that `sub` is a subtype of `sup`.
511 # Do not display an error message.
512 #
513 # This method is used a an entry point for the modelize phase to test static subtypes.
514 # Some refinements could redefine it to collect statictics.
515 fun check_subtype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
516 do
517 return sub.is_subtype(mmodule, anchor, sup)
518 end
519
520 # Check that `sub` and `sup` are equvalent types.
521 # Do not display an error message.
522 #
523 # This method is used a an entry point for the modelize phase to test static equivalent types.
524 # Some refinements could redefine it to collect statictics.
525 fun check_sametype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
526 do
527 return sub.is_subtype(mmodule, anchor, sup) and sup.is_subtype(mmodule, anchor, sub)
528 end
529 end
530
531 redef class ANode
532 # The indication that the node did not pass some semantic verifications.
533 #
534 # This simple flag is set by a given analysis to say that the node is broken and unusable in
535 # an execution.
536 # When a node status is set to broken, it is usually associated with a error message.
537 #
538 # If it is safe to do so, clients of the AST SHOULD just skip broken nodes in their processing.
539 # Clients that do not care about the executability (e.g. metrics) MAY still process the node or
540 # perform specific checks to determinate the validity of the node.
541 #
542 # Note that the broken status is not propagated to parent or children nodes.
543 # e.g. a broken expression used as argument does not make the whole call broken.
544 var is_broken = false is writable
545
546 redef fun dump_info(v) do
547 var res = super
548 if is_broken then
549 res += v.red("*broken*")
550 end
551 return res
552 end
553 end
554
555 redef class AType
556 # The mtype associated to the node
557 var mtype: nullable MType = null
558
559 # Is the mtype a valid one?
560 var checked_mtype: Bool = false
561
562 redef fun dump_info(v) do
563 var res = super
564 var mtype = self.mtype
565 if mtype != null then
566 res += v.yellow(":{mtype}")
567 end
568 return res
569 end
570 end
571
572 redef class AVisibility
573 # The visibility level associated with the AST node class
574 fun mvisibility: MVisibility is abstract
575 end
576 redef class AIntrudeVisibility
577 redef fun mvisibility do return intrude_visibility
578 end
579 redef class APublicVisibility
580 redef fun mvisibility do return public_visibility
581 end
582 redef class AProtectedVisibility
583 redef fun mvisibility do return protected_visibility
584 end
585 redef class APrivateVisibility
586 redef fun mvisibility do return private_visibility
587 end
588
589 redef class ADoc
590 private var mdoc_cache: nullable MDoc
591
592 # Convert `self` to a `MDoc`
593 fun to_mdoc: MDoc
594 do
595 var res = mdoc_cache
596 if res != null then return res
597 res = new MDoc(location)
598 for c in n_comment do
599 var text = c.text
600 if text.length < 2 then
601 res.content.add ""
602 continue
603 end
604 assert text.chars[0] == '#'
605 if text.chars[1] == ' ' then
606 text = text.substring_from(2) # eat starting `#` and space
607 else
608 text = text.substring_from(1) # eat atarting `#` only
609 end
610 if text.chars.last == '\n' then text = text.substring(0, text.length-1) # drop \n
611 res.content.add(text)
612 end
613 mdoc_cache = res
614 return res
615 end
616 end
617
618 redef class AQclassid
619 # The name of the package part, if any
620 fun mpackname: nullable String do
621 var nqualified = n_qualified
622 if nqualified == null then return null
623 var nids = nqualified.n_id
624 if nids.length <= 0 then return null
625 return nids[0].text
626 end
627
628 # The name of the module part, if any
629 fun mmodname: nullable String do
630 var nqualified = n_qualified
631 if nqualified == null then return null
632 var nids = nqualified.n_id
633 if nids.length <= 1 then return null
634 return nids[1].text
635 end
636
637 # Does `mclass` match the full qualified name?
638 fun accept(mclass: MClass): Bool
639 do
640 if mclass.name != n_id.text then return false
641 var mpackname = self.mpackname
642 if mpackname != null then
643 var mpackage = mclass.intro_mmodule.mpackage
644 if mpackage == null then return false
645 if mpackage.name != mpackname then return false
646 var mmodname = self.mmodname
647 if mmodname != null and mclass.intro_mmodule.name != mmodname then return false
648 end
649 return true
650 end
651
652 # The pretty name represented by self.
653 fun full_name: String
654 do
655 var res = n_id.text
656 var nqualified = n_qualified
657 if nqualified == null then return res
658 var ncid = nqualified.n_classid
659 if ncid != null then res = ncid.text + "::" + res
660 var nids = nqualified.n_id
661 if nids.not_empty then for n in nids.reverse_iterator do
662 res = n.text + "::" + res
663 end
664 return res
665 end
666 end