X-Git-Url: http://nitlanguage.org diff --git a/src/modelbuilder_base.nit b/src/modelbuilder_base.nit index 6d78f9e..0d75563 100644 --- a/src/modelbuilder_base.nit +++ b/src/modelbuilder_base.nit @@ -60,7 +60,7 @@ class ModelBuilder # If no such a class exists, then null is returned. # If more than one class exists, then an error on `anode` is displayed and null is returned. # FIXME: add a way to handle class name conflict - fun try_get_mclass_by_name(anode: ANode, mmodule: MModule, name: String): nullable MClass + fun try_get_mclass_by_name(anode: nullable ANode, mmodule: MModule, name: String): nullable MClass do var classes = model.get_mclasses_by_name(name) if classes == null then @@ -112,7 +112,7 @@ class ModelBuilder 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 + fun get_mclass_by_name(node: nullable ANode, mmodule: MModule, name: String): nullable MClass do var mclass = try_get_mclass_by_name(node, mmodule, name) if mclass == null then @@ -127,7 +127,7 @@ class ModelBuilder # If no such a property exists, then null is returned. # If more than one property exists, then an error on `anode` is displayed and null is returned. # FIXME: add a way to handle property name conflict - fun try_get_mproperty_by_name2(anode: ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty + fun try_get_mproperty_by_name2(anode: nullable ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty do var props = self.model.get_mproperties_by_name(name) if props == null then @@ -209,7 +209,7 @@ class ModelBuilder # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name) - fun try_get_mproperty_by_name(anode: ANode, mclassdef: MClassDef, name: String): nullable MProperty + fun try_get_mproperty_by_name(anode: nullable ANode, mclassdef: MClassDef, name: String): nullable MProperty do return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name) end @@ -260,18 +260,50 @@ class ModelBuilder 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) + # + # `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 + # + # Same as `resolve_mtype_unchecked3`, but get the context (module, class and + # anchor) from `mclassdef`. + # + # SEE: `resolve_mtype` + # SEE: `resolve_mtype3_unchecked` + # + # FIXME: Find a better name for this method. + fun resolve_mtype_unchecked(mclassdef: MClassDef, ntype: AType, with_virtual: Bool): nullable MType + do + return resolve_mtype3_unchecked( + mclassdef.mmodule, + mclassdef.mclass, + mclassdef.bound_mtype, + ntype, + with_virtual + ) + end + + # Return the static type associated to the node `ntype`. + # + # `mmodule`, `mclass` and `anchor` compose 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. + # + # Note: The “3” is for 3 contextual parameters. + # + # SEE: `resolve_mtype` + # SEE: `resolve_mtype_unchecked` + # + # FIXME: Find a better name for this method. + fun resolve_mtype3_unchecked(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, 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 anchor != null and with_virtual then + var prop = try_get_mproperty_by_name2(ntype, mmodule, anchor, 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.") @@ -284,8 +316,8 @@ class ModelBuilder end # Check parameter type - if mclassdef != null then - for p in mclassdef.mclass.mparameters do + if mclass != null then + for p in mclass.mparameters do if p.name != name then continue if not ntype.n_types.is_empty then @@ -300,32 +332,32 @@ class ModelBuilder end # Check class - var mclass = try_get_mclass_by_qid(qid, mmodule) - if mclass != null then + var found_class = try_get_mclass_by_qid(qid, mmodule) + if found_class != null then var arity = ntype.n_types.length - if arity != mclass.arity then + if arity != found_class.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: `{found_class.signature_to_s}` is a generic class.") + else if found_class.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}.") + error(ntype, "Type Error: expected {found_class.arity} formal argument(s) for `{found_class.signature_to_s}`; got {arity}.") end return null end if arity == 0 then - res = mclass.mclass_type + res = found_class.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) + var mt = resolve_mtype3_unchecked(mmodule, mclass, anchor, nt, with_virtual) if mt == null then return null # Forward error mtypes.add(mt) end - res = mclass.get_mtype(mtypes) + res = found_class.get_mtype(mtypes) if ntype.n_kwnullable != null then res = res.as_nullable ntype.mtype = res return res @@ -346,20 +378,39 @@ class ModelBuilder 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(ntype, "Error: class `{c.full_name}` not visible in module `{mmodule}`.") + error(qid, "Error: class `{c.full_name}` not visible in module `{mmodule}`.") return 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 @@ -368,54 +419,84 @@ class ModelBuilder 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 ")}?") + 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 bestd = name.length / 2 # limit up to 50% name change + 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 = name.levenshtein_distance(c.name) - if d <= bestd then - if d < bestd then - hints.clear - bestd = d - end - hints.add "`{c.full_name}`" - end + var d = qname.levenshtein_distance(c.name) + bests.update(d, c) + d = qname.levenshtein_distance(c.full_name) + bests.update(d, c) end - if hints.not_empty then - error(ntype, "Error: class `{name}` not found in module `{mmodule}`. Did you mean {hints.join(",", " or ")}?") + 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(ntype, "Error: class `{name}` not found in module `{mmodule}`.") + 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) + # + # `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 + # + # Same as `resolve_mtype3`, but get the context (module, class and ) from + # `mclassdef`. + # + # SEE: `resolve_mtype3` + # SEE: `resolve_mtype_unchecked` + # + # FIXME: Find a better name for this method. + fun resolve_mtype(mclassdef: MClassDef, ntype: AType): nullable MType + do + return resolve_mtype3( + mclassdef.mmodule, + mclassdef.mclass, + mclassdef.bound_mtype, + ntype + ) + end + + # Return the static type associated to the node `ntype`. + # + # `mmodule`, `mclass` and `anchor` compose 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. + # + # Note: The “3” is for 3 contextual parameters. + # + # SEE: `resolve_mtype` + # SEE: `resolve_mtype_unchecked` + # + # FIXME: Find a better name for this method. + fun resolve_mtype3(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, 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 mtype = resolve_mtype3_unchecked(mmodule, mclass, anchor, 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 + var found_class = mtype.mclass + for i in [0..found_class.arity[ do + var intro = found_class.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) + var mt = resolve_mtype3(mmodule, mclass, anchor, 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 @@ -461,6 +542,14 @@ redef class ANode # 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 + + redef fun dump_info(v) do + var res = super + if is_broken then + res += v.red("*broken*") + end + return res + end end redef class AType @@ -469,6 +558,15 @@ redef class AType # Is the mtype a valid one? var checked_mtype: Bool = false + + redef fun dump_info(v) do + var res = super + var mtype = self.mtype + if mtype != null then + res += v.yellow(":{mtype}") + end + return res + end end redef class AVisibility @@ -550,4 +648,19 @@ redef class AQclassid 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