modelbuilder: resolve_mtype does not crash when the class is incomplete
[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: 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, "Ambigous 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 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
91 do
92 var props = self.model.get_mproperties_by_name(name)
93 if props == null then
94 return null
95 end
96
97 var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
98 if cache != null then return cache
99
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
105
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
108 continue
109 end
110
111 if res == null then
112 res = mprop
113 continue
114 end
115
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
123 res = mprop
124 continue
125 end
126 end
127
128 # Ok, just keep all prop in the ress table
129 if ress == null then
130 ress = new Array[MProperty]
131 ress.add(res)
132 end
133 ress.add(mprop)
134 end
135
136 # There is conflict?
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]
141 for mprop in ress do
142 var mproptype = mprop.intro_mclassdef.bound_mtype
143 if not restype.is_subtype(mmodule, null, mproptype) then
144 ress2.add(mprop)
145 else if not mprop isa MMethod or not mprop.is_init then
146 ress2.add(mprop)
147 end
148 end
149 if ress2.is_empty then
150 ress = null
151 else
152 ress = ress2
153 ress.add(res)
154 end
155 end
156
157 if ress != null 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 ")}")
162 end
163
164 self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
165 return res
166 end
167
168 private var try_get_mproperty_by_name2_cache = new HashMap3[MModule, MType, String, nullable MProperty]
169
170
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
173 do
174 return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
175 end
176
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)
180 do
181 var l = null
182 if n != null then l = n.hot_location
183 self.toolcontext.error(l, text)
184 end
185
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)
189 do
190 var l = null
191 if n != null then l = n.hot_location
192 self.toolcontext.warning(l, tag, text)
193 end
194
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)
198 do
199 var l = null
200 if n != null then l = n.hot_location
201 self.toolcontext.advice(l, tag, text)
202 end
203
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
206 do
207 var res = mmodule.try_get_primitive_method(name, recv)
208 if res == null then
209 var l = null
210 if n != null then l = n.hot_location
211 self.toolcontext.fatal_error(l, "Fatal Error: {recv} must have a property named {name}.")
212 abort
213 end
214 return res
215 end
216
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
222 do
223 var name = ntype.n_id.text
224 var res: MType
225
226 # Check virtual type
227 if mclassdef != null and with_virtual then
228 var prop = try_get_mproperty_by_name(ntype, mclassdef, name).as(nullable MVirtualTypeProp)
229 if prop != null then
230 if not ntype.n_types.is_empty then
231 error(ntype, "Type error: formal type {name} cannot have formal parameters.")
232 end
233 res = prop.mvirtualtype
234 if ntype.n_kwnullable != null then res = res.as_nullable
235 ntype.mtype = res
236 return res
237 end
238 end
239
240 # Check parameter type
241 if mclassdef != null then
242 for p in mclassdef.mclass.mparameters do
243 if p.name != name then continue
244
245 if not ntype.n_types.is_empty then
246 error(ntype, "Type error: formal type {name} cannot have formal parameters.")
247 end
248
249 res = p
250 if ntype.n_kwnullable != null then res = res.as_nullable
251 ntype.mtype = res
252 return res
253 end
254 end
255
256 # Check class
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
261 if arity == 0 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.")
265 else
266 error(ntype, "Type error: '{name}' has {mclass.arity} parameters ({arity} are provided).")
267 end
268 return null
269 end
270 if arity == 0 then
271 res = mclass.mclass_type
272 if ntype.n_kwnullable != null then res = res.as_nullable
273 ntype.mtype = res
274 return res
275 else
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
280 mtypes.add(mt)
281 end
282 res = mclass.get_mtype(mtypes)
283 if ntype.n_kwnullable != null then res = res.as_nullable
284 ntype.mtype = res
285 return res
286 end
287 end
288
289 # If everything fail, then give up :(
290 error(ntype, "Type error: class {name} not found in module {mmodule}.")
291 return null
292 end
293
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
299 do
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
303
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 intro = mclass.try_intro
309 if intro == null then return null # skip error
310 var bound = intro.bound_mtype.arguments[i]
311 var nt = ntype.n_types[i]
312 var mt = resolve_mtype(mmodule, mclassdef, nt)
313 if mt == null then return null # forward error
314 var anchor
315 if mclassdef != null then anchor = mclassdef.bound_mtype else anchor = null
316 if not check_subtype(nt, mmodule, anchor, mt, bound) then
317 error(nt, "Type error: expected {bound}, got {mt}")
318 return null
319 end
320 end
321 end
322 ntype.checked_mtype = true
323 return mtype
324 end
325
326 # Check that `sub` is a subtype of `sup`.
327 # Do not display an error message.
328 #
329 # This method is used a an entry point for the modelize phase to test static subtypes.
330 # Some refinements could redefine it to collect statictics.
331 fun check_subtype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
332 do
333 return sub.is_subtype(mmodule, anchor, sup)
334 end
335
336 # Check that `sub` and `sup` are equvalent types.
337 # Do not display an error message.
338 #
339 # This method is used a an entry point for the modelize phase to test static equivalent types.
340 # Some refinements could redefine it to collect statictics.
341 fun check_sametype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
342 do
343 return sub.is_subtype(mmodule, anchor, sup) and sup.is_subtype(mmodule, anchor, sub)
344 end
345 end
346
347 redef class AType
348 # The mtype associated to the node
349 var mtype: nullable MType = null
350
351 # Is the mtype a valid one?
352 var checked_mtype: Bool = false
353 end
354
355 redef class AVisibility
356 # The visibility level associated with the AST node class
357 fun mvisibility: MVisibility is abstract
358 end
359 redef class AIntrudeVisibility
360 redef fun mvisibility do return intrude_visibility
361 end
362 redef class APublicVisibility
363 redef fun mvisibility do return public_visibility
364 end
365 redef class AProtectedVisibility
366 redef fun mvisibility do return protected_visibility
367 end
368 redef class APrivateVisibility
369 redef fun mvisibility do return private_visibility
370 end
371
372 redef class ADoc
373 private var mdoc_cache: nullable MDoc
374
375 # Convert `self` to a `MDoc`
376 fun to_mdoc: MDoc
377 do
378 var res = mdoc_cache
379 if res != null then return res
380 res = new MDoc(location)
381 for c in n_comment do
382 var text = c.text
383 if text.length < 2 then
384 res.content.add ""
385 continue
386 end
387 assert text.chars[0] == '#'
388 if text.chars[1] == ' ' then
389 text = text.substring_from(2) # eat starting `#` and space
390 else
391 text = text.substring_from(1) # eat atarting `#` only
392 end
393 if text.chars.last == '\n' then text = text.substring(0, text.length-1) # drop \n
394 res.content.add(text)
395 end
396 mdoc_cache = res
397 return res
398 end
399 end