return res
end
+ # Return a class identified by `qid` visible by the module `mmodule`.
+ # Visibility in modules and qualified names are correctly handled.
+ #
+ # If more than one class exists, then null is silently returned.
+ # It is up to the caller to post-analysis the result and display a correct error message.
+ # The method `class_not_found` can be used to display such a message.
+ fun try_get_mclass_by_qid(qid: AQclassid, mmodule: MModule): nullable MClass
+ do
+ var name = qid.n_id.text
+
+ var classes = model.get_mclasses_by_name(name)
+ if classes == null then
+ return null
+ end
+
+ var res: nullable MClass = null
+ for mclass in classes do
+ if not mmodule.in_importation <= mclass.intro_mmodule then continue
+ if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
+ if not qid.accept(mclass) then continue
+ if res == null then
+ res = mclass
+ else
+ return null
+ end
+ end
+
+ return res
+ end
+
# Like `try_get_mclass_by_name` but display an error message when the class is not found
fun get_mclass_by_name(node: ANode, mmodule: MModule, name: String): nullable MClass
do
# Helper function to display an error on a node.
# Alias for `self.toolcontext.error(n.hot_location, text)`
+ #
+ # This automatically sets `n.is_broken` to true.
fun error(n: nullable ANode, text: String)
do
var l = null
- if n != null then l = n.hot_location
+ if n != null then
+ l = n.hot_location
+ n.is_broken = true
+ end
self.toolcontext.error(l, text)
end
# 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 name = ntype.n_id.text
+ var qid = ntype.n_qid
+ var name = qid.n_id.text
var res: MType
# Check virtual type
end
# Check class
- var mclass = try_get_mclass_by_name(ntype, mmodule, name)
+ var mclass = try_get_mclass_by_qid(qid, mmodule)
if mclass != null then
var arity = ntype.n_types.length
if arity != mclass.arity then
end
end
- # If everything fail, then give up :(
- error(ntype, "Error: class `{name}` not found in module `{mmodule}`.")
+ # 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
+
+ if bad_class_names[mmodule].has(qname) then
+ error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
+ return
+ end
+ bad_class_names[mmodule].add(qname)
+
+ 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
+
+ # List of already reported bad class names.
+ # Used to not perform and repeat hints again and again.
+ private var bad_class_names = new MultiHashMap[MModule, String]
+
# 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.
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
return res
end
end
+
+redef class AQclassid
+ # The name of the package part, if any
+ fun mpackname: nullable String do
+ var nqualified = n_qualified
+ if nqualified == null then return null
+ var nids = nqualified.n_id
+ if nids.length <= 0 then return null
+ return nids[0].text
+ end
+
+ # The name of the module part, if any
+ fun mmodname: nullable String do
+ var nqualified = n_qualified
+ if nqualified == null then return null
+ var nids = nqualified.n_id
+ if nids.length <= 1 then return null
+ return nids[1].text
+ end
+
+ # Does `mclass` match the full qualified name?
+ fun accept(mclass: MClass): Bool
+ do
+ if mclass.name != n_id.text then return false
+ var mpackname = self.mpackname
+ if mpackname != null then
+ var mpackage = mclass.intro_mmodule.mpackage
+ if mpackage == null then return false
+ if mpackage.name != mpackname then return false
+ var mmodname = self.mmodname
+ if mmodname != null and mclass.intro_mmodule.name != mmodname then return false
+ end
+ return true
+ end
+
+ # The pretty name represented by self.
+ fun full_name: String
+ do
+ var res = n_id.text
+ var nqualified = n_qualified
+ if nqualified == null then return res
+ var ncid = nqualified.n_classid
+ if ncid != null then res = ncid.text + "::" + res
+ var nids = nqualified.n_id
+ if nids.not_empty then for n in nids.reverse_iterator do
+ res = n.text + "::" + res
+ end
+ return res
+ end
+end