1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2012 Jean Privat <jean@pryen.org>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Load nit source files and build the associated model
21 # FIXME split this module into submodules
22 # FIXME add missing error checks
23 module modelbuilder_base
29 private import more_collections
33 redef class ToolContext
35 # The modelbuilder 1-to-1 associated with the toolcontext
36 fun modelbuilder
: ModelBuilder do return modelbuilder_real
.as(not null)
38 private var modelbuilder_real
: nullable ModelBuilder = null
42 # A model builder knows how to load nit source files and build the associated model
44 # The model where new modules, classes and properties are added
47 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
48 var toolcontext
: ToolContext
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)
54 assert toolcontext
.modelbuilder_real
== null
55 toolcontext
.modelbuilder_real
= self
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
: ANode, mmodule
: MModule, name
: String): nullable MClass
65 var classes
= model
.get_mclasses_by_name
(name
)
66 if classes
== null then
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
77 error
(anode
, "Error: ambiguous class name `{name}`; conflict between `{mclass.full_name}` and `{res.full_name}`.")
84 # Return a class identified by `qid` visible by the module `mmodule`.
85 # Visibility in modules and qualified names are correctly handled.
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
92 var name
= qid
.n_id
.text
94 var classes
= model
.get_mclasses_by_name
(name
)
95 if classes
== null then
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
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
: ANode, mmodule
: MModule, name
: String): nullable MClass
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}'.")
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
: ANode, mmodule
: MModule, mtype
: MType, name
: String): nullable MProperty
132 var props
= self.model
.get_mproperties_by_name
(name
)
133 if props
== null then
137 var cache
= self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
]
138 if cache
!= null then return cache
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
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
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
168 # Ok, just keep all prop in the ress table
170 ress
= new Array[MProperty]
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]
182 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
183 if not restype
.is_subtype
(mmodule
, null, mproptype
) then
185 else if not mprop
isa MMethod or not mprop
.is_init
then
189 if ress2
.is_empty
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 ")}.")
204 self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
] = res
208 private var try_get_mproperty_by_name2_cache
= new HashMap3[MModule, MType, String, nullable MProperty]
211 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
212 fun try_get_mproperty_by_name
(anode
: ANode, mclassdef
: MClassDef, name
: String): nullable MProperty
214 return try_get_mproperty_by_name2
(anode
, mclassdef
.mmodule
, mclassdef
.bound_mtype
, name
)
217 # Helper function to display an error on a node.
218 # Alias for `self.toolcontext.error(n.hot_location, text)`
220 # This automatically sets `n.is_broken` to true.
221 fun error
(n
: nullable ANode, text
: String)
228 self.toolcontext
.error
(l
, text
)
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)
236 if n
!= null then l
= n
.hot_location
237 self.toolcontext
.warning
(l
, tag
, text
)
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)
245 if n
!= null then l
= n
.hot_location
246 self.toolcontext
.advice
(l
, tag
, text
)
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
252 var res
= mmodule
.try_get_primitive_method
(name
, recv
)
255 if n
!= null then l
= n
.hot_location
256 self.toolcontext
.fatal_error
(l
, "Fatal Error: `{recv}` must have a property named `{name}`.")
262 # Return the static type associated to the node `ntype`.
263 # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
264 # In case of problem, an error is displayed on `ntype` and null is returned.
265 # FIXME: the name "resolve_mtype" is awful
266 fun resolve_mtype_unchecked
(mmodule
: MModule, mclassdef
: nullable MClassDef, ntype
: AType, with_virtual
: Bool): nullable MType
268 var qid
= ntype
.n_qid
269 var name
= qid
.n_id
.text
273 if mclassdef
!= null and with_virtual
then
274 var prop
= try_get_mproperty_by_name
(ntype
, mclassdef
, name
).as(nullable MVirtualTypeProp)
276 if not ntype
.n_types
.is_empty
then
277 error
(ntype
, "Type Error: formal type `{name}` cannot have formal parameters.")
279 res
= prop
.mvirtualtype
280 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
286 # Check parameter type
287 if mclassdef
!= null then
288 for p
in mclassdef
.mclass
.mparameters
do
289 if p
.name
!= name
then continue
291 if not ntype
.n_types
.is_empty
then
292 error
(ntype
, "Type Error: formal type `{name}` cannot have formal parameters.")
296 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
303 var mclass
= try_get_mclass_by_qid
(qid
, mmodule
)
304 if mclass
!= null then
305 var arity
= ntype
.n_types
.length
306 if arity
!= mclass
.arity
then
308 error
(ntype
, "Type Error: `{mclass.signature_to_s}` is a generic class.")
309 else if mclass
.arity
== 0 then
310 error
(ntype
, "Type Error: `{name}` is not a generic class.")
312 error
(ntype
, "Type Error: expected {mclass.arity} formal argument(s) for `{mclass.signature_to_s}`; got {arity}.")
317 res
= mclass
.mclass_type
318 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
322 var mtypes
= new Array[MType]
323 for nt
in ntype
.n_types
do
324 var mt
= resolve_mtype_unchecked
(mmodule
, mclassdef
, nt
, with_virtual
)
325 if mt
== null then return null # Forward error
328 res
= mclass
.get_mtype
(mtypes
)
329 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
335 # If everything fail, then give up with class by proposing things.
337 # TODO Give hints on formal types (param and virtual)
338 class_not_found
(qid
, mmodule
)
339 ntype
.is_broken
= true
343 # Print an error and suggest hints when the class identified by `qid` in `mmodule` is not found.
345 # This just print error messages.
346 fun class_not_found
(qid
: AQclassid, mmodule
: MModule)
348 var name
= qid
.n_id
.text
349 var qname
= qid
.full_name
351 if bad_class_names
[mmodule
].has
(qname
) then
352 error
(qid
, "Error: class `{qname}` not found in module `{mmodule}`.")
355 bad_class_names
[mmodule
].add
(qname
)
357 var all_classes
= model
.get_mclasses_by_name
(name
)
358 var hints
= new Array[String]
360 # Look for conflicting classes.
361 if all_classes
!= null then for c
in all_classes
do
362 if not mmodule
.is_visible
(c
.intro_mmodule
, c
.visibility
) then continue
363 if not qid
.accept
(c
) then continue
364 hints
.add
"`{c.full_name}`"
366 if hints
.length
> 1 then
367 error
(qid
, "Error: ambiguous class name `{qname}` in module `{mmodule}`. Conflicts are between {hints.join(",", " and ")}.")
372 # Look for imported but invisible classes.
373 if all_classes
!= null then for c
in all_classes
do
374 if not mmodule
.in_importation
<= c
.intro_mmodule
then continue
375 if mmodule
.is_visible
(c
.intro_mmodule
, c
.visibility
) then continue
376 if not qid
.accept
(c
) then continue
377 error
(qid
, "Error: class `{c.full_name}` not visible in module `{mmodule}`.")
381 # Look for not imported but known classes from importable modules
382 if all_classes
!= null then for c
in all_classes
do
383 if mmodule
.in_importation
<= c
.intro_mmodule
then continue
384 if c
.intro_mmodule
.in_importation
<= mmodule
then continue
385 if c
.visibility
<= private_visibility
then continue
386 if not qid
.accept
(c
) then continue
387 hints
.add
"`{c.intro_mmodule.full_name}`"
389 if hints
.not_empty
then
390 error
(qid
, "Error: class `{qname}` not found in module `{mmodule}`. Maybe import {hints.join(",", " or ")}?")
394 # Look for classes with an approximative name.
395 var bests
= new BestDistance[MClass](qname
.length
- name
.length
/ 2) # limit up to 50% name change
396 for c
in model
.mclasses
do
397 if not mmodule
.in_importation
<= c
.intro_mmodule
then continue
398 if not mmodule
.is_visible
(c
.intro_mmodule
, c
.visibility
) then continue
399 var d
= qname
.levenshtein_distance
(c
.name
)
401 d
= qname
.levenshtein_distance
(c
.full_name
)
404 if bests
.best_items
.not_empty
then
405 for c
in bests
.best_items
do hints
.add
"`{c.full_name}`"
406 error
(qid
, "Error: class `{qname}` not found in module `{mmodule}`. Did you mean {hints.join(",", " or ")}?")
410 error
(qid
, "Error: class `{qname}` not found in module `{mmodule}`.")
413 # List of already reported bad class names.
414 # Used to not perform and repeat hints again and again.
415 private var bad_class_names
= new MultiHashMap[MModule, String]
417 # Return the static type associated to the node `ntype`.
418 # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
419 # In case of problem, an error is displayed on `ntype` and null is returned.
420 # FIXME: the name "resolve_mtype" is awful
421 fun resolve_mtype
(mmodule
: MModule, mclassdef
: nullable MClassDef, ntype
: AType): nullable MType
423 var mtype
= ntype
.mtype
424 if mtype
== null then mtype
= resolve_mtype_unchecked
(mmodule
, mclassdef
, ntype
, true)
425 if mtype
== null then return null # Forward error
427 if ntype
.checked_mtype
then return mtype
428 if mtype
isa MGenericType then
429 var mclass
= mtype
.mclass
430 for i
in [0..mclass
.arity
[ do
431 var intro
= mclass
.try_intro
432 if intro
== null then return null # skip error
433 var bound
= intro
.bound_mtype
.arguments
[i
]
434 var nt
= ntype
.n_types
[i
]
435 var mt
= resolve_mtype
(mmodule
, mclassdef
, nt
)
436 if mt
== null then return null # forward error
438 if mclassdef
!= null then anchor
= mclassdef
.bound_mtype
else anchor
= null
439 if not check_subtype
(nt
, mmodule
, anchor
, mt
, bound
) then
440 error
(nt
, "Type Error: expected `{bound}`, got `{mt}`.")
445 ntype
.checked_mtype
= true
449 # Check that `sub` is a subtype of `sup`.
450 # Do not display an error message.
452 # This method is used a an entry point for the modelize phase to test static subtypes.
453 # Some refinements could redefine it to collect statictics.
454 fun check_subtype
(node
: ANode, mmodule
: MModule, anchor
: nullable MClassType, sub
, sup
: MType): Bool
456 return sub
.is_subtype
(mmodule
, anchor
, sup
)
459 # Check that `sub` and `sup` are equvalent types.
460 # Do not display an error message.
462 # This method is used a an entry point for the modelize phase to test static equivalent types.
463 # Some refinements could redefine it to collect statictics.
464 fun check_sametype
(node
: ANode, mmodule
: MModule, anchor
: nullable MClassType, sub
, sup
: MType): Bool
466 return sub
.is_subtype
(mmodule
, anchor
, sup
) and sup
.is_subtype
(mmodule
, anchor
, sub
)
471 # The indication that the node did not pass some semantic verifications.
473 # This simple flag is set by a given analysis to say that the node is broken and unusable in
475 # When a node status is set to broken, it is usually associated with a error message.
477 # If it is safe to do so, clients of the AST SHOULD just skip broken nodes in their processing.
478 # Clients that do not care about the executability (e.g. metrics) MAY still process the node or
479 # perform specific checks to determinate the validity of the node.
481 # Note that the broken status is not propagated to parent or children nodes.
482 # e.g. a broken expression used as argument does not make the whole call broken.
483 var is_broken
= false is writable
485 redef fun dump_info
(v
) do
488 res
+= v
.red
("*broken*")
495 # The mtype associated to the node
496 var mtype
: nullable MType = null
498 # Is the mtype a valid one?
499 var checked_mtype
: Bool = false
501 redef fun dump_info
(v
) do
503 var mtype
= self.mtype
504 if mtype
!= null then
505 res
+= v
.yellow
(":{mtype}")
511 redef class AVisibility
512 # The visibility level associated with the AST node class
513 fun mvisibility
: MVisibility is abstract
515 redef class AIntrudeVisibility
516 redef fun mvisibility
do return intrude_visibility
518 redef class APublicVisibility
519 redef fun mvisibility
do return public_visibility
521 redef class AProtectedVisibility
522 redef fun mvisibility
do return protected_visibility
524 redef class APrivateVisibility
525 redef fun mvisibility
do return private_visibility
529 private var mdoc_cache
: nullable MDoc
531 # Convert `self` to a `MDoc`
535 if res
!= null then return res
536 res
= new MDoc(location
)
537 for c
in n_comment
do
539 if text
.length
< 2 then
543 assert text
.chars
[0] == '#'
544 if text
.chars
[1] == ' ' then
545 text
= text
.substring_from
(2) # eat starting `#` and space
547 text
= text
.substring_from
(1) # eat atarting `#` only
549 if text
.chars
.last
== '\n' then text
= text
.substring
(0, text
.length-1
) # drop \n
550 res
.content
.add
(text
)
557 redef class AQclassid
558 # The name of the package part, if any
559 fun mpackname
: nullable String do
560 var nqualified
= n_qualified
561 if nqualified
== null then return null
562 var nids
= nqualified
.n_id
563 if nids
.length
<= 0 then return null
567 # The name of the module part, if any
568 fun mmodname
: nullable String do
569 var nqualified
= n_qualified
570 if nqualified
== null then return null
571 var nids
= nqualified
.n_id
572 if nids
.length
<= 1 then return null
576 # Does `mclass` match the full qualified name?
577 fun accept
(mclass
: MClass): Bool
579 if mclass
.name
!= n_id
.text
then return false
580 var mpackname
= self.mpackname
581 if mpackname
!= null then
582 var mpackage
= mclass
.intro_mmodule
.mpackage
583 if mpackage
== null then return false
584 if mpackage
.name
!= mpackname
then return false
585 var mmodname
= self.mmodname
586 if mmodname
!= null and mclass
.intro_mmodule
.name
!= mmodname
then return false
591 # The pretty name represented by self.
592 fun full_name
: String
595 var nqualified
= n_qualified
596 if nqualified
== null then return res
597 var ncid
= nqualified
.n_classid
598 if ncid
!= null then res
= ncid
.text
+ "::" + res
599 var nids
= nqualified
.n_id
600 if nids
.not_empty
then for n
in nids
.reverse_iterator
do
601 res
= n
.text
+ "::" + res