Merge: Enforce namespace rules
authorJean Privat <jean@pryen.org>
Sat, 10 Jan 2015 02:31:30 +0000 (21:31 -0500)
committerJean Privat <jean@pryen.org>
Sat, 10 Jan 2015 02:31:30 +0000 (21:31 -0500)
Enforce rules indicated in #1047.

So, having homonymous public modules and classes in a same project raises an error.
In the current code there was no conflicting modules. It is not surprising because for a long time the compilers refused homonymous modules even in different projects.
There was only 2 conflicting classes (on a total of 3640). This is more a good surprise since I expected a lot of conflicts. It is some kind of prof that the proposed policy is not that crazy. The two pairs of conflicting classes were `UnicodeChar` in `lib/string_experimentations/utf8.nit` and `lib/string_experimentations/utf8_noindex.nit`, and `Frame` in `naive_interpreter` and `abstract_compiler`. Some commits in the PR rename one of each pair to solve the conflicts.

For public properties, the proposed rule is to have a unique full-name "project::class::name".
There was 11 conflicts, again, it is far less than I expected.
Two of these conflict are resolved in some commits.
The other 9 are currently left as is (and the displayed error is in fact a warning).
All these remaining conflicts are a variation of the same pattern: homonymous options in refinements of ToolContext for different tools. Eg `opt_rta` for `nitmetrics` and for `nitc`.
I am not sure what is the correct way to solve these since the conflict is not only in the name but also in the behavior (a refinement of those two modules will have a broken option parsing). Maybe, behind the name conflict, there is also a bad model that misuses class refinement.

Pull-Request: #1069
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

1  2 
src/compiler/abstract_compiler.nit
src/compiler/global_compiler.nit
src/compiler/separate_compiler.nit
src/loader.nit
src/modelize/modelize_class.nit
src/modelize/modelize_property.nit

@@@ -481,10 -481,6 +481,10 @@@ abstract class AbstractCompile
                self.realmainmodule = mainmodule
        end
  
 +      # Do the full code generation of the program `mainmodule`
 +      # It is the main method usually called after the instantiation
 +      fun do_compilation is abstract
 +
        # Force the creation of a new file
        # The point is to avoid contamination between must-be-compiled-separately files
        fun new_file(name: String): CodeFile
@@@ -1040,8 -1036,8 +1040,8 @@@ abstract class AbstractCompilerVisito
        # The current visited AST node
        var current_node: nullable ANode = null is writable
  
-       # The current `Frame`
-       var frame: nullable Frame = null is writable
+       # The current `StaticFrame`
+       var frame: nullable StaticFrame = null is writable
  
        # Alias for self.compiler.mainmodule.object_type
        fun object_type: MClassType do return self.compiler.mainmodule.object_type
                self.require_declaration(s)
        end
  
 -      # look for a needed .h and .c file for a given .nit source-file
 -      # FIXME: bad API, parameter should be a `MModule`, not its source-file
 -      fun add_extern(file: String)
 +      # Look for a needed .h and .c file for a given module
 +      # This is used for the legacy FFI
 +      fun add_extern(mmodule: MModule)
        do
 +              var file = mmodule.location.file.filename
                file = file.strip_extension(".nit")
                var tryfile = file + ".nit.h"
                if tryfile.file_exists then
@@@ -1670,8 -1665,8 +1670,8 @@@ class RuntimeVariabl
        end
  end
  
- # A frame correspond to a visited property in a `GlobalCompilerVisitor`
- class Frame
+ # The static context of a visited property in a `AbstractCompilerVisitor`
+ class StaticFrame
  
        type VISITOR: AbstractCompilerVisitor
  
@@@ -2156,13 -2151,16 +2156,13 @@@ redef class AMethPropde
        do
                var externname
                var at = self.get_single_annotation("extern", v.compiler.modelbuilder)
 -              if at != null then
 +              if at != null and at.n_args.length == 1 then
                        externname = at.arg_as_string(v.compiler.modelbuilder)
                        if externname == null then return false
                else
                        return false
                end
 -              if location.file != null then
 -                      var file = location.file.filename
 -                      v.add_extern(file)
 -              end
 +              v.add_extern(mpropdef.mclassdef.mmodule)
                var res: nullable RuntimeVariable = null
                var ret = mpropdef.msignature.return_mtype
                if ret != null then
                else
                        return false
                end
 -              if location.file != null then
 -                      var file = location.file.filename
 -                      v.add_extern(file)
 -              end
 +              v.add_extern(mpropdef.mclassdef.mmodule)
                v.adapt_signature(mpropdef, arguments)
                v.unbox_signature_extern(mpropdef, arguments)
                var ret = arguments.first.mtype
@@@ -2270,7 -2271,7 +2270,7 @@@ redef class AAttrPropde
                var oldnode = v.current_node
                v.current_node = self
                var old_frame = v.frame
-               var frame = new Frame(v, self.mpropdef.as(not null), recv.mcasttype.as_notnullable.as(MClassType), [recv])
+               var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mcasttype.as_notnullable.as(MClassType), [recv])
                v.frame = frame
  
                var value
                var oldnode = v.current_node
                v.current_node = self
                var old_frame = v.frame
-               var frame = new Frame(v, self.mpropdef.as(not null), recv.mtype.as(MClassType), [recv])
+               var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mtype.as(MClassType), [recv])
                v.frame = frame
                # Force read to check the initialization
                v.read_attribute(self.mpropdef.mproperty, recv)
@@@ -59,43 -59,6 +59,43 @@@ redef class ModelBuilde
                self.toolcontext.info("*** GENERATING C ***", 1)
  
                var compiler = new GlobalCompiler(mainmodule, self, runtime_type_analysis)
 +              compiler.do_compilation
 +              compiler.display_stats
 +
 +              var time1 = get_time
 +              self.toolcontext.info("*** END GENERATING C: {time1-time0} ***", 2)
 +              write_and_make(compiler)
 +      end
 +end
 +
 +# Compiler that use global compilation and perform hard optimisations like:
 +#   * customization
 +#   * switch dispatch
 +#   * inlining
 +class GlobalCompiler
 +      super AbstractCompiler
 +
 +      redef type VISITOR: GlobalCompilerVisitor
 +
 +      # The result of the RTA (used to know live types and methods)
 +      var runtime_type_analysis: RapidTypeAnalysis
 +
 +      init
 +      do
 +              var file = new_file("{mainmodule.c_name}.nitgg")
 +              self.header = new CodeWriter(file)
 +              self.live_primitive_types = new Array[MClassType]
 +              for t in runtime_type_analysis.live_types do
 +                      if t.ctype != "val*" or t.mclass.name == "Pointer" then
 +                              self.live_primitive_types.add(t)
 +                      end
 +              end
 +      end
 +
 +      redef fun do_compilation
 +      do
 +              var compiler = self
 +
                compiler.compile_header
  
                if mainmodule.model.get_mclasses_by_name("Pointer") != null then
                # Compile until all runtime_functions are visited
                while not compiler.todos.is_empty do
                        var m = compiler.todos.shift
 -                      self.toolcontext.info("Compile {m} ({compiler.seen.length-compiler.todos.length}/{compiler.seen.length})", 3)
 +                      modelbuilder.toolcontext.info("Compile {m} ({compiler.seen.length-compiler.todos.length}/{compiler.seen.length})", 3)
                        m.compile_to_c(compiler)
                end
 -              self.toolcontext.info("Total methods to compile to C: {compiler.seen.length}", 2)
 -
 -              compiler.display_stats
 -
 -              var time1 = get_time
 -              self.toolcontext.info("*** END GENERATING C: {time1-time0} ***", 2)
 -              write_and_make(compiler)
 -      end
 -end
 -
 -# Compiler that use global compilation and perform hard optimisations like:
 -#   * customization
 -#   * switch dispatch
 -#   * inlining
 -class GlobalCompiler
 -      super AbstractCompiler
 -
 -      redef type VISITOR: GlobalCompilerVisitor
 -
 -      # The result of the RTA (used to know live types and methods)
 -      var runtime_type_analysis: RapidTypeAnalysis
 +              modelbuilder.toolcontext.info("Total methods to compile to C: {compiler.seen.length}", 2)
  
 -      init
 -      do
 -              var file = new_file("{mainmodule.c_name}.nitgg")
 -              self.header = new CodeWriter(file)
 -              self.live_primitive_types = new Array[MClassType]
 -              for t in runtime_type_analysis.live_types do
 -                      if t.ctype != "val*" or t.mclass.name == "Pointer" then
 -                              self.live_primitive_types.add(t)
 -                      end
 -              end
        end
  
        # Compile class names (for the class_name and output_class_name methods)
@@@ -441,7 -434,7 +441,7 @@@ class GlobalCompilerVisito
                if args.first.mcasttype isa MNullableType or args.first.mcasttype isa MNullType and consider_null then
                        # The reciever is potentially null, so we have to 3 cases: ==, != or NullPointerException
                        self.add("if ({args.first} == NULL) \{ /* Special null case */")
 -                      if m.name == "==" then
 +                      if m.name == "==" or m.name == "is_same_instance" then
                                assert res != null
                                if args[1].mcasttype isa MNullableType then
                                        self.add("{res} = ({args[1]} == NULL);")
@@@ -972,7 -965,7 +972,7 @@@ private class CustomizedRuntimeFunctio
                        selfvar.is_exact = true
                end
                var arguments = new Array[RuntimeVariable]
-               var frame = new Frame(v, mmethoddef, recv, arguments)
+               var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
  
                var sig = new FlatBuffer
                        ret = v.resolve_for(ret, arguments.first)
                end
                if self.mmethoddef.can_inline(v) then
-                       var frame = new Frame(v, self.mmethoddef, self.recv, arguments)
+                       var frame = new StaticFrame(v, self.mmethoddef, self.recv, arguments)
                        frame.returnlabel = v.get_name("RET_LABEL")
                        if ret != null then
                                frame.returnvar = v.new_var(ret)
@@@ -90,55 -90,12 +90,55 @@@ redef class ModelBuilde
                self.toolcontext.info("*** GENERATING C ***", 1)
  
                var compiler = new SeparateCompiler(mainmodule, self, runtime_type_analysis)
 +              compiler.do_compilation
 +              compiler.display_stats
 +
 +              var time1 = get_time
 +              self.toolcontext.info("*** END GENERATING C: {time1-time0} ***", 2)
 +              write_and_make(compiler)
 +      end
 +
 +      # Count number of invocations by VFT
 +      private var nb_invok_by_tables = 0
 +      # Count number of invocations by direct call
 +      private var nb_invok_by_direct = 0
 +      # Count number of invocations by inlining
 +      private var nb_invok_by_inline = 0
 +end
 +
 +# Singleton that store the knowledge about the separate compilation process
 +class SeparateCompiler
 +      super AbstractCompiler
 +
 +      redef type VISITOR: SeparateCompilerVisitor
 +
 +      # The result of the RTA (used to know live types and methods)
 +      var runtime_type_analysis: nullable RapidTypeAnalysis
 +
 +      private var undead_types: Set[MType] = new HashSet[MType]
 +      private var live_unresolved_types: Map[MClassDef, Set[MType]] = new HashMap[MClassDef, HashSet[MType]]
 +
 +      private var type_ids: Map[MType, Int] is noinit
 +      private var type_colors: Map[MType, Int] is noinit
 +      private var opentype_colors: Map[MType, Int] is noinit
 +      protected var method_colors: Map[PropertyLayoutElement, Int] is noinit
 +      protected var attr_colors: Map[MAttribute, Int] is noinit
 +
 +      init do
 +              var file = new_file("nit.common")
 +              self.header = new CodeWriter(file)
 +              self.compile_box_kinds
 +      end
 +
 +      redef fun do_compilation
 +      do
 +              var compiler = self
                compiler.compile_header
  
                var c_name = mainmodule.c_name
  
                # compile class structures
 -              self.toolcontext.info("Property coloring", 2)
 +              modelbuilder.toolcontext.info("Property coloring", 2)
                compiler.new_file("{c_name}.classes")
                compiler.do_property_coloring
                for m in mainmodule.in_importation.greaters do
  
                # compile methods
                for m in mainmodule.in_importation.greaters do
 -                      self.toolcontext.info("Generate C for module {m.full_name}", 2)
 +                      modelbuilder.toolcontext.info("Generate C for module {m.full_name}", 2)
                        compiler.new_file("{m.c_name}.sep")
                        compiler.compile_module_to_c(m)
                end
  
                # compile live & cast type structures
 -              self.toolcontext.info("Type coloring", 2)
 +              modelbuilder.toolcontext.info("Type coloring", 2)
                compiler.new_file("{c_name}.types")
 +              compiler.compile_types
 +      end
 +
 +      # Color and compile type structures and cast information
 +      fun compile_types
 +      do
 +              var compiler = self
 +
                var mtypes = compiler.do_type_coloring
                for t in mtypes do
                        compiler.compile_type_to_c(t)
                        compiler.compile_type_to_c(t)
                end
  
 -              compiler.display_stats
 -
 -              var time1 = get_time
 -              self.toolcontext.info("*** END GENERATING C: {time1-time0} ***", 2)
 -              write_and_make(compiler)
 -      end
 -
 -      # Count number of invocations by VFT
 -      private var nb_invok_by_tables = 0
 -      # Count number of invocations by direct call
 -      private var nb_invok_by_direct = 0
 -      # Count number of invocations by inlining
 -      private var nb_invok_by_inline = 0
 -end
 -
 -# Singleton that store the knowledge about the separate compilation process
 -class SeparateCompiler
 -      super AbstractCompiler
 -
 -      redef type VISITOR: SeparateCompilerVisitor
 -
 -      # The result of the RTA (used to know live types and methods)
 -      var runtime_type_analysis: nullable RapidTypeAnalysis
 -
 -      private var undead_types: Set[MType] = new HashSet[MType]
 -      private var live_unresolved_types: Map[MClassDef, Set[MType]] = new HashMap[MClassDef, HashSet[MType]]
 -
 -      private var type_ids: Map[MType, Int] is noinit
 -      private var type_colors: Map[MType, Int] is noinit
 -      private var opentype_colors: Map[MType, Int] is noinit
 -      protected var method_colors: Map[PropertyLayoutElement, Int] is noinit
 -      protected var attr_colors: Map[MAttribute, Int] is noinit
 -
 -      init do
 -              var file = new_file("nit.common")
 -              self.header = new CodeWriter(file)
 -              self.compile_box_kinds
        end
  
        redef fun compile_header_structs do
@@@ -1077,8 -1063,8 +1077,8 @@@ class SeparateCompilerVisito
        do
                var rta = compiler.runtime_type_analysis
                var mmethod = callsite.mproperty
 -              # TODO: Inlining of new-style constructors
 -              if compiler.modelbuilder.toolcontext.opt_direct_call_monomorph.value and rta != null and not mmethod.is_root_init then
 +              # TODO: Inlining of new-style constructors with initializers
 +              if compiler.modelbuilder.toolcontext.opt_direct_call_monomorph.value and rta != null and callsite.mpropdef.initializers.is_empty then
                        var tgs = rta.live_targets(callsite)
                        if tgs.length == 1 then
                                # DIRECT CALL
                var res: nullable RuntimeVariable = null
                var recv = arguments.first
                var consider_null = not self.compiler.modelbuilder.toolcontext.opt_no_check_null.value or mmethod.name == "==" or mmethod.name == "!="
 -              var maybenull = recv.mcasttype isa MNullableType and consider_null
 +              var maybenull = (recv.mcasttype isa MNullableType or recv.mcasttype isa MNullType) and consider_null
                if maybenull then
                        self.add("if ({recv} == NULL) \{")
 -                      if mmethod.name == "==" then
 +                      if mmethod.name == "==" or mmethod.name == "is_same_instance" then
                                res = self.new_var(bool_type)
                                var arg = arguments[1]
                                if arg.mcasttype isa MNullableType then
                else
                        self.add("\{")
                end
 -              if not self.compiler.modelbuilder.toolcontext.opt_no_shortcut_equate.value and (mmethod.name == "==" or mmethod.name == "!=") then
 -                      if res == null then res = self.new_var(bool_type)
 -                      # Recv is not null, thus is arg is, it is easy to conclude (and respect the invariants)
 +              if not self.compiler.modelbuilder.toolcontext.opt_no_shortcut_equate.value and (mmethod.name == "==" or mmethod.name == "!=" or mmethod.name == "is_same_instance") then
 +                      # Recv is not null, thus if arg is, it is easy to conclude (and respect the invariants)
                        var arg = arguments[1]
                        if arg.mcasttype isa MNullType then
 -                              if mmethod.name == "==" then
 -                                      self.add("{res} = 0; /* arg is null but recv is not */")
 -                              else
 +                              if res == null then res = self.new_var(bool_type)
 +                              if mmethod.name == "!=" then
                                        self.add("{res} = 1; /* arg is null and recv is not */")
 +                              else # `==` and `is_same_instance`
 +                                      self.add("{res} = 0; /* arg is null but recv is not */")
                                end
                                self.add("\}") # closes the null case
                                self.add("if (0) \{") # what follow is useless, CC will drop it
                        (compiler.modelbuilder.toolcontext.opt_inline_some_methods.value and mmethoddef.can_inline(self)) then
                        compiler.modelbuilder.nb_invok_by_inline += 1
                        if compiler.modelbuilder.toolcontext.opt_invocation_metrics.value then add("count_invoke_by_inline++;")
-                       var frame = new Frame(self, mmethoddef, recvtype, arguments)
+                       var frame = new StaticFrame(self, mmethoddef, recvtype, arguments)
                        frame.returnlabel = self.get_name("RET_LABEL")
                        frame.returnvar = res
                        var old_frame = self.frame
                # of the method (ie recv) if the static type is unresolved
                # This is more complex than usual because the unresolved type must not be resolved
                # with the current receiver (ie self).
-               # Therefore to isolate the resolution from self, a local Frame is created.
+               # Therefore to isolate the resolution from self, a local StaticFrame is created.
                # One can see this implementation as an inlined method of the receiver whose only
                # job is to allocate the array
                var old_frame = self.frame
-               var frame = new Frame(self, mpropdef, mpropdef.mclassdef.bound_mtype, [recv])
+               var frame = new StaticFrame(self, mpropdef, mpropdef.mclassdef.bound_mtype, [recv])
                self.frame = frame
                #print "required Array[{elttype}] for recv {recv.inspect}. bound=Array[{self.resolve_for(elttype, recv)}]. selfvar={frame.arguments.first.inspect}"
                var res = self.array_instance(varargs, elttype)
@@@ -1820,7 -1806,7 +1820,7 @@@ class SeparateRuntimeFunctio
                var v = compiler.new_visitor
                var selfvar = new RuntimeVariable("self", recv, recv)
                var arguments = new Array[RuntimeVariable]
-               var frame = new Frame(v, mmethoddef, recv, arguments)
+               var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
  
                var msignature = mmethoddef.msignature.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true)
@@@ -1892,7 -1878,7 +1892,7 @@@ class VirtualRuntimeFunctio
                var v = compiler.new_visitor
                var selfvar = new RuntimeVariable("self", v.object_type, recv)
                var arguments = new Array[RuntimeVariable]
-               var frame = new Frame(v, mmethoddef, recv, arguments)
+               var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
  
                var sig = new FlatBuffer
diff --combined src/loader.nit
@@@ -88,61 -88,6 +88,61 @@@ redef class ModelBuilde
                return mmodules.to_a
        end
  
 +      # Load recursively all modules of the group `mgroup`.
 +      # See `parse` for details.
 +      fun parse_group(mgroup: MGroup): Array[MModule]
 +      do
 +              var res = new Array[MModule]
 +              visit_group(mgroup)
 +              for mg in mgroup.in_nesting.smallers do
 +                      for mp in mg.module_paths do
 +                              var nmodule = self.load_module(mp.filepath)
 +                              if nmodule == null then continue # Skip error
 +                              # Load imported module
 +                              build_module_importation(nmodule)
 +
 +                              res.add(nmodule.mmodule.as(not null))
 +                      end
 +              end
 +              return res
 +      end
 +
 +      # Load a bunch of modules and groups.
 +      # Each name can be a module or a group.
 +      # If it is a group then recursively all its modules are parsed.
 +      # See `parse` for details.
 +      fun parse_full(names: Sequence[String]): Array[MModule]
 +      do
 +              var time0 = get_time
 +              # Parse and recursively load
 +              self.toolcontext.info("*** PARSE ***", 1)
 +              var mmodules = new ArraySet[MModule]
 +              for a in names do
 +                      var mgroup = self.get_mgroup(a)
 +                      if mgroup != null then
 +                              mmodules.add_all parse_group(mgroup)
 +                              continue
 +                      end
 +                      var nmodule = self.load_module(a)
 +                      if nmodule == null then continue # Skip error
 +                      # Load imported module
 +                      build_module_importation(nmodule)
 +
 +                      mmodules.add(nmodule.mmodule.as(not null))
 +              end
 +              var time1 = get_time
 +              self.toolcontext.info("*** END PARSE: {time1-time0} ***", 2)
 +
 +              self.toolcontext.check_errors
 +
 +              if toolcontext.opt_only_parse.value then
 +                      self.toolcontext.info("*** ONLY PARSE...", 1)
 +                      exit(0)
 +              end
 +
 +              return mmodules.to_a
 +      end
 +
        # The list of directories to search for top level modules
        # The list is initially set with:
        #
        do
                # Check the module name
                var decl = nmodule.n_moduledecl
 -              if decl == null then
 -                      #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
 -              else
 +              if decl != null then
                        var decl_name = decl.n_name.n_id.text
                        if decl_name != mod_name then
                                error(decl.n_name, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
                        end
                end
  
+               # Check for conflicting module names in the project
+               if mgroup != null then
+                       var others = model.get_mmodules_by_name(mod_name)
+                       if others != null then for other in others do
+                               if other.mgroup!= null and other.mgroup.mproject == mgroup.mproject then
+                                       var node: ANode
+                                       if decl == null then node = nmodule else node = decl.n_name
+                                       error(node, "Error: A module named `{other.full_name}` already exists at {other.location}")
+                                       break
+                               end
+                       end
+               end
                # Create the module
                var mmodule = new MModule(model, mgroup, mod_name, nmodule.location)
                nmodule.mmodule = mmodule
        var nmodules = new Array[AModule]
  
        # Register the nmodule associated to each mmodule
 -      # FIXME: why not refine the `MModule` class with a nullable attribute?
 -      var mmodule2nmodule = new HashMap[MModule, AModule]
 +      #
 +      # Public clients need to use `mmodule2node` to access stuff.
 +      private var mmodule2nmodule = new HashMap[MModule, AModule]
 +
 +      # Retrieve the associated AST node of a mmodule.
 +      # This method is used to associate model entity with syntactic entities.
 +      #
 +      # If the module is not associated with a node, returns null.
 +      fun mmodule2node(mmodule: MModule): nullable AModule
 +      do
 +              return mmodule2nmodule.get_or_null(mmodule)
 +      end
  end
  
  # File-system location of a module (file) that is identified but not always loaded.
@@@ -97,6 -97,18 +97,18 @@@ redef class ModelBuilde
                                error(nclassdef, "Redef error: No imported class {name} to refine.")
                                return
                        end
+                       # Check for conflicting class full-names in the project
+                       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.mproject == mmodule.mgroup.mproject then
+                                               error(nclassdef, "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)
                        #print "new class {mclass}"
                else if nclassdef isa AStdClassdef and nmodule.mclass2nclassdef.has_key(mclass) then
                nmodule.build_classes_is_done = true
                var mmodule = nmodule.mmodule.as(not null)
                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
@@@ -45,16 -45,11 +45,16 @@@ redef class ModelBuilde
        # Retrieve the associated AST node of a mpropertydef.
        # This method is used to associate model entity with syntactic entities.
        #
 -      # If the property definition is not associated with a node, returns node.
 +      # If the property definition is not associated with a node, returns `null`.
        fun mpropdef2node(mpropdef: MPropDef): nullable ANode
        do
 -              var res: nullable ANode = mpropdef2npropdef.get_or_null(mpropdef)
 -              if res != null then return res
 +              var res
 +              res = mpropdef2npropdef.get_or_null(mpropdef)
 +              if res != null then
 +                      # Run the phases on it
 +                      toolcontext.run_phases_on_npropdef(res)
 +                      return res
 +              end
                if mpropdef isa MMethodDef and mpropdef.mproperty.is_root_init then
                        res = mclassdef2nclassdef.get_or_null(mpropdef.mclassdef)
                        if res != null then return res
@@@ -71,8 -66,6 +71,8 @@@
                if n == null then return res
                for npropdef in n.n_propdefs do
                        if npropdef isa AAttrPropdef then
 +                              # Run the phases on it
 +                              toolcontext.run_phases_on_npropdef(npropdef)
                                res.add(npropdef)
                        end
                end
@@@ -485,6 -478,18 +485,18 @@@ redef class APropde
                                modelbuilder.error(self, "Redef error: {mclassdef.mclass}::{mprop.name} is an inherited property. To redefine it, add the redef keyword.")
                                return false
                        end
+                       # Check for full-name conflicts in the project.
+                       # A public property should have a unique qualified name `project::class::prop`.
+                       if mprop.intro_mclassdef.mmodule.mgroup != null and mprop.visibility >= protected_visibility then
+                               var others = modelbuilder.model.get_mproperties_by_name(mprop.name)
+                               if others != null then for other in others do
+                                       if other != mprop and other.intro_mclassdef.mmodule.mgroup != null and other.intro_mclassdef.mmodule.mgroup.mproject == mprop.intro_mclassdef.mmodule.mgroup.mproject and other.intro_mclassdef.mclass.name == mprop.intro_mclassdef.mclass.name and other.visibility >= protected_visibility then
+                                               modelbuilder.advice(self, "full-name-conflict", "Warning: A property named `{other.full_name}` is already defined in module `{other.intro_mclassdef.mmodule}` for the class `{other.intro_mclassdef.mclass.name}`.")
+                                               break
+                                       end
+                               end
+                       end
                else
                        if not need_redef then
                                modelbuilder.error(self, "Error: No property {mclassdef.mclass}::{mprop.name} is inherited. Remove the redef keyword to define a new property.")