modelbuilder: add `ANode::is_broken`
[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, "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 # Like `try_get_mclass_by_name` but display an error message when the class is not found
85 fun get_mclass_by_name(node: ANode, mmodule: MModule, name: String): nullable MClass
86 do
87 var mclass = try_get_mclass_by_name(node, mmodule, name)
88 if mclass == null then
89 error(node, "Type Error: missing primitive class `{name}'.")
90 end
91 return mclass
92 end
93
94 # Return a property named `name` on the type `mtype` visible in the module `mmodule`.
95 # Visibility in modules is correctly handled.
96 # Protected properties are returned (it is up to the caller to check and reject protected properties).
97 # If no such a property exists, then null is returned.
98 # If more than one property exists, then an error on `anode` is displayed and null is returned.
99 # FIXME: add a way to handle property name conflict
100 fun try_get_mproperty_by_name2(anode: ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty
101 do
102 var props = self.model.get_mproperties_by_name(name)
103 if props == null then
104 return null
105 end
106
107 var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
108 if cache != null then return cache
109
110 var res: nullable MProperty = null
111 var ress: nullable Array[MProperty] = null
112 for mprop in props do
113 if not mtype.has_mproperty(mmodule, mprop) then continue
114 if not mmodule.is_visible(mprop.intro_mclassdef.mmodule, mprop.visibility) then continue
115
116 # new-factories are invisible outside of the class
117 if mprop isa MMethod and mprop.is_new and (not mtype isa MClassType or mprop.intro_mclassdef.mclass != mtype.mclass) then
118 continue
119 end
120
121 if res == null then
122 res = mprop
123 continue
124 end
125
126 # Two global properties?
127 # First, special case for init, keep the most specific ones
128 if res isa MMethod and mprop isa MMethod and res.is_init and mprop.is_init then
129 var restype = res.intro_mclassdef.bound_mtype
130 var mproptype = mprop.intro_mclassdef.bound_mtype
131 if mproptype.is_subtype(mmodule, null, restype) then
132 # found a most specific constructor, so keep it
133 res = mprop
134 continue
135 end
136 end
137
138 # Ok, just keep all prop in the ress table
139 if ress == null then
140 ress = new Array[MProperty]
141 ress.add(res)
142 end
143 ress.add(mprop)
144 end
145
146 # There is conflict?
147 if ress != null and res isa MMethod and res.is_init then
148 # special case forinit again
149 var restype = res.intro_mclassdef.bound_mtype
150 var ress2 = new Array[MProperty]
151 for mprop in ress do
152 var mproptype = mprop.intro_mclassdef.bound_mtype
153 if not restype.is_subtype(mmodule, null, mproptype) then
154 ress2.add(mprop)
155 else if not mprop isa MMethod or not mprop.is_init then
156 ress2.add(mprop)
157 end
158 end
159 if ress2.is_empty then
160 ress = null
161 else
162 ress = ress2
163 ress.add(res)
164 end
165 end
166
167 if ress != null then
168 assert ress.length > 1
169 var s = new Array[String]
170 for mprop in ress do s.add mprop.full_name
171 self.error(anode, "Error: ambiguous property name `{name}` for `{mtype}`; conflict between {s.join(" and ")}.")
172 end
173
174 self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
175 return res
176 end
177
178 private var try_get_mproperty_by_name2_cache = new HashMap3[MModule, MType, String, nullable MProperty]
179
180
181 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
182 fun try_get_mproperty_by_name(anode: ANode, mclassdef: MClassDef, name: String): nullable MProperty
183 do
184 return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
185 end
186
187 # Helper function to display an error on a node.
188 # Alias for `self.toolcontext.error(n.hot_location, text)`
189 #
190 # This automatically sets `n.is_broken` to true.
191 fun error(n: nullable ANode, text: String)
192 do
193 var l = null
194 if n != null then
195 l = n.hot_location
196 n.is_broken = true
197 end
198 self.toolcontext.error(l, text)
199 end
200
201 # Helper function to display a warning on a node.
202 # Alias for: `self.toolcontext.warning(n.hot_location, text)`
203 fun warning(n: nullable ANode, tag, text: String)
204 do
205 var l = null
206 if n != null then l = n.hot_location
207 self.toolcontext.warning(l, tag, text)
208 end
209
210 # Helper function to display an advice on a node.
211 # Alias for: `self.toolcontext.advice(n.hot_location, text)`
212 fun advice(n: nullable ANode, tag, text: String)
213 do
214 var l = null
215 if n != null then l = n.hot_location
216 self.toolcontext.advice(l, tag, text)
217 end
218
219 # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
220 fun force_get_primitive_method(n: nullable ANode, name: String, recv: MClass, mmodule: MModule): MMethod
221 do
222 var res = mmodule.try_get_primitive_method(name, recv)
223 if res == null then
224 var l = null
225 if n != null then l = n.hot_location
226 self.toolcontext.fatal_error(l, "Fatal Error: `{recv}` must have a property named `{name}`.")
227 abort
228 end
229 return res
230 end
231
232 # Return the static type associated to the node `ntype`.
233 # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
234 # In case of problem, an error is displayed on `ntype` and null is returned.
235 # FIXME: the name "resolve_mtype" is awful
236 fun resolve_mtype_unchecked(mmodule: MModule, mclassdef: nullable MClassDef, ntype: AType, with_virtual: Bool): nullable MType
237 do
238 var name = ntype.n_id.text
239 var res: MType
240
241 # Check virtual type
242 if mclassdef != null and with_virtual then
243 var prop = try_get_mproperty_by_name(ntype, mclassdef, name).as(nullable MVirtualTypeProp)
244 if prop != null then
245 if not ntype.n_types.is_empty then
246 error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
247 end
248 res = prop.mvirtualtype
249 if ntype.n_kwnullable != null then res = res.as_nullable
250 ntype.mtype = res
251 return res
252 end
253 end
254
255 # Check parameter type
256 if mclassdef != null then
257 for p in mclassdef.mclass.mparameters do
258 if p.name != name then continue
259
260 if not ntype.n_types.is_empty then
261 error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
262 end
263
264 res = p
265 if ntype.n_kwnullable != null then res = res.as_nullable
266 ntype.mtype = res
267 return res
268 end
269 end
270
271 # Check class
272 var mclass = try_get_mclass_by_name(ntype, mmodule, name)
273 if mclass != null then
274 var arity = ntype.n_types.length
275 if arity != mclass.arity then
276 if arity == 0 then
277 error(ntype, "Type Error: `{mclass.signature_to_s}` is a generic class.")
278 else if mclass.arity == 0 then
279 error(ntype, "Type Error: `{name}` is not a generic class.")
280 else
281 error(ntype, "Type Error: expected {mclass.arity} formal argument(s) for `{mclass.signature_to_s}`; got {arity}.")
282 end
283 return null
284 end
285 if arity == 0 then
286 res = mclass.mclass_type
287 if ntype.n_kwnullable != null then res = res.as_nullable
288 ntype.mtype = res
289 return res
290 else
291 var mtypes = new Array[MType]
292 for nt in ntype.n_types do
293 var mt = resolve_mtype_unchecked(mmodule, mclassdef, nt, with_virtual)
294 if mt == null then return null # Forward error
295 mtypes.add(mt)
296 end
297 res = mclass.get_mtype(mtypes)
298 if ntype.n_kwnullable != null then res = res.as_nullable
299 ntype.mtype = res
300 return res
301 end
302 end
303
304 # If everything fail, then give up :(
305 error(ntype, "Error: class `{name}` not found in module `{mmodule}`.")
306 return null
307 end
308
309 # Return the static type associated to the node `ntype`.
310 # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
311 # In case of problem, an error is displayed on `ntype` and null is returned.
312 # FIXME: the name "resolve_mtype" is awful
313 fun resolve_mtype(mmodule: MModule, mclassdef: nullable MClassDef, ntype: AType): nullable MType
314 do
315 var mtype = ntype.mtype
316 if mtype == null then mtype = resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
317 if mtype == null then return null # Forward error
318
319 if ntype.checked_mtype then return mtype
320 if mtype isa MGenericType then
321 var mclass = mtype.mclass
322 for i in [0..mclass.arity[ do
323 var intro = mclass.try_intro
324 if intro == null then return null # skip error
325 var bound = intro.bound_mtype.arguments[i]
326 var nt = ntype.n_types[i]
327 var mt = resolve_mtype(mmodule, mclassdef, nt)
328 if mt == null then return null # forward error
329 var anchor
330 if mclassdef != null then anchor = mclassdef.bound_mtype else anchor = null
331 if not check_subtype(nt, mmodule, anchor, mt, bound) then
332 error(nt, "Type Error: expected `{bound}`, got `{mt}`.")
333 return null
334 end
335 end
336 end
337 ntype.checked_mtype = true
338 return mtype
339 end
340
341 # Check that `sub` is a subtype of `sup`.
342 # Do not display an error message.
343 #
344 # This method is used a an entry point for the modelize phase to test static subtypes.
345 # Some refinements could redefine it to collect statictics.
346 fun check_subtype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
347 do
348 return sub.is_subtype(mmodule, anchor, sup)
349 end
350
351 # Check that `sub` and `sup` are equvalent types.
352 # Do not display an error message.
353 #
354 # This method is used a an entry point for the modelize phase to test static equivalent types.
355 # Some refinements could redefine it to collect statictics.
356 fun check_sametype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
357 do
358 return sub.is_subtype(mmodule, anchor, sup) and sup.is_subtype(mmodule, anchor, sub)
359 end
360 end
361
362 redef class ANode
363 # The indication that the node did not pass some semantic verifications.
364 #
365 # This simple flag is set by a given analysis to say that the node is broken and unusable in
366 # an execution.
367 # When a node status is set to broken, it is usually associated with a error message.
368 #
369 # If it is safe to do so, clients of the AST SHOULD just skip broken nodes in their processing.
370 # Clients that do not care about the executability (e.g. metrics) MAY still process the node or
371 # perform specific checks to determinate the validity of the node.
372 #
373 # Note that the broken status is not propagated to parent or children nodes.
374 # e.g. a broken expression used as argument does not make the whole call broken.
375 var is_broken = false is writable
376 end
377
378 redef class AType
379 # The mtype associated to the node
380 var mtype: nullable MType = null
381
382 # Is the mtype a valid one?
383 var checked_mtype: Bool = false
384 end
385
386 redef class AVisibility
387 # The visibility level associated with the AST node class
388 fun mvisibility: MVisibility is abstract
389 end
390 redef class AIntrudeVisibility
391 redef fun mvisibility do return intrude_visibility
392 end
393 redef class APublicVisibility
394 redef fun mvisibility do return public_visibility
395 end
396 redef class AProtectedVisibility
397 redef fun mvisibility do return protected_visibility
398 end
399 redef class APrivateVisibility
400 redef fun mvisibility do return private_visibility
401 end
402
403 redef class ADoc
404 private var mdoc_cache: nullable MDoc
405
406 # Convert `self` to a `MDoc`
407 fun to_mdoc: MDoc
408 do
409 var res = mdoc_cache
410 if res != null then return res
411 res = new MDoc(location)
412 for c in n_comment do
413 var text = c.text
414 if text.length < 2 then
415 res.content.add ""
416 continue
417 end
418 assert text.chars[0] == '#'
419 if text.chars[1] == ' ' then
420 text = text.substring_from(2) # eat starting `#` and space
421 else
422 text = text.substring_from(1) # eat atarting `#` only
423 end
424 if text.chars.last == '\n' then text = text.substring(0, text.length-1) # drop \n
425 res.content.add(text)
426 end
427 mdoc_cache = res
428 return res
429 end
430 end