+
+ # Return the static type associated to the node `ntype`.
+ # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
+ # In case of problem, an error is displayed on `ntype` and null is returned.
+ # FIXME: the name "resolve_mtype" is awful
+ fun resolve_mtype_unchecked(mmodule: MModule, mclassdef: nullable MClassDef, ntype: AType, with_virtual: Bool): nullable MType
+ do
+ var qid = ntype.n_qid
+ var name = qid.n_id.text
+ var res: MType
+
+ # Check virtual type
+ if mclassdef != null and with_virtual then
+ var prop = try_get_mproperty_by_name(ntype, mclassdef, name).as(nullable MVirtualTypeProp)
+ if prop != null then
+ if not ntype.n_types.is_empty then
+ error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
+ end
+ res = prop.mvirtualtype
+ if ntype.n_kwnullable != null then res = res.as_nullable
+ ntype.mtype = res
+ return res
+ end
+ end
+
+ # Check parameter type
+ if mclassdef != null then
+ for p in mclassdef.mclass.mparameters do
+ if p.name != name then continue
+
+ if not ntype.n_types.is_empty then
+ error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
+ end
+
+ res = p
+ if ntype.n_kwnullable != null then res = res.as_nullable
+ ntype.mtype = res
+ return res
+ end
+ end
+
+ # Check class
+ var mclass = try_get_mclass_by_qid(qid, mmodule)
+ if mclass != null then
+ var arity = ntype.n_types.length
+ if arity != mclass.arity then
+ if arity == 0 then
+ error(ntype, "Type Error: `{mclass.signature_to_s}` is a generic class.")
+ else if mclass.arity == 0 then
+ error(ntype, "Type Error: `{name}` is not a generic class.")
+ else
+ error(ntype, "Type Error: expected {mclass.arity} formal argument(s) for `{mclass.signature_to_s}`; got {arity}.")
+ end
+ return null
+ end
+ if arity == 0 then
+ res = mclass.mclass_type
+ if ntype.n_kwnullable != null then res = res.as_nullable
+ ntype.mtype = res
+ return res
+ else
+ var mtypes = new Array[MType]
+ for nt in ntype.n_types do
+ var mt = resolve_mtype_unchecked(mmodule, mclassdef, nt, with_virtual)
+ if mt == null then return null # Forward error
+ mtypes.add(mt)
+ end
+ res = mclass.get_mtype(mtypes)
+ if ntype.n_kwnullable != null then res = res.as_nullable
+ ntype.mtype = res
+ return res
+ end
+ end
+
+ # If everything fail, then give up with class by proposing things.
+ #
+ # TODO Give hints on formal types (param and virtual)
+ class_not_found(qid, mmodule)
+ ntype.is_broken = true
+ return null
+ end
+
+ # Print an error and suggest hints when the class identified by `qid` in `mmodule` is not found.
+ #
+ # This just print error messages.
+ fun class_not_found(qid: AQclassid, mmodule: MModule)
+ do
+ var name = qid.n_id.text
+ var qname = qid.full_name
+
+ var all_classes = model.get_mclasses_by_name(name)
+ var hints = new Array[String]
+
+ # Look for conflicting classes.
+ if all_classes != null then for c in all_classes do
+ if not mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
+ if not qid.accept(c) then continue
+ hints.add "`{c.full_name}`"
+ end
+ if hints.length > 1 then
+ error(qid, "Error: ambiguous class name `{qname}` in module `{mmodule}`. Conflicts are between {hints.join(",", " and ")}.")
+ return
+ end
+ hints.clear
+
+ # Look for imported but invisible classes.
+ if all_classes != null then for c in all_classes do
+ if not mmodule.in_importation <= c.intro_mmodule then continue
+ if mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
+ if not qid.accept(c) then continue
+ error(qid, "Error: class `{c.full_name}` not visible in module `{mmodule}`.")
+ return
+ end
+
+ # Look for not imported but known classes from importable modules
+ if all_classes != null then for c in all_classes do
+ if mmodule.in_importation <= c.intro_mmodule then continue
+ if c.intro_mmodule.in_importation <= mmodule then continue
+ if c.visibility <= private_visibility then continue
+ if not qid.accept(c) then continue
+ hints.add "`{c.intro_mmodule.full_name}`"
+ end
+ if hints.not_empty then
+ error(qid, "Error: class `{qname}` not found in module `{mmodule}`. Maybe import {hints.join(",", " or ")}?")
+ return
+ end
+
+ # Look for classes with an approximative name.
+ var bests = new BestDistance[MClass](qname.length - name.length / 2) # limit up to 50% name change
+ for c in model.mclasses do
+ if not mmodule.in_importation <= c.intro_mmodule then continue
+ if not mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
+ var d = qname.levenshtein_distance(c.name)
+ bests.update(d, c)
+ d = qname.levenshtein_distance(c.full_name)
+ bests.update(d, c)
+ end
+ if bests.best_items.not_empty then
+ for c in bests.best_items do hints.add "`{c.full_name}`"
+ error(qid, "Error: class `{qname}` not found in module `{mmodule}`. Did you mean {hints.join(",", " or ")}?")
+ return
+ end
+
+ error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
+ end
+
+ # Return the static type associated to the node `ntype`.
+ # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
+ # In case of problem, an error is displayed on `ntype` and null is returned.
+ # FIXME: the name "resolve_mtype" is awful
+ fun resolve_mtype(mmodule: MModule, mclassdef: nullable MClassDef, ntype: AType): nullable MType
+ do
+ var mtype = ntype.mtype
+ if mtype == null then mtype = resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
+ if mtype == null then return null # Forward error
+
+ if ntype.checked_mtype then return mtype
+ if mtype isa MGenericType then
+ var mclass = mtype.mclass
+ for i in [0..mclass.arity[ do
+ var intro = mclass.try_intro
+ if intro == null then return null # skip error
+ var bound = intro.bound_mtype.arguments[i]
+ var nt = ntype.n_types[i]
+ var mt = resolve_mtype(mmodule, mclassdef, nt)
+ if mt == null then return null # forward error
+ var anchor
+ if mclassdef != null then anchor = mclassdef.bound_mtype else anchor = null
+ if not check_subtype(nt, mmodule, anchor, mt, bound) then
+ error(nt, "Type Error: expected `{bound}`, got `{mt}`.")
+ return null
+ end
+ end
+ end
+ ntype.checked_mtype = true
+ return mtype
+ end
+
+ # Check that `sub` is a subtype of `sup`.
+ # Do not display an error message.
+ #
+ # This method is used a an entry point for the modelize phase to test static subtypes.
+ # Some refinements could redefine it to collect statictics.
+ fun check_subtype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
+ do
+ return sub.is_subtype(mmodule, anchor, sup)
+ end
+
+ # Check that `sub` and `sup` are equvalent types.
+ # Do not display an error message.
+ #
+ # This method is used a an entry point for the modelize phase to test static equivalent types.
+ # Some refinements could redefine it to collect statictics.
+ fun check_sametype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
+ do
+ return sub.is_subtype(mmodule, anchor, sup) and sup.is_subtype(mmodule, anchor, sub)
+ end
+end
+
+redef class ANode
+ # The indication that the node did not pass some semantic verifications.
+ #
+ # This simple flag is set by a given analysis to say that the node is broken and unusable in
+ # an execution.
+ # When a node status is set to broken, it is usually associated with a error message.
+ #
+ # If it is safe to do so, clients of the AST SHOULD just skip broken nodes in their processing.
+ # Clients that do not care about the executability (e.g. metrics) MAY still process the node or
+ # perform specific checks to determinate the validity of the node.
+ #
+ # Note that the broken status is not propagated to parent or children nodes.
+ # e.g. a broken expression used as argument does not make the whole call broken.
+ var is_broken = false is writable
+end
+
+redef class AType
+ # The mtype associated to the node
+ var mtype: nullable MType = null
+
+ # Is the mtype a valid one?
+ var checked_mtype: Bool = false