src: extract modelize_property.nit from modelbuiler.nit
[nit.git] / src / modelbuilder.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
24
25 import parser
26 import model
27 import poset
28 import opts
29 import toolcontext
30 import phase
31
32 ###
33
34 redef class ToolContext
35 # Option --path
36 readable var _opt_path: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
37
38 # Option --only-metamodel
39 readable var _opt_only_metamodel: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
40
41 # Option --only-parse
42 readable var _opt_only_parse: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
43
44 redef init
45 do
46 super
47 option_context.add_option(opt_path, opt_only_parse, opt_only_metamodel)
48 end
49
50 fun modelbuilder: ModelBuilder do return modelbuilder_real.as(not null)
51 private var modelbuilder_real: nullable ModelBuilder = null
52
53 var modelize_class_phase: Phase = new ModelizeClassPhase(self, null)
54 end
55
56 private class ModelizeClassPhase
57 super Phase
58
59 redef fun process_nmodule(nmodule)
60 do
61 toolcontext.modelbuilder.build_classes(nmodule)
62 end
63 end
64
65 # A model builder knows how to load nit source files and build the associated model
66 class ModelBuilder
67 # The model where new modules, classes and properties are added
68 var model: Model
69
70 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
71 var toolcontext: ToolContext
72
73 # Run phases on all loaded modules
74 fun run_phases
75 do
76 var mmodules = model.mmodules.to_a
77 model.mmodule_importation_hierarchy.sort(mmodules)
78 var nmodules = new Array[AModule]
79 for mm in mmodules do
80 nmodules.add(mmodule2nmodule[mm])
81 end
82 toolcontext.run_phases(nmodules)
83 end
84
85 # Instantiate a modelbuilder for a model and a toolcontext
86 # Important, the options of the toolcontext must be correctly set (parse_option already called)
87 init(model: Model, toolcontext: ToolContext)
88 do
89 self.model = model
90 self.toolcontext = toolcontext
91 assert toolcontext.modelbuilder_real == null
92 toolcontext.modelbuilder_real = self
93
94 # Setup the paths value
95 paths.append(toolcontext.opt_path.value)
96
97 var path_env = "NIT_PATH".environ
98 if not path_env.is_empty then
99 paths.append(path_env.split_with(':'))
100 end
101
102 path_env = "NIT_DIR".environ
103 if not path_env.is_empty then
104 var libname = "{path_env}/lib"
105 if libname.file_exists then paths.add(libname)
106 end
107
108 var libname = "{sys.program_name.dirname}/../lib"
109 if libname.file_exists then paths.add(libname.simplify_path)
110 end
111
112 # Load a bunch of modules.
113 # `modules' can contains filenames or module names.
114 # Imported modules are automatically loaded and modelized.
115 # The result is the corresponding model elements.
116 # Errors and warnings are printed with the toolcontext.
117 #
118 # Note: class and property model element are not analysed.
119 fun parse(modules: Sequence[String]): Array[MModule]
120 do
121 var time0 = get_time
122 # Parse and recursively load
123 self.toolcontext.info("*** PARSE ***", 1)
124 var mmodules = new Array[MModule]
125 for a in modules do
126 var nmodule = self.load_module(null, a)
127 if nmodule == null then continue # Skip error
128 mmodules.add(nmodule.mmodule.as(not null))
129 end
130 var time1 = get_time
131 self.toolcontext.info("*** END PARSE: {time1-time0} ***", 2)
132
133 self.toolcontext.check_errors
134 return mmodules
135 end
136
137 # Return a class named `name' visible by the module `mmodule'.
138 # Visibility in modules is correctly handled.
139 # If no such a class exists, then null is returned.
140 # If more than one class exists, then an error on `anode' is displayed and null is returned.
141 # FIXME: add a way to handle class name conflict
142 fun try_get_mclass_by_name(anode: ANode, mmodule: MModule, name: String): nullable MClass
143 do
144 var classes = model.get_mclasses_by_name(name)
145 if classes == null then
146 return null
147 end
148
149 var res: nullable MClass = null
150 for mclass in classes do
151 if not mmodule.in_importation <= mclass.intro_mmodule then continue
152 if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
153 if res == null then
154 res = mclass
155 else
156 error(anode, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
157 return null
158 end
159 end
160 return res
161 end
162
163 # Return a property named `name' on the type `mtype' visible in the module `mmodule'.
164 # Visibility in modules is correctly handled.
165 # Protected properties are returned (it is up to the caller to check and reject protected properties).
166 # If no such a property exists, then null is returned.
167 # If more than one property exists, then an error on `anode' is displayed and null is returned.
168 # FIXME: add a way to handle property name conflict
169 fun try_get_mproperty_by_name2(anode: ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty
170 do
171 var props = self.model.get_mproperties_by_name(name)
172 if props == null then
173 return null
174 end
175
176 var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
177 if cache != null then return cache
178
179 var res: nullable MProperty = null
180 var ress: nullable Array[MProperty] = null
181 for mprop in props do
182 if not mtype.has_mproperty(mmodule, mprop) then continue
183 if not mmodule.is_visible(mprop.intro_mclassdef.mmodule, mprop.visibility) then continue
184 if res == null then
185 res = mprop
186 else
187 var restype = res.intro_mclassdef.bound_mtype
188 var mproptype = mprop.intro_mclassdef.bound_mtype
189 if restype.is_subtype(mmodule, null, mproptype) then
190 # we keep res
191 else if mproptype.is_subtype(mmodule, null, restype) then
192 res = mprop
193 else
194 if ress == null then ress = new Array[MProperty]
195 ress.add(mprop)
196 end
197 end
198 end
199 if ress != null then
200 var restype = res.intro_mclassdef.bound_mtype
201 for mprop in ress do
202 var mproptype = mprop.intro_mclassdef.bound_mtype
203 if not restype.is_subtype(mmodule, null, mproptype) then
204 self.error(anode, "Ambigous property name '{name}' for {mtype}; conflict between {mprop.full_name} and {res.full_name}")
205 return null
206 end
207 end
208 end
209
210 self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
211 return res
212 end
213
214 private var try_get_mproperty_by_name2_cache: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
215
216
217 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
218 fun try_get_mproperty_by_name(anode: ANode, mclassdef: MClassDef, name: String): nullable MProperty
219 do
220 return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
221 end
222
223 # The list of directories to search for top level modules
224 # The list is initially set with :
225 # * the toolcontext --path option
226 # * the NIT_PATH environment variable
227 # * some heuristics including the NIT_DIR environment variable and the progname of the process
228 # Path can be added (or removed) by the client
229 var paths: Array[String] = new Array[String]
230
231 # Get a module by its short name; if required, the module is loaded, parsed and its hierarchies computed.
232 # If `mmodule' is set, then the module search starts from it up to the top level (see `paths');
233 # if `mmodule' is null then the module is searched in the top level only.
234 # If no module exists or there is a name conflict, then an error on `anode' is displayed and null is returned.
235 # FIXME: add a way to handle module name conflict
236 fun get_mmodule_by_name(anode: ANode, mmodule: nullable MModule, name: String): nullable MModule
237 do
238 var origmmodule = mmodule
239 var modules = model.get_mmodules_by_name(name)
240
241 var tries = new Array[String]
242
243 var lastmodule = mmodule
244 while mmodule != null do
245 var dirname = mmodule.location.file.filename.dirname
246
247 # Determine the owner
248 var owner: nullable MModule
249 if dirname.basename("") != mmodule.name then
250 owner = mmodule.direct_owner
251 else
252 owner = mmodule
253 end
254
255 # First, try the already known nested modules
256 if modules != null then
257 for candidate in modules do
258 if candidate.direct_owner == owner then
259 return candidate
260 end
261 end
262 end
263
264 # Second, try the directory to find a file
265 var try_file = dirname + "/" + name + ".nit"
266 tries.add try_file
267 if try_file.file_exists then
268 var res = self.load_module(owner, try_file.simplify_path)
269 if res == null then return null # Forward error
270 return res.mmodule.as(not null)
271 end
272
273 # Third, try if the requested module is itself an owner
274 try_file = dirname + "/" + name + "/" + name + ".nit"
275 if try_file.file_exists then
276 var res = self.load_module(owner, try_file.simplify_path)
277 if res == null then return null # Forward error
278 return res.mmodule.as(not null)
279 end
280
281 lastmodule = mmodule
282 mmodule = mmodule.direct_owner
283 end
284
285 if modules != null then
286 for candidate in modules do
287 if candidate.direct_owner == null then
288 return candidate
289 end
290 end
291 end
292
293 # Look at some known directories
294 var lookpaths = self.paths
295
296 # Look in the directory of the last module also (event if not in the path)
297 if lastmodule != null then
298 var dirname = lastmodule.location.file.filename.dirname
299 if dirname.basename("") == lastmodule.name then
300 dirname = dirname.dirname
301 end
302 if not lookpaths.has(dirname) then
303 lookpaths = lookpaths.to_a
304 lookpaths.add(dirname)
305 end
306 end
307
308 var candidate: nullable String = null
309 for dirname in lookpaths do
310 var try_file = (dirname + "/" + name + ".nit").simplify_path
311 tries.add try_file
312 if try_file.file_exists then
313 if candidate == null then
314 candidate = try_file
315 else if candidate != try_file then
316 error(anode, "Error: conflicting module file for {name}: {candidate} {try_file}")
317 end
318 end
319 try_file = (dirname + "/" + name + "/" + name + ".nit").simplify_path
320 if try_file.file_exists then
321 if candidate == null then
322 candidate = try_file
323 else if candidate != try_file then
324 error(anode, "Error: conflicting module file for {name}: {candidate} {try_file}")
325 end
326 end
327 end
328 if candidate == null then
329 if origmmodule != null then
330 error(anode, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
331 else
332 error(anode, "Error: cannot find module {name}. tried {tries.join(", ")}")
333 end
334 return null
335 end
336 var res = self.load_module(mmodule, candidate)
337 if res == null then return null # Forward error
338 return res.mmodule.as(not null)
339 end
340
341 # Try to load a module using a path.
342 # Display an error if there is a problem (IO / lexer / parser) and return null
343 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
344 fun load_module(owner: nullable MModule, filename: String): nullable AModule
345 do
346 if not filename.file_exists then
347 self.toolcontext.error(null, "Error: file {filename} not found.")
348 return null
349 end
350
351 var x = if owner != null then owner.to_s else "."
352 self.toolcontext.info("load module {filename} in {x}", 2)
353
354 # Load the file
355 var file = new IFStream.open(filename)
356 var lexer = new Lexer(new SourceFile(filename, file))
357 var parser = new Parser(lexer)
358 var tree = parser.parse
359 file.close
360
361 # Handle lexer and parser error
362 var nmodule = tree.n_base
363 if nmodule == null then
364 var neof = tree.n_eof
365 assert neof isa AError
366 error(neof, neof.message)
367 return null
368 end
369
370 # Check the module name
371 var mod_name = filename.basename(".nit")
372 var decl = nmodule.n_moduledecl
373 if decl == null then
374 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
375 else
376 var decl_name = decl.n_name.n_id.text
377 if decl_name != mod_name then
378 error(decl.n_name, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
379 end
380 end
381
382 # Create the module
383 var mmodule = new MModule(model, owner, mod_name, nmodule.location)
384 nmodule.mmodule = mmodule
385 nmodules.add(nmodule)
386 self.mmodule2nmodule[mmodule] = nmodule
387
388 build_module_importation(nmodule)
389
390 return nmodule
391 end
392
393 # Analysis the module importation and fill the module_importation_hierarchy
394 private fun build_module_importation(nmodule: AModule)
395 do
396 if nmodule.is_importation_done then return
397 nmodule.is_importation_done = true
398 var mmodule = nmodule.mmodule.as(not null)
399 var stdimport = true
400 var imported_modules = new Array[MModule]
401 for aimport in nmodule.n_imports do
402 stdimport = false
403 if not aimport isa AStdImport then
404 continue
405 end
406 var mod_name = aimport.n_name.n_id.text
407 var sup = self.get_mmodule_by_name(aimport.n_name, mmodule, mod_name)
408 if sup == null then continue # Skip error
409 imported_modules.add(sup)
410 var mvisibility = aimport.n_visibility.mvisibility
411 mmodule.set_visibility_for(sup, mvisibility)
412 end
413 if stdimport then
414 var mod_name = "standard"
415 var sup = self.get_mmodule_by_name(nmodule, null, mod_name)
416 if sup != null then # Skip error
417 imported_modules.add(sup)
418 mmodule.set_visibility_for(sup, public_visibility)
419 end
420 end
421 self.toolcontext.info("{mmodule} imports {imported_modules.join(", ")}", 3)
422 mmodule.set_imported_mmodules(imported_modules)
423 end
424
425 # All the loaded modules
426 var nmodules: Array[AModule] = new Array[AModule]
427
428 # Visit the AST and create the MClass objects
429 private fun build_a_mclass(nmodule: AModule, nclassdef: AClassdef)
430 do
431 var mmodule = nmodule.mmodule.as(not null)
432
433 var name: String
434 var nkind: nullable AClasskind
435 var mkind: MClassKind
436 var nvisibility: nullable AVisibility
437 var mvisibility: nullable MVisibility
438 var arity = 0
439 if nclassdef isa AStdClassdef then
440 name = nclassdef.n_id.text
441 nkind = nclassdef.n_classkind
442 mkind = nkind.mkind
443 nvisibility = nclassdef.n_visibility
444 mvisibility = nvisibility.mvisibility
445 arity = nclassdef.n_formaldefs.length
446 else if nclassdef isa ATopClassdef then
447 name = "Object"
448 nkind = null
449 mkind = interface_kind
450 nvisibility = null
451 mvisibility = public_visibility
452 else if nclassdef isa AMainClassdef then
453 name = "Sys"
454 nkind = null
455 mkind = concrete_kind
456 nvisibility = null
457 mvisibility = public_visibility
458 else
459 abort
460 end
461
462 var mclass = try_get_mclass_by_name(nclassdef, mmodule, name)
463 if mclass == null then
464 mclass = new MClass(mmodule, name, arity, mkind, mvisibility)
465 #print "new class {mclass}"
466 else if nclassdef isa AStdClassdef and nmodule.mclass2nclassdef.has_key(mclass) then
467 error(nclassdef, "Error: A class {name} is already defined at line {nmodule.mclass2nclassdef[mclass].location.line_start}.")
468 return
469 else if nclassdef isa AStdClassdef and nclassdef.n_kwredef == null then
470 error(nclassdef, "Redef error: {name} is an imported class. Add the redef keyword to refine it.")
471 return
472 else if mclass.arity != arity then
473 error(nclassdef, "Redef error: Formal parameter arity missmatch; got {arity}, expected {mclass.arity}.")
474 return
475 else if nkind != null and mkind != concrete_kind and mclass.kind != mkind then
476 error(nkind, "Error: refinement changed the kind from a {mclass.kind} to a {mkind}")
477 else if nvisibility != null and mvisibility != public_visibility and mclass.visibility != mvisibility then
478 error(nvisibility, "Error: refinement changed the visibility from a {mclass.visibility} to a {mvisibility}")
479 end
480 nclassdef.mclass = mclass
481 nmodule.mclass2nclassdef[mclass] = nclassdef
482 end
483
484 # Visit the AST and create the MClassDef objects
485 private fun build_a_mclassdef(nmodule: AModule, nclassdef: AClassdef)
486 do
487 var mmodule = nmodule.mmodule.as(not null)
488 var objectclass = try_get_mclass_by_name(nmodule, mmodule, "Object")
489 var mclass = nclassdef.mclass
490 if mclass == null then return # Skip error
491 #var mclassdef = nclassdef.mclassdef.as(not null)
492
493 var names = new Array[String]
494 var bounds = new Array[MType]
495 if nclassdef isa AStdClassdef and mclass.arity > 0 then
496 # Collect formal parameter names
497 for i in [0..mclass.arity[ do
498 var nfd = nclassdef.n_formaldefs[i]
499 var ptname = nfd.n_id.text
500 if names.has(ptname) then
501 error(nfd, "Error: A formal parameter type `{ptname}' already exists")
502 return
503 end
504 names.add(ptname)
505 end
506
507 # Revolve bound for formal parameter names
508 for i in [0..mclass.arity[ do
509 var nfd = nclassdef.n_formaldefs[i]
510 var nfdt = nfd.n_type
511 if nfdt != null then
512 var bound = resolve_mtype_unchecked(nclassdef, nfdt, false)
513 if bound == null then return # Forward error
514 if bound.need_anchor then
515 # No F-bounds!
516 error(nfd, "Error: Formal parameter type `{names[i]}' bounded with a formal parameter type")
517 else
518 bounds.add(bound)
519 end
520 else if mclass.mclassdefs.is_empty then
521 # No bound, then implicitely bound by nullable Object
522 bounds.add(objectclass.mclass_type.as_nullable)
523 else
524 # Inherit the bound
525 bounds.add(mclass.intro.bound_mtype.arguments[i])
526 end
527 end
528 end
529
530 var bound_mtype = mclass.get_mtype(bounds)
531 var mclassdef = new MClassDef(mmodule, bound_mtype, nclassdef.location, names)
532 nclassdef.mclassdef = mclassdef
533 self.mclassdef2nclassdef[mclassdef] = nclassdef
534
535 if mclassdef.is_intro then
536 self.toolcontext.info("{mclassdef} introduces new {mclass.kind} {mclass.full_name}", 3)
537 else
538 self.toolcontext.info("{mclassdef} refine {mclass.kind} {mclass.full_name}", 3)
539 end
540 end
541
542 # Visit the AST and set the super-types of the MClassdef objects
543 private fun collect_a_mclassdef_inheritance(nmodule: AModule, nclassdef: AClassdef)
544 do
545 var mmodule = nmodule.mmodule.as(not null)
546 var objectclass = try_get_mclass_by_name(nmodule, mmodule, "Object")
547 var mclass = nclassdef.mclass.as(not null)
548 var mclassdef = nclassdef.mclassdef.as(not null)
549
550 var specobject = true
551 var supertypes = new Array[MClassType]
552 if nclassdef isa AStdClassdef then
553 for nsc in nclassdef.n_superclasses do
554 specobject = false
555 var ntype = nsc.n_type
556 var mtype = resolve_mtype_unchecked(nclassdef, ntype, false)
557 if mtype == null then continue # Skip because of error
558 if not mtype isa MClassType then
559 error(ntype, "Error: supertypes cannot be a formal type")
560 return
561 end
562 supertypes.add mtype
563 #print "new super : {mclass} < {mtype}"
564 end
565 end
566 if specobject and mclass.name != "Object" and objectclass != null and mclassdef.is_intro then
567 supertypes.add objectclass.mclass_type
568 end
569
570 mclassdef.set_supertypes(supertypes)
571 if not supertypes.is_empty then self.toolcontext.info("{mclassdef} new super-types: {supertypes.join(", ")}", 3)
572 end
573
574 # Check the validity of the specialization heirarchy
575 private fun check_supertypes(nmodule: AModule, nclassdef: AClassdef)
576 do
577 var mmodule = nmodule.mmodule.as(not null)
578 var objectclass = try_get_mclass_by_name(nmodule, mmodule, "Object")
579 var mclass = nclassdef.mclass.as(not null)
580 var mclassdef = nclassdef.mclassdef.as(not null)
581
582 for s in mclassdef.supertypes do
583 if s.is_subtype(mmodule, mclassdef.bound_mtype, mclassdef.bound_mtype) then
584 error(nclassdef, "Error: Inheritance loop for class {mclass} with type {s}")
585 end
586 end
587 end
588
589 # Build the classes of the module `nmodule'.
590 # REQUIRE: classes of imported modules are already build. (let `phase' do the job)
591 private fun build_classes(nmodule: AModule)
592 do
593 # Force building recursively
594 if nmodule.build_classes_is_done then return
595 nmodule.build_classes_is_done = true
596 var mmodule = nmodule.mmodule.as(not null)
597 for imp in mmodule.in_importation.direct_greaters do
598
599 build_classes(mmodule2nmodule[imp])
600 end
601
602 # Create all classes
603 for nclassdef in nmodule.n_classdefs do
604 self.build_a_mclass(nmodule, nclassdef)
605 end
606
607 # Create all classdefs
608 for nclassdef in nmodule.n_classdefs do
609 self.build_a_mclassdef(nmodule, nclassdef)
610 end
611
612 for nclassdef in nmodule.n_classdefs do
613 if nclassdef.mclassdef == null then return # forward error
614 end
615
616 # Create inheritance on all classdefs
617 for nclassdef in nmodule.n_classdefs do
618 self.collect_a_mclassdef_inheritance(nmodule, nclassdef)
619 end
620
621 # Create the mclassdef hierarchy
622 for nclassdef in nmodule.n_classdefs do
623 var mclassdef = nclassdef.mclassdef.as(not null)
624 mclassdef.add_in_hierarchy
625 end
626
627 # Check inheritance
628 for nclassdef in nmodule.n_classdefs do
629 self.check_supertypes(nmodule, nclassdef)
630 end
631
632 # Check unchecked ntypes
633 for nclassdef in nmodule.n_classdefs do
634 if nclassdef isa AStdClassdef then
635 # check bound of formal parameter
636 for nfd in nclassdef.n_formaldefs do
637 var nfdt = nfd.n_type
638 if nfdt != null and nfdt.mtype != null then
639 var bound = resolve_mtype(nclassdef, nfdt)
640 if bound == null then return # Forward error
641 end
642 end
643 # check declared super types
644 for nsc in nclassdef.n_superclasses do
645 var ntype = nsc.n_type
646 if ntype.mtype != null then
647 var mtype = resolve_mtype(nclassdef, ntype)
648 if mtype == null then return # Forward error
649 end
650 end
651 end
652
653 end
654
655 # TODO: Check that the super-class is not intrusive
656
657 # TODO: Check that the super-class is not already known (by transitivity)
658 end
659
660 # Register the nmodule associated to each mmodule
661 # FIXME: why not refine the MModule class with a nullable attribute?
662 var mmodule2nmodule: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
663 # Register the nclassdef associated to each mclassdef
664 # FIXME: why not refine the MClassDef class with a nullable attribute?
665 var mclassdef2nclassdef: HashMap[MClassDef, AClassdef] = new HashMap[MClassDef, AClassdef]
666
667 # Return the static type associated to the node `ntype'.
668 # `classdef' is the context where the call is made (used to understand formal types)
669 # The mmodule used as context is `nclassdef.mmodule'
670 # In case of problem, an error is displayed on `ntype' and null is returned.
671 # FIXME: the name "resolve_mtype" is awful
672 fun resolve_mtype_unchecked(nclassdef: AClassdef, ntype: AType, with_virtual: Bool): nullable MType
673 do
674 var name = ntype.n_id.text
675 var mclassdef = nclassdef.mclassdef
676 var mmodule = nclassdef.parent.as(AModule).mmodule.as(not null)
677 var res: MType
678
679 # Check virtual type
680 if mclassdef != null and with_virtual then
681 var prop = try_get_mproperty_by_name(ntype, mclassdef, name).as(nullable MVirtualTypeProp)
682 if prop != null then
683 if not ntype.n_types.is_empty then
684 error(ntype, "Type error: formal type {name} cannot have formal parameters.")
685 end
686 res = prop.mvirtualtype
687 if ntype.n_kwnullable != null then res = res.as_nullable
688 ntype.mtype = res
689 return res
690 end
691 end
692
693 # Check parameter type
694 if mclassdef != null and mclassdef.parameter_names.has(name) then
695 if not ntype.n_types.is_empty then
696 error(ntype, "Type error: formal type {name} cannot have formal parameters.")
697 end
698 for i in [0..mclassdef.parameter_names.length[ do
699 if mclassdef.parameter_names[i] == name then
700 res = mclassdef.mclass.mclass_type.arguments[i]
701 if ntype.n_kwnullable != null then res = res.as_nullable
702 ntype.mtype = res
703 return res
704 end
705 end
706 abort
707 end
708
709 # Check class
710 var mclass = try_get_mclass_by_name(ntype, mmodule, name)
711 if mclass != null then
712 var arity = ntype.n_types.length
713 if arity != mclass.arity then
714 if arity == 0 then
715 error(ntype, "Type error: '{name}' is a generic class.")
716 else if mclass.arity == 0 then
717 error(ntype, "Type error: '{name}' is not a generic class.")
718 else
719 error(ntype, "Type error: '{name}' has {mclass.arity} parameters ({arity} are provided).")
720 end
721 return null
722 end
723 if arity == 0 then
724 res = mclass.mclass_type
725 if ntype.n_kwnullable != null then res = res.as_nullable
726 ntype.mtype = res
727 return res
728 else
729 var mtypes = new Array[MType]
730 for nt in ntype.n_types do
731 var mt = resolve_mtype_unchecked(nclassdef, nt, with_virtual)
732 if mt == null then return null # Forward error
733 mtypes.add(mt)
734 end
735 res = mclass.get_mtype(mtypes)
736 if ntype.n_kwnullable != null then res = res.as_nullable
737 ntype.mtype = res
738 return res
739 end
740 end
741
742 # If everything fail, then give up :(
743 error(ntype, "Type error: class {name} not found in module {mmodule}.")
744 return null
745 end
746
747 # Return the static type associated to the node `ntype'.
748 # `classdef' is the context where the call is made (used to understand formal types)
749 # The mmodule used as context is `nclassdef.mmodule'
750 # In case of problem, an error is displayed on `ntype' and null is returned.
751 # FIXME: the name "resolve_mtype" is awful
752 fun resolve_mtype(nclassdef: AClassdef, ntype: AType): nullable MType
753 do
754 var mtype = ntype.mtype
755 if mtype == null then mtype = resolve_mtype_unchecked(nclassdef, ntype, true)
756 if mtype == null then return null # Forward error
757
758 if ntype.checked_mtype then return mtype
759 if mtype isa MGenericType then
760 var mmodule = nclassdef.parent.as(AModule).mmodule.as(not null)
761 var mclassdef = nclassdef.mclassdef
762 var mclass = mtype.mclass
763 for i in [0..mclass.arity[ do
764 var bound = mclass.intro.bound_mtype.arguments[i]
765 var nt = ntype.n_types[i]
766 var mt = resolve_mtype(nclassdef, nt)
767 if mt == null then return null # forward error
768 if not mt.is_subtype(mmodule, mclassdef.bound_mtype, bound) then
769 error(nt, "Type error: expected {bound}, got {mt}")
770 return null
771 end
772 end
773 end
774 ntype.checked_mtype = true
775 return mtype
776 end
777
778 # Helper function to display an error on a node.
779 # Alias for `self.toolcontext.error(n.hot_location, text)'
780 fun error(n: ANode, text: String)
781 do
782 self.toolcontext.error(n.hot_location, text)
783 end
784
785 # Helper function to display a warning on a node.
786 # Alias for: `self.toolcontext.warning(n.hot_location, text)'
787 fun warning(n: ANode, text: String)
788 do
789 self.toolcontext.warning(n.hot_location, text)
790 end
791
792 # Force to get the primitive method named `name' on the type `recv' or do a fatal error on `n'
793 fun force_get_primitive_method(n: ANode, name: String, recv: MType, mmodule: MModule): MMethod
794 do
795 var res = mmodule.try_get_primitive_method(name, recv)
796 if res == null then
797 self.toolcontext.fatal_error(n.hot_location, "Fatal Error: {recv} must have a property named {name}.")
798 abort
799 end
800 return res
801 end
802 end
803
804 redef class AModule
805 # The associated MModule once build by a `ModelBuilder'
806 var mmodule: nullable MModule
807 # Flag that indicate if the importation is already completed
808 var is_importation_done: Bool = false
809 # Flag that indicate if the class and prop building is already completed
810 var build_classes_is_done: Bool = false
811 # What is the AClassdef associated to a MClass?
812 # Used to check multiple definition of a class.
813 var mclass2nclassdef: Map[MClass, AClassdef] = new HashMap[MClass, AClassdef]
814
815 end
816
817 redef class AClassdef
818 # The associated MClass once build by a `ModelBuilder'
819 var mclass: nullable MClass
820 # The associated MClassDef once build by a `ModelBuilder'
821 var mclassdef: nullable MClassDef
822 end
823
824 redef class AClasskind
825 # The class kind associated with the AST node class
826 private fun mkind: MClassKind is abstract
827 end
828 redef class AConcreteClasskind
829 redef fun mkind do return concrete_kind
830 end
831 redef class AAbstractClasskind
832 redef fun mkind do return abstract_kind
833 end
834 redef class AInterfaceClasskind
835 redef fun mkind do return interface_kind
836 end
837 redef class AEnumClasskind
838 redef fun mkind do return enum_kind
839 end
840 redef class AExternClasskind
841 redef fun mkind do return extern_kind
842 end
843
844 redef class AVisibility
845 # The visibility level associated with the AST node class
846 fun mvisibility: MVisibility is abstract
847 end
848 redef class AIntrudeVisibility
849 redef fun mvisibility do return intrude_visibility
850 end
851 redef class APublicVisibility
852 redef fun mvisibility do return public_visibility
853 end
854 redef class AProtectedVisibility
855 redef fun mvisibility do return protected_visibility
856 end
857 redef class APrivateVisibility
858 redef fun mvisibility do return private_visibility
859 end
860
861 redef class AType
862 # The mtype associated to the node
863 var mtype: nullable MType = null
864
865 # Is the mtype a valid one?
866 var checked_mtype: Bool = false
867 end