X-Git-Url: http://nitlanguage.org diff --git a/src/modelbuilder_base.nit b/src/modelbuilder_base.nit index f01fbd8..5986770 100644 --- a/src/modelbuilder_base.nit +++ b/src/modelbuilder_base.nit @@ -74,13 +74,52 @@ class ModelBuilder if res == null then res = mclass else - error(anode, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}") + error(anode, "Error: ambiguous class name `{name}`; conflict between `{mclass.full_name}` and `{res.full_name}`.") return null end end 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. + 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 + var mclass = try_get_mclass_by_name(node, mmodule, name) + if mclass == null then + error(node, "Type Error: missing primitive class `{name}'.") + end + return mclass + end + # Return a property named `name` on the type `mtype` visible in the module `mmodule`. # Visibility in modules is correctly handled. # Protected properties are returned (it is up to the caller to check and reject protected properties). @@ -158,7 +197,7 @@ class ModelBuilder assert ress.length > 1 var s = new Array[String] for mprop in ress do s.add mprop.full_name - self.error(anode, "Ambigous property name '{name}' for {mtype}; conflict between {s.join(" and ")}") + self.error(anode, "Error: ambiguous property name `{name}` for `{mtype}`; conflict between {s.join(" and ")}.") end self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res @@ -176,10 +215,15 @@ class ModelBuilder # 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 @@ -208,7 +252,7 @@ class ModelBuilder if res == null then var l = null if n != null then l = n.hot_location - self.toolcontext.fatal_error(l, "Fatal Error: {recv} must have a property named {name}.") + self.toolcontext.fatal_error(l, "Fatal Error: `{recv}` must have a property named `{name}`.") abort end return res @@ -220,7 +264,8 @@ class ModelBuilder # 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 @@ -228,7 +273,7 @@ class ModelBuilder 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.") + 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 @@ -243,7 +288,7 @@ class ModelBuilder 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.") + error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.") end res = p @@ -254,16 +299,16 @@ class ModelBuilder 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 if arity == 0 then - error(ntype, "Type error: '{name}' is a generic class.") + 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.") + error(ntype, "Type Error: `{name}` is not a generic class.") else - error(ntype, "Type error: '{name}' has {mclass.arity} parameters ({arity} are provided).") + error(ntype, "Type Error: expected {mclass.arity} formal argument(s) for `{mclass.signature_to_s}`; got {arity}.") end return null end @@ -286,8 +331,56 @@ class ModelBuilder end end - # If everything fail, then give up :( - error(ntype, "Type 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) + # TODO How to move this in a libified autonomous code? + + var all_classes = model.get_mclasses_by_name(name) + + # 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(ntype, "Error: class `{c.full_name}` not visible in module `{mmodule}`.") + return null + end + + # Look for not imported but known classes from importable modules + var hints = new Array[String] + 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(ntype, "Error: class `{name}` not found in module `{mmodule}`. Maybe import {hints.join(",", " or ")}?") + return null + end + + # Look for classes with an approximative name. + var bestd = 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 = name.levenshtein_distance(c.name) + if d <= bestd then + if d < bestd then + hints.clear + bestd = d + end + hints.add "`{c.full_name}`" + end + end + if hints.not_empty then + error(ntype, "Error: class `{name}` not found in module `{mmodule}`. Did you mean {hints.join(",", " or ")}?") + return null + end + + error(ntype, "Error: class `{name}` not found in module `{mmodule}`.") return null end @@ -314,7 +407,7 @@ class ModelBuilder 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}") + error(nt, "Type Error: expected `{bound}`, got `{mt}`.") return null end end @@ -344,6 +437,22 @@ class ModelBuilder 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 @@ -397,3 +506,38 @@ redef class ADoc 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 +end