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
, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
84 # Return a property named `name` on the type `mtype` visible in the module `mmodule`.
85 # Visibility in modules is correctly handled.
86 # Protected properties are returned (it is up to the caller to check and reject protected properties).
87 # If no such a property exists, then null is returned.
88 # If more than one property exists, then an error on `anode` is displayed and null is returned.
89 # FIXME: add a way to handle property name conflict
90 fun try_get_mproperty_by_name2
(anode
: ANode, mmodule
: MModule, mtype
: MType, name
: String): nullable MProperty
92 var props
= self.model
.get_mproperties_by_name
(name
)
97 var cache
= self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
]
98 if cache
!= null then return cache
100 var res
: nullable MProperty = null
101 var ress
: nullable Array[MProperty] = null
102 for mprop
in props
do
103 if not mtype
.has_mproperty
(mmodule
, mprop
) then continue
104 if not mmodule
.is_visible
(mprop
.intro_mclassdef
.mmodule
, mprop
.visibility
) then continue
106 # new-factories are invisible outside of the class
107 if mprop
isa MMethod and mprop
.is_new
and (not mtype
isa MClassType or mprop
.intro_mclassdef
.mclass
!= mtype
.mclass
) then
116 # Two global properties?
117 # First, special case for init, keep the most specific ones
118 if res
isa MMethod and mprop
isa MMethod and res
.is_init
and mprop
.is_init
then
119 var restype
= res
.intro_mclassdef
.bound_mtype
120 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
121 if mproptype
.is_subtype
(mmodule
, null, restype
) then
122 # found a most specific constructor, so keep it
128 # Ok, just keep all prop in the ress table
130 ress
= new Array[MProperty]
137 if ress
!= null and res
isa MMethod and res
.is_init
then
138 # special case forinit again
139 var restype
= res
.intro_mclassdef
.bound_mtype
140 var ress2
= new Array[MProperty]
142 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
143 if not restype
.is_subtype
(mmodule
, null, mproptype
) then
145 else if not mprop
isa MMethod or not mprop
.is_init
then
149 if ress2
.is_empty
then
158 assert ress
.length
> 1
159 var s
= new Array[String]
160 for mprop
in ress
do s
.add mprop
.full_name
161 self.error
(anode
, "Ambigous property name '{name}' for {mtype}; conflict between {s.join(" and ")}")
164 self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
] = res
168 private var try_get_mproperty_by_name2_cache
= new HashMap3[MModule, MType, String, nullable MProperty]
171 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
172 fun try_get_mproperty_by_name
(anode
: ANode, mclassdef
: MClassDef, name
: String): nullable MProperty
174 return try_get_mproperty_by_name2
(anode
, mclassdef
.mmodule
, mclassdef
.bound_mtype
, name
)
177 # Helper function to display an error on a node.
178 # Alias for `self.toolcontext.error(n.hot_location, text)`
179 fun error
(n
: nullable ANode, text
: String)
182 if n
!= null then l
= n
.hot_location
183 self.toolcontext
.error
(l
, text
)
186 # Helper function to display a warning on a node.
187 # Alias for: `self.toolcontext.warning(n.hot_location, text)`
188 fun warning
(n
: nullable ANode, tag
, text
: String)
191 if n
!= null then l
= n
.hot_location
192 self.toolcontext
.warning
(l
, tag
, text
)
195 # Helper function to display an advice on a node.
196 # Alias for: `self.toolcontext.advice(n.hot_location, text)`
197 fun advice
(n
: nullable ANode, tag
, text
: String)
200 if n
!= null then l
= n
.hot_location
201 self.toolcontext
.advice
(l
, tag
, text
)
204 # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
205 fun force_get_primitive_method
(n
: nullable ANode, name
: String, recv
: MClass, mmodule
: MModule): MMethod
207 var res
= mmodule
.try_get_primitive_method
(name
, recv
)
210 if n
!= null then l
= n
.hot_location
211 self.toolcontext
.fatal_error
(l
, "Fatal Error: {recv} must have a property named {name}.")
217 # Return the static type associated to the node `ntype`.
218 # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
219 # In case of problem, an error is displayed on `ntype` and null is returned.
220 # FIXME: the name "resolve_mtype" is awful
221 fun resolve_mtype_unchecked
(mmodule
: MModule, mclassdef
: nullable MClassDef, ntype
: AType, with_virtual
: Bool): nullable MType
223 var name
= ntype
.n_id
.text
227 if mclassdef
!= null and with_virtual
then
228 var prop
= try_get_mproperty_by_name
(ntype
, mclassdef
, name
).as(nullable MVirtualTypeProp)
230 if not ntype
.n_types
.is_empty
then
231 error
(ntype
, "Type error: formal type {name} cannot have formal parameters.")
233 res
= prop
.mvirtualtype
234 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
240 # Check parameter type
241 if mclassdef
!= null then
242 for p
in mclassdef
.mclass
.mparameters
do
243 if p
.name
!= name
then continue
245 if not ntype
.n_types
.is_empty
then
246 error
(ntype
, "Type error: formal type {name} cannot have formal parameters.")
250 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
257 var mclass
= try_get_mclass_by_name
(ntype
, mmodule
, name
)
258 if mclass
!= null then
259 var arity
= ntype
.n_types
.length
260 if arity
!= mclass
.arity
then
262 error
(ntype
, "Type error: '{name}' is a generic class.")
263 else if mclass
.arity
== 0 then
264 error
(ntype
, "Type error: '{name}' is not a generic class.")
266 error
(ntype
, "Type error: '{name}' has {mclass.arity} parameters ({arity} are provided).")
271 res
= mclass
.mclass_type
272 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
276 var mtypes
= new Array[MType]
277 for nt
in ntype
.n_types
do
278 var mt
= resolve_mtype_unchecked
(mmodule
, mclassdef
, nt
, with_virtual
)
279 if mt
== null then return null # Forward error
282 res
= mclass
.get_mtype
(mtypes
)
283 if ntype
.n_kwnullable
!= null then res
= res
.as_nullable
289 # If everything fail, then give up :(
290 error
(ntype
, "Type error: class {name} not found in module {mmodule}.")
294 # Return the static type associated to the node `ntype`.
295 # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
296 # In case of problem, an error is displayed on `ntype` and null is returned.
297 # FIXME: the name "resolve_mtype" is awful
298 fun resolve_mtype
(mmodule
: MModule, mclassdef
: nullable MClassDef, ntype
: AType): nullable MType
300 var mtype
= ntype
.mtype
301 if mtype
== null then mtype
= resolve_mtype_unchecked
(mmodule
, mclassdef
, ntype
, true)
302 if mtype
== null then return null # Forward error
304 if ntype
.checked_mtype
then return mtype
305 if mtype
isa MGenericType then
306 var mclass
= mtype
.mclass
307 for i
in [0..mclass
.arity
[ do
308 var bound
= mclass
.intro
.bound_mtype
.arguments
[i
]
309 var nt
= ntype
.n_types
[i
]
310 var mt
= resolve_mtype
(mmodule
, mclassdef
, nt
)
311 if mt
== null then return null # forward error
313 if mclassdef
!= null then anchor
= mclassdef
.bound_mtype
else anchor
= null
314 if not check_subtype
(nt
, mmodule
, anchor
, mt
, bound
) then
315 error
(nt
, "Type error: expected {bound}, got {mt}")
320 ntype
.checked_mtype
= true
324 # Check that `sub` is a subtype of `sup`.
325 # Do not display an error message.
327 # This method is used a an entry point for the modelize phase to test static subtypes.
328 # Some refinements could redefine it to collect statictics.
329 fun check_subtype
(node
: ANode, mmodule
: MModule, anchor
: nullable MClassType, sub
, sup
: MType): Bool
331 return sub
.is_subtype
(mmodule
, anchor
, sup
)
334 # Check that `sub` and `sup` are equvalent types.
335 # Do not display an error message.
337 # This method is used a an entry point for the modelize phase to test static equivalent types.
338 # Some refinements could redefine it to collect statictics.
339 fun check_sametype
(node
: ANode, mmodule
: MModule, anchor
: nullable MClassType, sub
, sup
: MType): Bool
341 return sub
.is_subtype
(mmodule
, anchor
, sup
) and sup
.is_subtype
(mmodule
, anchor
, sub
)
346 # The mtype associated to the node
347 var mtype
: nullable MType = null
349 # Is the mtype a valid one?
350 var checked_mtype
: Bool = false
353 redef class AVisibility
354 # The visibility level associated with the AST node class
355 fun mvisibility
: MVisibility is abstract
357 redef class AIntrudeVisibility
358 redef fun mvisibility
do return intrude_visibility
360 redef class APublicVisibility
361 redef fun mvisibility
do return public_visibility
363 redef class AProtectedVisibility
364 redef fun mvisibility
do return protected_visibility
366 redef class APrivateVisibility
367 redef fun mvisibility
do return private_visibility
371 private var mdoc_cache
: nullable MDoc
373 # Convert `self` to a `MDoc`
377 if res
!= null then return res
379 for c
in n_comment
do
381 if text
.length
< 2 then
385 assert text
.chars
[0] == '#'
386 if text
.chars
[1] == ' ' then
387 text
= text
.substring_from
(2) # eat starting `#` and space
389 text
= text
.substring_from
(1) # eat atarting `#` only
391 if text
.chars
.last
== '\n' then text
= text
.substring
(0, text
.length-1
) # drop \n
392 res
.content
.add
(text
)