Refactor name mangling to separate class, improve error messages (#735)
diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py
index 2fb47c0..8ebab21 100755
--- a/generator/nanopb_generator.py
+++ b/generator/nanopb_generator.py
@@ -1447,6 +1447,9 @@
                     raise Exception("Could not find enum type %s while generating default values for %s.\n" % (enumname, self.name)
                                     + "Try passing all source files to generator at once, or use -I option.")
 
+                if not isinstance(enumtype, Enum):
+                    raise Exception("Expected enum type as %s, got %s" % (enumname, repr(enumtype)))
+
                 if field.HasField('default_value'):
                     defvals = [v for n,v in enumtype.values if n.parts[-1] == field.default_value]
                 else:
@@ -1561,6 +1564,81 @@
             result += '_'
     return result
 
+class MangleNames:
+    '''Handles conversion of type names according to mangle_names option:
+    M_NONE = 0; // Default, no typename mangling
+    M_STRIP_PACKAGE = 1; // Strip current package name
+    M_FLATTEN = 2; // Only use last path component
+    M_PACKAGE_INITIALS = 3; // Replace the package name by the initials
+    '''
+    def __init__(self, fdesc, file_options):
+        self.file_options = file_options
+        self.mangle_names = file_options.mangle_names
+        self.flatten = (self.mangle_names == nanopb_pb2.M_FLATTEN)
+        self.strip_prefix = None
+        self.replacement_prefix = None
+        self.name_mapping = {}
+        self.reverse_name_mapping = {}
+
+        if self.mangle_names == nanopb_pb2.M_STRIP_PACKAGE:
+            self.strip_prefix = "." + fdesc.package
+        elif self.mangle_names == nanopb_pb2.M_PACKAGE_INITIALS:
+            self.strip_prefix = "." + fdesc.package
+            self.replacement_prefix = ""
+            for part in fdesc.package.split("."):
+                self.replacement_prefix += part[0]
+        elif file_options.package:
+            self.strip_prefix = "." + fdesc.package
+            self.replacement_prefix = file_options.package
+
+        if self.replacement_prefix is not None:
+            self.base_name = Names(self.replacement_prefix.split('.'))
+        elif fdesc.package:
+            self.base_name = Names(fdesc.package.split('.'))
+        else:
+            self.base_name = Names()
+
+    def create_name(self, names):
+        '''Create name for a new message / enum.
+        Argument can be either string or Names instance.
+        '''
+        if str(names) not in self.name_mapping:
+            if self.mangle_names in (nanopb_pb2.M_NONE, nanopb_pb2.M_PACKAGE_INITIALS):
+                new_name = self.base_name + names
+            elif self.mangle_names == nanopb_pb2.M_STRIP_PACKAGE:
+                new_name = Names(names)
+            elif isinstance(names, Names):
+                new_name = Names(names.parts[-1])
+            else:
+                new_name = Names(names)
+
+            if str(new_name) in self.reverse_name_mapping:
+                sys.stderr.write("Warning: Duplicate name with mangle_names=%s: %s and %s map to %s\n" %
+                    (self.mangle_names, self.reverse_name_mapping[str(new_name)], names, new_name))
+
+            self.name_mapping[str(names)] = new_name
+            self.reverse_name_mapping[str(new_name)] = names
+
+        return self.name_mapping[str(names)]
+
+    def mangle_field_typename(self, typename):
+        '''Mangle type name for a submessage / enum crossreference.
+        Argument is a string.
+        '''
+        if self.mangle_names == nanopb_pb2.M_FLATTEN:
+            return "." + typename.split(".")[-1]
+
+        if self.strip_prefix is not None and typename.startswith(self.strip_prefix):
+            if self.replacement_prefix is not None:
+                return "." + self.replacement_prefix + typename[len(self.strip_prefix):]
+            else:
+                return typename[len(self.strip_prefix):]
+
+        if self.file_options.package:
+            return "." + self.replacement_prefix + typename
+
+        return typename
+
 class ProtoFile:
     def __init__(self, fdesc, file_options):
         '''Takes a FileDescriptorProto and parses it.'''
@@ -1582,51 +1660,7 @@
         self.enums = []
         self.messages = []
         self.extensions = []
-
-        mangle_names = self.file_options.mangle_names
-        flatten = mangle_names == nanopb_pb2.M_FLATTEN
-        strip_prefix = None
-        replacement_prefix = None
-        if mangle_names == nanopb_pb2.M_STRIP_PACKAGE:
-            strip_prefix = "." + self.fdesc.package
-        elif mangle_names == nanopb_pb2.M_PACKAGE_INITIALS:
-            strip_prefix = "." + self.fdesc.package
-            replacement_prefix = ""
-            for part in self.fdesc.package.split("."):
-                replacement_prefix += part[0]
-        elif self.file_options.package:
-            strip_prefix = "." + self.fdesc.package
-            replacement_prefix = self.file_options.package
-
-
-        def create_name(names):
-            if mangle_names in (nanopb_pb2.M_NONE, nanopb_pb2.M_PACKAGE_INITIALS):
-                return base_name + names
-            if mangle_names == nanopb_pb2.M_STRIP_PACKAGE:
-                return Names(names)
-            single_name = names
-            if isinstance(names, Names):
-                single_name = names.parts[-1]
-            return Names(single_name)
-
-        def mangle_field_typename(typename):
-            if mangle_names == nanopb_pb2.M_FLATTEN:
-                return "." + typename.split(".")[-1]
-            if strip_prefix is not None and typename.startswith(strip_prefix):
-                if replacement_prefix is not None:
-                    return "." + replacement_prefix + typename[len(strip_prefix):]
-                else:
-                    return typename[len(strip_prefix):]
-            if self.file_options.package:
-                return "." + replacement_prefix + typename
-            return typename
-
-        if replacement_prefix is not None:
-            base_name = Names(replacement_prefix.split('.'))
-        elif self.fdesc.package:
-            base_name = Names(self.fdesc.package.split('.'))
-        else:
-            base_name = Names()
+        self.manglenames = MangleNames(self.fdesc, self.file_options)
 
         # process source code comment locations
         # ignores any locations that do not contain any comment information
@@ -1637,12 +1671,12 @@
         }
 
         for index, enum in enumerate(self.fdesc.enum_type):
-            name = create_name(enum.name)
+            name = self.manglenames.create_name(enum.name)
             enum_options = get_nanopb_suboptions(enum, self.file_options, name)
             self.enums.append(Enum(name, enum, enum_options, index, self.comment_locations))
 
-        for index, (names, message) in enumerate(iterate_messages(self.fdesc, flatten)):
-            name = create_name(names)
+        for index, (names, message) in enumerate(iterate_messages(self.fdesc, self.manglenames.flatten)):
+            name = self.manglenames.create_name(names)
             message_options = get_nanopb_suboptions(message, self.file_options, name)
 
             if message_options.skip_message:
@@ -1651,21 +1685,21 @@
             message = copy.deepcopy(message)
             for field in message.field:
                 if field.type in (FieldD.TYPE_MESSAGE, FieldD.TYPE_ENUM):
-                    field.type_name = mangle_field_typename(field.type_name)
+                    field.type_name = self.manglenames.mangle_field_typename(field.type_name)
 
             self.messages.append(Message(name, message, message_options, index, self.comment_locations))
             for index, enum in enumerate(message.enum_type):
-                name = create_name(names + enum.name)
+                name = self.manglenames.create_name(names + enum.name)
                 enum_options = get_nanopb_suboptions(enum, message_options, name)
                 self.enums.append(Enum(name, enum, enum_options, index, self.comment_locations))
 
-        for names, extension in iterate_extensions(self.fdesc, flatten):
-            name = create_name(names + extension.name)
+        for names, extension in iterate_extensions(self.fdesc, self.manglenames.flatten):
+            name = self.manglenames.create_name(names + extension.name)
             field_options = get_nanopb_suboptions(extension, self.file_options, name)
 
             extension = copy.deepcopy(extension)
             if extension.type in (FieldD.TYPE_MESSAGE, FieldD.TYPE_ENUM):
-                extension.type_name = mangle_field_typename(extension.type_name)
+                extension.type_name = self.manglenames.mangle_field_typename(extension.type_name)
 
             if field_options.type != nanopb_pb2.FT_IGNORE:
                 self.extensions.append(ExtensionField(name, extension, field_options))