X-Git-Url: http://nitlanguage.org diff --git a/src/modelize/modelize_class.nit b/src/modelize/modelize_class.nit index ae9da02..853d8ff 100644 --- a/src/modelize/modelize_class.nit +++ b/src/modelize/modelize_class.nit @@ -20,6 +20,7 @@ module modelize_class import modelbuilder redef class ToolContext + # Run `AModule::build_classes` on each module var modelize_class_phase: Phase = new ModelizeClassPhase(self, null) end @@ -45,8 +46,11 @@ redef class ModelBuilder var mvisibility: nullable MVisibility var arity = 0 var names = new Array[String] + var mclass if nclassdef isa AStdClassdef then - name = nclassdef.n_id.text + var qid = nclassdef.n_qid + assert qid != null + name = qid.n_id.text nkind = nclassdef.n_classkind mkind = nkind.mkind nvisibility = nclassdef.n_visibility @@ -64,53 +68,73 @@ redef class ModelBuilder var nfd = nclassdef.n_formaldefs[i] var ptname = nfd.n_id.text if names.has(ptname) then - error(nfd, "Error: A formal parameter type `{ptname}' already exists") + error(nfd, "Error: a formal parameter type `{ptname}` already exists.") return end for c in ptname.chars do if c >= 'a' and c<= 'z' then - warning(nfd, "formal-type-name", "Warning: lowercase in the formal parameter type {ptname}") + warning(nfd, "formal-type-name", "Warning: lowercase in the formal parameter type `{ptname}`.") break end names.add(ptname) end + mclass = try_get_mclass_by_qid(qid, mmodule) + if mclass == null and (qid.n_qualified != null or nclassdef.n_kwredef != null) then + class_not_found(qid, mmodule) + nclassdef.is_broken = true + return + end - else if nclassdef isa ATopClassdef then + else if nclassdef isa ATopClassdef and nclassdef.n_propdefs.first.as(AMethPropdef).n_methid.collect_text == "sys" then + # Special case to keep `sys` in object. + # Needed to keep working bootstrap and a working java FFI together. + # TODO: remove once safe to remove name = "Object" nkind = null mkind = interface_kind nvisibility = null mvisibility = public_visibility - else if nclassdef isa AMainClassdef then + mclass = try_get_mclass_by_name(nclassdef, mmodule, name) + else name = "Sys" nkind = null mkind = concrete_kind nvisibility = null mvisibility = public_visibility - else - abort + mclass = try_get_mclass_by_name(nclassdef, mmodule, name) end - var mclass = try_get_mclass_by_name(nclassdef, mmodule, name) if mclass == null then - if nclassdef isa AStdClassdef and nclassdef.n_kwredef != null then - error(nclassdef, "Redef error: No imported class {name} to refine.") - return + # Check for conflicting class full-names in the package + if mmodule.mgroup != null and mvisibility >= protected_visibility then + var mclasses = model.get_mclasses_by_name(name) + if mclasses != null then for other in mclasses do + if other.intro_mmodule.mgroup != null and other.intro_mmodule.mgroup.mpackage == mmodule.mgroup.mpackage then + # Skip classes that are buggy + if other.try_intro == null then continue + warning(nclassdef, "full-name-conflict", "Error: a class named `{other.full_name}` is already defined in module `{other.intro_mmodule}` at {other.intro.location}.") + break + end + end end - mclass = new MClass(mmodule, name, names, mkind, mvisibility) + + mclass = new MClass(mmodule, name, nclassdef.location, names, mkind, mvisibility) #print "new class {mclass}" else if nclassdef isa AStdClassdef and nmodule.mclass2nclassdef.has_key(mclass) then - error(nclassdef, "Error: A class {name} is already defined at line {nmodule.mclass2nclassdef[mclass].location.line_start}.") + error(nclassdef, "Error: a class `{name}` is already defined at line {nmodule.mclass2nclassdef[mclass].location.line_start}.") + mclass.is_broken = true return else if nclassdef isa AStdClassdef and nclassdef.n_kwredef == null then - error(nclassdef, "Redef error: {name} is an imported class. Add the redef keyword to refine it.") + error(nclassdef, "Redef Error: `{name}` is an imported class. Add the `redef` keyword to refine it.") + mclass.is_broken = true return else if arity != 0 and mclass.arity != arity then - error(nclassdef, "Redef error: Formal parameter arity missmatch; got {arity}, expected {mclass.arity}.") + error(nclassdef, "Redef Error: expected {mclass.arity} formal parameter(s) for {mclass.signature_to_s}; got {arity}.") + mclass.is_broken = true return else if nkind != null and mkind != concrete_kind and mclass.kind != mkind then - error(nkind, "Error: refinement changed the kind from a {mclass.kind} to a {mkind}") + error(nkind, "Redef Error: refinement changed the kind from `{mclass.kind}` to `{mkind}`.") else if nvisibility != null and mvisibility != public_visibility and mclass.visibility != mvisibility then - error(nvisibility, "Error: refinement changed the visibility from a {mclass.visibility} to a {mvisibility}") + error(nvisibility, "Redef Error: refinement changed the visibility from `{mclass.visibility}` to `{mvisibility}`") end nclassdef.mclass = mclass if not nmodule.mclass2nclassdef.has_key(mclass) then @@ -125,7 +149,6 @@ redef class ModelBuilder private fun build_a_mclassdef(nmodule: AModule, nclassdef: AClassdef) do var mmodule = nmodule.mmodule.as(not null) - var objectclass = try_get_mclass_by_name(nmodule, mmodule, "Object") var mclass = nclassdef.mclass if mclass == null then return # Skip error @@ -137,8 +160,45 @@ redef class ModelBuilder return end + var bound_mtype = build_a_bound_mtype(nmodule, nclassdef) + if bound_mtype == null then return + var mclassdef = new MClassDef(mmodule, bound_mtype, nclassdef.location) + nclassdef.mclassdef = mclassdef + self.mclassdef2nclassdef[mclassdef] = nclassdef + + if nclassdef isa AStdClassdef then + var ndoc = nclassdef.n_doc + if ndoc != null then + var mdoc = ndoc.to_mdoc + mclassdef.mdoc = mdoc + mdoc.original_mentity = mclassdef + else if mclassdef.is_intro and mclass.visibility >= public_visibility then + advice(nclassdef, "missing-doc", "Documentation warning: Undocumented public class `{mclass}`") + end + end + + if mclassdef.is_intro then + self.toolcontext.info("{mclassdef} introduces new {mclass.kind} {mclass.full_name}", 3) + else + self.toolcontext.info("{mclassdef} refines {mclass.kind} {mclass.full_name}", 3) + end + end + + # Determine the type parameter bounds for `nclassdef`. + # + # In case of error, return `null`. + # + # REQUIRE: `nmodule.mmodule != null` + # REQUIRE: `nclassdef.mclass != null` + private fun build_a_bound_mtype(nmodule: AModule, nclassdef: AClassdef): nullable MClassType + do + var mmodule = nmodule.mmodule.as(not null) + var mclass = nclassdef.mclass.as(not null) + var bounds = new Array[MType] if nclassdef isa AStdClassdef and mclass.arity > 0 then + var objectclass = try_get_mclass_by_name(nmodule, mmodule, "Object") + # Revolve bound for formal parameters for i in [0..mclass.arity[ do if nclassdef.n_formaldefs.is_empty then @@ -151,26 +211,23 @@ redef class ModelBuilder var nfd = nclassdef.n_formaldefs[i] var pname = mclass.mparameters[i].name if nfd.n_id.text != pname then - error(nfd.n_id, "Error: Formal parameter type #{i} `{nfd.n_id.text}` must be named `{pname}' as in the original definition in module `{mclass.intro.mmodule}`.") + error(nfd.n_id, "Error: formal parameter type #{i} `{nfd.n_id.text}` must be named `{pname}` as in the original definition in module `{mclass.intro.mmodule}`.") end var nfdt = nfd.n_type if nfdt != null then - var bound = resolve_mtype_unchecked(mmodule, null, nfdt, false) - if bound == null then return # Forward error + var bound = resolve_mtype3_unchecked(mmodule, null, null, nfdt, false) + if bound == null then return null # Forward error if bound.need_anchor then # No F-bounds! - error(nfd, "Error: Formal parameter type `{pname}' bounded with a formal parameter type") + error(nfd, "Error: formal parameter type `{pname}` bounded with a formal parameter type.") else bounds.add(bound) nfd.bound = bound end - if bound isa MClassType and bound.mclass.kind == enum_kind then - warning(nfdt, "useless-bound", "Warning: Useless formal parameter type since `{bound}` cannnot have subclasses.") - end else if mclass.mclassdefs.is_empty then if objectclass == null then - error(nfd, "Error: Formal parameter type `{pname}' unbounded but no Object class exist.") - return + error(nfd, "Error: formal parameter type `{pname}` unbounded but no `Object` class exists.") + return null end # No bound, then implicitely bound by nullable Object var bound = objectclass.mclass_type.as_nullable @@ -185,37 +242,38 @@ redef class ModelBuilder end end - var bound_mtype = mclass.get_mtype(bounds) - var mclassdef = new MClassDef(mmodule, bound_mtype, nclassdef.location) - nclassdef.mclassdef = mclassdef - self.mclassdef2nclassdef[mclassdef] = nclassdef + return mclass.get_mtype(bounds) + end - if nclassdef isa AStdClassdef then - var ndoc = nclassdef.n_doc - if ndoc != null then - var mdoc = ndoc.to_mdoc - mclassdef.mdoc = mdoc - mdoc.original_mentity = mclassdef - else if mclassdef.is_intro and mclass.visibility >= public_visibility then - advice(nclassdef, "missing-doc", "Documentation warning: Undocumented public class `{mclass}`") - end - end + # Visit the AST and set the super-types of the `MClassDef` objects + private fun build_a_mclassdef_inheritance(nmodule: AModule, nclassdef: AClassdef) + do + var mmodule = nmodule.mmodule + if mmodule == null then return + var mclass = nclassdef.mclass + if mclass == null then return + var mclassdef = nclassdef.mclassdef + if mclassdef == null then return - if mclassdef.is_intro then - self.toolcontext.info("{mclassdef} introduces new {mclass.kind} {mclass.full_name}", 3) - else - self.toolcontext.info("{mclassdef} refine {mclass.kind} {mclass.full_name}", 3) - end + var supertypes = collect_supertypes(nmodule, nclassdef, mclassdef.is_intro) + mclassdef.set_supertypes(supertypes) + if not supertypes.is_empty then self.toolcontext.info("{mclassdef} new super-types: {supertypes.join(", ")}", 3) end - # Visit the AST and set the super-types of the `MClassDef` objects - private fun collect_a_mclassdef_inheritance(nmodule: AModule, nclassdef: AClassdef) + # List the supertypes specified or implied by `nclassdef`. + # + # REQUIRE: `nmodule.mmodule != null` + # REQUIRE: `nclassdef.mclass != null` + private fun collect_supertypes(nmodule: AModule, nclassdef: AClassdef, + is_intro: Bool): Array[MClassType] do var mmodule = nmodule.mmodule.as(not null) + var mclass = nclassdef.mclass.as(not null) + var name = mclass.name + var kind = mclass.kind + var objectclass = try_get_mclass_by_name(nmodule, mmodule, "Object") var pointerclass = try_get_mclass_by_name(nmodule, mmodule, "Pointer") - var mclass = nclassdef.mclass.as(not null) - var mclassdef = nclassdef.mclassdef.as(not null) # Do we need to specify Object as a super class? var specobject = true @@ -229,110 +287,130 @@ redef class ModelBuilder for nsc in nclassdef.n_superclasses do specobject = false var ntype = nsc.n_type - var mtype = resolve_mtype_unchecked(mmodule, mclassdef, ntype, false) + var mtype = resolve_mtype3_unchecked(mmodule, mclass, null, + ntype, false) if mtype == null then continue # Skip because of error if not mtype isa MClassType then - error(ntype, "Error: supertypes cannot be a formal type") - return + error(ntype, "Error: a supertype cannot be a formal type.") + continue end - if not mclass.kind.can_specialize(mtype.mclass.kind) then - error(ntype, "Error: {mclass.kind} {mclass} cannot specialize {mtype.mclass.kind} {mtype.mclass}") + var superclass = mtype.mclass + var super_kind = superclass.kind + if not kind.can_specialize(super_kind) then + error(ntype, "Error: {kind} `{mclass}` cannot specialize {super_kind} `{superclass}`.") end supertypes.add mtype #print "new super : {mclass} < {mtype}" - if mtype.mclass.kind == extern_kind then specpointer = false + if super_kind == extern_kind then specpointer = false end end - if mclassdef.is_intro and objectclass != null then - if mclass.kind == extern_kind and mclass.name != "Pointer" then + if is_intro and objectclass != null then + if kind == extern_kind and name != "Pointer" then # it is an extern class, but not a Pointer + if pointerclass == null then + error(nclassdef, "Error: `Pointer` must be defined first.") + return supertypes + end if specpointer then supertypes.add pointerclass.mclass_type - else if specobject and mclass.name != "Object" then - # it is a standard class without super class (but is not Object) - supertypes.add objectclass.mclass_type + else if specobject then + if name != "Object" then + # it is a standard class without super class (but is not Object) + supertypes.add objectclass.mclass_type + else if kind != interface_kind then + error(nclassdef, "Error: `Object` must be an {interface_kind}.") + end end end - mclassdef.set_supertypes(supertypes) - if not supertypes.is_empty then self.toolcontext.info("{mclassdef} new super-types: {supertypes.join(", ")}", 3) + return supertypes end # Check the validity of the specialization heirarchy private fun check_supertypes(nmodule: AModule, nclassdef: AClassdef) do - var mmodule = nmodule.mmodule.as(not null) - var mclass = nclassdef.mclass.as(not null) - var mclassdef = nclassdef.mclassdef.as(not null) + var mmodule = nmodule.mmodule + if mmodule == null then return + var mclass = nclassdef.mclass + if mclass == null then return + var mclassdef = nclassdef.mclassdef + if mclassdef == null then return for s in mclassdef.supertypes do if s.is_subtype(mmodule, mclassdef.bound_mtype, mclassdef.bound_mtype) then - error(nclassdef, "Error: Inheritance loop for class {mclass} with type {s}") + error(nclassdef, "Error: inheritance loop for class `{mclass}` with type `{s}`.") end end end # Build the classes of the module `nmodule`. - # REQUIRE: classes of imported modules are already build. (let `phase` do the job) private fun build_classes(nmodule: AModule) do - var errcount = toolcontext.error_count # Force building recursively if nmodule.build_classes_is_done then return nmodule.build_classes_is_done = true - var mmodule = nmodule.mmodule.as(not null) + var mmodule = nmodule.mmodule + if mmodule == null then return for imp in mmodule.in_importation.direct_greaters do - - if not mmodule2nmodule.has_key(imp) then continue - build_classes(mmodule2nmodule[imp]) + var nimp = mmodule2node(imp) + if nimp != null then build_classes(nimp) end - if errcount != toolcontext.error_count then return - # Create all classes + # process AStdClassdef before so that non-AStdClassdef classes can be attached to existing ones, if any for nclassdef in nmodule.n_classdefs do + if not nclassdef isa AStdClassdef then continue + self.build_a_mclass(nmodule, nclassdef) + end + for nclassdef in nmodule.n_classdefs do + if nclassdef isa AStdClassdef then continue self.build_a_mclass(nmodule, nclassdef) end - - if errcount != toolcontext.error_count then return # Create all classdefs for nclassdef in nmodule.n_classdefs do + if not nclassdef isa AStdClassdef then continue + self.build_a_mclassdef(nmodule, nclassdef) + end + for nclassdef in nmodule.n_classdefs do + if nclassdef isa AStdClassdef then continue self.build_a_mclassdef(nmodule, nclassdef) end - - if errcount != toolcontext.error_count then return # Create inheritance on all classdefs for nclassdef in nmodule.n_classdefs do - self.collect_a_mclassdef_inheritance(nmodule, nclassdef) + self.build_a_mclassdef_inheritance(nmodule, nclassdef) end - if errcount != toolcontext.error_count then return - # Create the mclassdef hierarchy for mclassdef in mmodule.mclassdefs do mclassdef.add_in_hierarchy end - if errcount != toolcontext.error_count then return - # Check inheritance for nclassdef in nmodule.n_classdefs do self.check_supertypes(nmodule, nclassdef) end - if errcount != toolcontext.error_count then return - # Check unchecked ntypes for nclassdef in nmodule.n_classdefs do if nclassdef isa AStdClassdef then var mclassdef = nclassdef.mclassdef + var mclass + var anchor + if mclassdef == null then + mclass = null + anchor = null + else + mclass = mclassdef.mclass + anchor = mclassdef.bound_mtype + end + # check bound of formal parameter - for nfd in nclassdef.n_formaldefs do + for nfd in nclassdef.n_formaldefs do var nfdt = nfd.n_type if nfdt != null and nfdt.mtype != null then - var bound = resolve_mtype(mmodule, mclassdef, nfdt) + var bound = resolve_mtype3(mmodule, mclass, anchor, nfdt) if bound == null then return # Forward error end end @@ -340,18 +418,17 @@ redef class ModelBuilder for nsc in nclassdef.n_superclasses do var ntype = nsc.n_type if ntype.mtype != null then - var mtype = resolve_mtype(mmodule, mclassdef, ntype) + var mtype = resolve_mtype3(mmodule, mclass, anchor, ntype) if mtype == null then return # Forward error end end end end - if errcount != toolcontext.error_count then return - # Check clash of ancestors for nclassdef in nmodule.n_classdefs do - var mclassdef = nclassdef.mclassdef.as(not null) + var mclassdef = nclassdef.mclassdef + if mclassdef == null then continue var superclasses = new HashMap[MClass, MClassType] for scd in mclassdef.in_hierarchy.greaters do for st in scd.supertypes do @@ -361,21 +438,20 @@ redef class ModelBuilder var st1 = superclasses[st.mclass].resolve_for(mclassdef.mclass.mclass_type, mclassdef.bound_mtype, mmodule, false) var st2 = st.resolve_for(mclassdef.mclass.mclass_type, mclassdef.bound_mtype, mmodule, false) if st1 != st2 then - error(nclassdef, "Error: Incompatibles ancestors for {mclassdef.mclass}: {st1}, {st2}") + error(nclassdef, "Error: incompatible ancestors for `{mclassdef.mclass}`; conflict: `{st1}` and `{st2}`") end end end end end - if errcount != toolcontext.error_count then return - # TODO: Check that the super-class is not intrusive # Check that the superclasses are not already known (by transitivity) for nclassdef in nmodule.n_classdefs do - if not nclassdef isa AStdClassdef then continue - var mclassdef = nclassdef.mclassdef.as(not null) + if not nclassdef isa AStdClassdef or nclassdef.is_broken then continue + var mclassdef = nclassdef.mclassdef + if mclassdef == null then continue # Get the direct superclasses # Since we are a mclassdef, just look at the mclassdef hierarchy @@ -394,18 +470,21 @@ redef class ModelBuilder for nsc in nclassdef.n_superclasses do var ntype = nsc.n_type var mtype = ntype.mtype - if mtype == null then continue - assert mtype isa MClassType + + # If the supertype is `null` or don’t refer to a class, we + # already raised an error. + if not mtype isa MClassType then continue + var sc = mtype.mclass if not parents.has(sc) or sc == objectclass then # Skip the warning on generated code if ntype.location.file != null and not ntype.location.file.filename.is_empty then - warning(ntype, "useless-superclass", "Warning: superfluous super-class {mtype} in class {mclassdef.mclass}.") + warning(ntype, "useless-superclass", "Warning: superfluous super-class `{mtype}` in class `{mclassdef.mclass}`.") end else if not seen_parents.has_key(sc) then seen_parents[sc] = ntype else - warning(ntype, "useless-superclass", "Warning: duplicated super-class {mtype} in class {mclassdef.mclass}.") + warning(ntype, "useless-superclass", "Warning: duplicated super-class `{mtype}` in class `{mclassdef.mclass}`.") end end end @@ -414,113 +493,13 @@ redef class ModelBuilder # Registration of the nclassdef associated to each mclassdef private var mclassdef2nclassdef = new HashMap[MClassDef, AClassdef] - # 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 name = ntype.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_name(ntype, mmodule, name) - 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.") - else if mclass.arity == 0 then - error(ntype, "Type error: '{name}' is not a generic class.") - else - error(ntype, "Type error: '{name}' has {mclass.arity} parameters ({arity} are provided).") - 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 :( - error(ntype, "Type error: class {name} not found in module {mmodule}.") - return null - 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 bound = mclass.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 mt.is_subtype(mmodule, anchor, bound) then - error(nt, "Type error: expected {bound}, got {mt}") - return null - end - end - end - ntype.checked_mtype = true - return mtype + # Retrieve the associated AST node of a mclassdef. + # + # This method is used to associate model entity with syntactic entities. + # If the class definition is not associated with a node, returns `null`. + fun mclassdef2node(mclassdef: MClassDef): nullable AClassdef do + return mclassdef2nclassdef.get_or_null(mclassdef) end - end redef class AModule @@ -559,6 +538,9 @@ end redef class AExternClasskind redef fun mkind do return extern_kind end +redef class ASubsetClasskind + redef fun mkind do return subset_kind +end redef class AFormaldef # The associated parameter type @@ -567,11 +549,3 @@ redef class AFormaldef # The associated bound var bound: nullable MType = null 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 -end