Fix glib:error-quark scanning for unregistered enum types
[gnome.gobject-introspection] / giscanner / glibtransformer.py
index 02b2767..cfde94b 100644 (file)
@@ -27,12 +27,13 @@ import subprocess
 
 from .ast import (Alias, Bitfield, Callback, Constant, Enum, Function, Member,
                   Namespace, Parameter, Property, Record, Return, Type, Union,
-                  Field, type_name_from_ctype,
+                  Field, VFunction, type_name_from_ctype,
                   default_array_types, TYPE_UINT8, PARAM_TRANSFER_FULL)
 from .transformer import Names
 from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember, GLibFlags,
                       GLibInterface, GLibObject, GLibSignal, GLibBoxedStruct,
-                      GLibBoxedUnion, GLibBoxedOther, type_names)
+                      GLibBoxedUnion, GLibBoxedOther, GLibRecord,
+                      type_names)
 from .utils import to_underscores, to_underscores_noprefix
 
 default_array_types['guchar*'] = TYPE_UINT8
@@ -59,6 +60,18 @@ SYMBOL_BLACKLIST = [
 SYMBOL_BLACKLIST_RE = [re.compile(x) for x in \
                            [r'\w+_marshal_[A-Z]+__', ]]
 
+GET_TYPE_OVERRIDES = {
+    # this is a special case, from glibtransforer.py:create_gobject
+    'intern': 'g_object_get_type',
+    # this is presumably a typo, should be fixed upstream
+    'g_gstring_get_type': 'g_string_get_type',
+    # this is historical cruft: there's a deprecated
+    #   #define gdk_window_get_type gdk_window_get_window_type
+    # upstream; this method can be renamed properly upstream once
+    # that deprecated alias is removed (in some future release)
+    'gdk_window_object_get_type': 'gdk_window_get_type',
+}
+
 
 class IntrospectionBinary(object):
 
@@ -99,9 +112,6 @@ class GLibTransformer(object):
 
     # Public API
 
-    def set_introspection_binary(self, binary):
-        self._binary = binary
-
     def _print_statistics(self):
         nodes = list(self._names.names.itervalues())
 
@@ -114,9 +124,16 @@ class GLibTransformer(object):
         print " %d nodes; %d objects, %d interfaces, %d enums" \
             % (len(nodes), objectcount, ifacecount, enumcount)
 
-    def parse(self):
+    def init_parse(self):
+        """Do parsing steps that don't involve the introspection binary
+
+        This does enough work that get_type_functions() can be called.
+
+        """
+
         namespace = self._transformer.parse()
         self._namespace_name = namespace.name
+        self._namespace_version = namespace.version
 
         # First pass: parsing
         for node in namespace.nodes:
@@ -127,6 +144,15 @@ class GLibTransformer(object):
         if namespace.name == 'GObject':
             del self._names.aliases['Type']
 
+    def get_get_type_functions(self):
+        return self._get_type_functions
+
+    def set_introspection_binary(self, binary):
+        self._binary = binary
+
+    def parse(self):
+        """Do remaining parsing steps requiring introspection binary"""
+
         # Get all the GObject data by passing our list of get_type
         # functions to the compiled binary
 
@@ -144,21 +170,26 @@ class GLibTransformer(object):
             try:
                 self._resolve_node(node)
             except KeyError, e:
-                print "WARNING: DELETING node %s: %s" % (node.name, e)
+                #print "WARNING: DELETING node %s: %s" % (node.name, e)
                 self._remove_attribute(node.name)
+        # Another pass, since we need to have the methods parsed
+        # in order to correctly modify them after class/record
+        # pairing
+        for (ns, node) in nodes:
             # associate GtkButtonClass with GtkButton
             if isinstance(node, Record):
                 self._pair_class_record(node)
         for (ns, alias) in self._names.aliases.itervalues():
             self._resolve_alias(alias)
         self._resolve_quarks()
-        self._print_statistics()
         # Fourth pass: ensure all types are known
         if not self._noclosure:
-            self._validate(nodes)
+            self._resolve_types(nodes)
+
+        #self._validate(nodes)
 
         # Create a new namespace with what we found
-        namespace = Namespace(namespace.name, namespace.version)
+        namespace = Namespace(self._namespace_name, self._namespace_version)
         namespace.nodes = map(lambda x: x[1], self._names.aliases.itervalues())
         for (ns, x) in self._names.names.itervalues():
             namespace.nodes.append(x)
@@ -191,24 +222,59 @@ class GLibTransformer(object):
                 return node[1]
         return node
 
-    def _register_internal_type(self, type_name, node):
-        self._names.type_names[type_name] = (None, node)
-        uscored = to_underscores(type_name).lower()
-        self._uscore_type_names[uscored] = node
+    def _get_no_uscore_prefixed_name(self, type_name):
         # Besides the straight underscore conversion, we also try
         # removing the underscores from the namespace as a possible C
         # mapping; e.g. it's webkit_web_view, not web_kit_web_view
         suffix = self._transformer.remove_prefix(type_name)
         prefix = type_name[:-len(suffix)]
-        no_uscore_prefixed = (prefix + '_' + to_underscores(suffix)).lower()
-        self._uscore_type_names[no_uscore_prefixed] = node
+        return (prefix + '_' + to_underscores(suffix)).lower()
+
+    def _register_internal_type(self, type_name, node):
+        self._names.type_names[type_name] = (None, node)
+        uscored = to_underscores(type_name).lower()
+        # prefer the prefix of the get_type method, if there is one
+        if hasattr(node, 'get_type'):
+            uscored = GET_TYPE_OVERRIDES.get(node.get_type, node.get_type)
+            uscored = uscored[:-len('_get_type')]
+        self._uscore_type_names[uscored] = node
+
+        no_uscore_prefixed = self._get_no_uscore_prefixed_name(type_name)
+        # since this is a guess, don't overwrite any 'real' prefix
+        if no_uscore_prefixed not in self._uscore_type_names:
+            self._uscore_type_names[no_uscore_prefixed] = node
 
     def _resolve_quarks(self):
+        # self._uscore_type_names is an authoritative mapping of types
+        # to underscored versions, since it is based on get_type() methods;
+        # but only covers enums that are registered as GObject enums.
+        # Create a fallback mapping based on all known enums in this module.
+        uscore_enums = {}
+        for enum in self._transformer.iter_enums():
+            type_name = enum.symbol
+            uscored = to_underscores(type_name).lower()
+
+            uscore_enums[uscored] = enum
+
+            no_uscore_prefixed = self._get_no_uscore_prefixed_name(type_name)
+            if no_uscore_prefixed not in uscore_enums:
+                uscore_enums[no_uscore_prefixed] = enum
+
         for node in self._error_quark_functions:
             short = node.symbol[:-len('_quark')]
-            enum = self._uscore_type_names.get(short)
+            if short == "g_io_error":
+                # Special case; GIOError was already taken forcing GIOErrorEnum
+                enum = self._names.type_names["GIOErrorEnum"][1]
+            else:
+                enum = self._uscore_type_names.get(short)
+                if enum is None:
+                    enum = uscore_enums.get(short)
             if enum is not None:
                 enum.error_quark = node.symbol
+            else:
+                print "WARNING: " + \
+                      "Couldn't find corresponding enumeration for %s" % \
+                          (node.symbol, )
 
     # Helper functions
 
@@ -219,6 +285,17 @@ class GLibTransformer(object):
         except KeyError, e:
             return Unresolved(gtype_name)
 
+    def _resolve_gtypename_chain(self, gtype_names):
+        """Like _resolve_gtypename, but grab the first one that resolves.
+        If none of them do, return an Unresolved for the first."""
+        for gtype_name in gtype_names:
+            try:
+                return self._transformer.gtypename_to_giname(gtype_name,
+                                                             self._names)
+            except KeyError, e:
+                continue
+        return Unresolved(gtype_names[0])
+
     def _execute_binary(self):
         in_path = os.path.join(self._binary.tmpdir, 'types.txt')
         f = open(in_path, 'w')
@@ -261,8 +338,20 @@ class GLibTransformer(object):
             parent_type_name = 'GObject'
             parent_gitype = self._resolve_gtypename(parent_type_name)
             symbol = 'g_initially_unowned_get_type'
+        else:
+            assert False
         gnode = GLibObject(node.name, parent_gitype, type_name, symbol, True)
-        gnode.fields.extend(node.fields)
+        if type_name == 'GObject':
+            gnode.fields.extend(node.fields)
+        else:
+            # http://bugzilla.gnome.org/show_bug.cgi?id=569408
+            # GInitiallyUnowned is actually a typedef for GObject, but
+            # that's not reflected in the GIR, where it appears as a
+            # subclass (as it appears in the GType hierarchy).  So
+            # what we do here is copy all of the GObject fields into
+            # GInitiallyUnowned so that struct offset computation
+            # works correctly.
+            gnode.fields = self._names.names['Object'][1].fields
         self._add_attribute(gnode)
         self._register_internal_type(type_name, gnode)
 
@@ -325,6 +414,10 @@ class GLibTransformer(object):
         if self._namespace_name == 'GLib':
             # No GObjects in GLib
             return False
+        if (self._namespace_name == 'GObject' and
+            symbol in ('g_object_get_type', 'g_initially_unowned_get_type')):
+            # We handle these internally, see _create_gobject
+            return True
         if func.parameters:
             return False
         # GType *_get_type(void)
@@ -344,8 +437,8 @@ class GLibTransformer(object):
             return False
         if func.parameters:
             return False
-        if func.retval.type.name not in ['GLib.Quark',
-                                         'GQuark']:
+        if (func.retval.type.name != 'GLib.Quark' and
+            func.retval.type.ctype != 'GQuark'):
             return False
 
         self._error_quark_functions.append(func)
@@ -366,7 +459,7 @@ class GLibTransformer(object):
         target_klass = None
         prefix_components = None
         methname = None
-        for i in xrange(1, len(components)-1):
+        for i in xrange(1, len(components)):
             prefix_components = '_'.join(components[0:-i])
             methname = '_'.join(components[-i:])
             target_klass = self._uscore_type_names.get(prefix_components)
@@ -411,6 +504,13 @@ class GLibTransformer(object):
             argtype = target_arg.type.ctype.replace('*', '')
             name = self._transformer.remove_prefix(argtype)
             name_uscore = to_underscores_noprefix(name).lower()
+            # prefer the prefix of the _get_type method, if there is one
+            if argtype in self._names.type_names:
+                node = self._names.type_names[argtype][1]
+                if hasattr(node, 'get_type'):
+                    name_uscore = GET_TYPE_OVERRIDES.get(node.get_type,
+                                                         node.get_type)
+                    name_uscore = name_uscore[:-len('_get_type')]
             name_offset = func.symbol.find(name_uscore)
             if name_offset < 0:
                 return None
@@ -487,6 +587,8 @@ class GLibTransformer(object):
         elif record.name in g_internal_names:
             # Avoid duplicates
             return
+        if record.name == 'InitiallyUnownedClass':
+            record.fields = self._names.names['ObjectClass'][1].fields
         node = self._names.names.get(record.name)
         if node is None:
             self._add_attribute(record, replace=True)
@@ -529,9 +631,15 @@ class GLibTransformer(object):
         if name == maybe_class.name:
             return
 
-        if self._arg_is_failed(maybe_class):
-            print "WARNING: deleting no-type %r" % (maybe_class.name, )
-            del self._names.names[maybe_class.name]
+        class_struct = maybe_class
+        if self._arg_is_failed(class_struct):
+            print "WARNING: deleting no-type %r" % (class_struct.name, )
+            del self._names.names[class_struct.name]
+            return
+
+        pair_class = self._get_attribute(name)
+        if (not pair_class or
+            not isinstance(pair_class, (GLibObject, GLibInterface))):
             return
 
         # Object class fields are assumed to be read-only
@@ -540,19 +648,47 @@ class GLibTransformer(object):
             if isinstance(field, Field):
                 field.writable = False
 
-        name = self._resolve_type_name(name)
-        resolved = self._transformer.remove_prefix(name)
-        pair_class = self._get_attribute(resolved)
-        if pair_class and isinstance(pair_class, GLibInterface):
-            for field in maybe_class.fields[1:]:
-                pair_class.fields.append(field)
-            return
-        name = self._transformer.remove_prefix(maybe_class.name)
-        pair_class = self._get_attribute(name)
-        if pair_class and isinstance(pair_class,
-                                     (GLibObject, GLibInterface)):
-
-            del self._names.names[maybe_class.name]
+        # Loop through fields to determine which are virtual
+        # functions and which are signal slots by
+        # assuming everything that doesn't share a name
+        # with a known signal is a virtual slot.
+        for field in maybe_class.fields:
+            if not isinstance(field, Callback):
+                continue
+            # Check the first parameter is the object
+            if len(field.parameters) == 0:
+                continue
+            firstparam_type = field.parameters[0].type
+            if firstparam_type != pair_class:
+                continue
+            # Also double check we don't have a signal with this
+            # name.
+            matched_signal = False
+            for signal in pair_class.signals:
+                if signal.name.replace('-', '_') == field.name:
+                    matched_signal = True
+                    break
+            if matched_signal:
+                continue
+            vfunc = VFunction.from_callback(field)
+            pair_class.virtual_methods.append(vfunc)
+
+        # Take the set of virtual methods we found, and try
+        # to pair up with any matching methods using the
+        # name+signature.
+        for vfunc in pair_class.virtual_methods:
+            for method in pair_class.methods:
+                if (method.name != vfunc.name or
+                    method.retval != vfunc.retval or
+                    method.parameters != vfunc.parameters):
+                    continue
+                vfunc.invoker = method.name
+
+        gclass_struct = GLibRecord.from_record(class_struct)
+        self._remove_attribute(class_struct.name)
+        self._add_attribute(gclass_struct, True)
+        pair_class.glib_type_struct = gclass_struct
+        gclass_struct.is_gtype_struct_for = name
 
     # Introspection
 
@@ -571,7 +707,10 @@ class GLibTransformer(object):
     def _introspect_enum(self, node):
         members = []
         for member in node.findall('member'):
-            members.append(GLibEnumMember(member.attrib['nick'],
+            # Keep the name closer to what we'd take from C by default;
+            # see http://bugzilla.gnome.org/show_bug.cgi?id=575613
+            name = member.attrib['nick'].replace('-', '_')
+            members.append(GLibEnumMember(name,
                                           member.attrib['value'],
                                           member.attrib['name'],
                                           member.attrib['nick']))
@@ -590,8 +729,11 @@ class GLibTransformer(object):
         # to skip it
         if type_name == 'GObject':
             return
-        parent_type_name = xmlnode.attrib['parent']
-        parent_gitype = self._resolve_gtypename(parent_type_name)
+        # Get a list of parents here; some of them may be hidden, and what
+        # we really want to do is use the most-derived one that we know of.
+        #
+        parent_type_names = xmlnode.attrib['parents'].split(',')
+        parent_gitype = self._resolve_gtypename_chain(parent_type_names)
         is_abstract = not not xmlnode.attrib.get('abstract', False)
         node = GLibObject(
             self._transformer.remove_prefix(type_name),
@@ -649,7 +791,7 @@ class GLibTransformer(object):
         for interface in xmlnode.findall('implements'):
             gitype = self._resolve_gtypename(interface.attrib['name'])
             gt_interfaces.append(gitype)
-        node.interfaces = gt_interfaces
+        node.interfaces = sorted(gt_interfaces)
 
     def _introspect_properties(self, node, xmlnode):
         for pspec in xmlnode.findall('property'):
@@ -665,6 +807,7 @@ class GLibTransformer(object):
                 readable, writable, construct, construct_only,
                 ctype,
                 ))
+        node.properties = sorted(node.properties)
 
     def _introspect_signals(self, node, xmlnode):
         for signal_info in xmlnode.findall('signal'):
@@ -684,6 +827,7 @@ class GLibTransformer(object):
                 param.transfer = 'none'
                 signal.parameters.append(param)
             node.signals.append(signal)
+        node.signals = sorted(node.signals)
 
     # Resolver
 
@@ -786,7 +930,17 @@ class GLibTransformer(object):
              for x in node.prerequisites])
 
     def _resolve_glib_object(self, node):
-        node.parent = self._force_resolve(node.parent)
+        # If we can't find the parent class, just drop back to GObject.
+        # This supports hidden parent classes.
+        # http://bugzilla.gnome.org/show_bug.cgi?id=561360
+        try:
+            node.parent = self._force_resolve(node.parent)
+        except KeyError, e:
+            #print ("WARNING: Parent %r of class %r" +\
+            #       " not found; using GObject") % (node.parent.target,
+            #                                       node.name)
+            node.parent = self._transformer.gtypename_to_giname("GObject",
+                                                                self._names)
         node.interfaces = filter(None,
             [self._force_resolve(x, allow_unknown=True)
                                     for x in node.interfaces])
@@ -822,8 +976,8 @@ class GLibTransformer(object):
             except KeyError, e:
                 failed.append(prop)
         for fail in failed:
-            print ("WARNING: Deleting object property %r (of %r) "
-                   "with unknown type") % (fail, context)
+            #print ("WARNING: Deleting object property %r (of %r) "
+            #       "with unknown type") % (fail, context)
             properties.remove(fail)
 
     def _resolve_property(self, prop):
@@ -835,9 +989,9 @@ class GLibTransformer(object):
 
         last_param = func.parameters.pop()
 
-        if (last_param.type.name == 'GLib.Error' or
-            (self._namespace_name == 'GLib' and
-             last_param.type.name == 'Error')):
+        # Checking type.name=='GLib.Error' generates false positives
+        # on methods that take a 'GError *'
+        if last_param.type.ctype == 'GError**':
             func.throws = True
         else:
             func.parameters.append(last_param)
@@ -854,22 +1008,24 @@ class GLibTransformer(object):
     def _resolve_field(self, field):
         if isinstance(field, Callback):
             self._resolve_function(field)
-            return
-        field.type = self._resolve_param_type(field.type)
+        elif isinstance(field, Record): # non-typedef'd struct
+            self._resolve_record(field)
+        elif isinstance(field, Union): # non-typedef'd union
+            self._resolve_union(field)
+        else:
+            field.type = self._resolve_param_type(field.type)
 
     def _resolve_alias(self, alias):
         alias.target = self._resolve_type_name(alias.target, alias.target)
 
-    # Validation
-
-    def _validate(self, nodes):
+    def _resolve_types(self, nodes):
         nodes = list(self._names.names.itervalues())
         i = 0
         self._validating = True
         while True:
             initlen = len(nodes)
 
-            print "Type resolution; pass=%d" % (i, )
+            #print "Type resolution; pass=%d" % (i, )
             nodes = list(self._names.names.itervalues())
             for node in nodes:
                 try:
@@ -882,3 +1038,18 @@ class GLibTransformer(object):
             i += 1
             self._print_statistics()
         self._validating = False
+
+    # Validation
+
+    def _validate_interface(self, iface):
+        for vfunc in iface.virtual_methods:
+            if not vfunc.invoker:
+                print ("warning: Interface %r virtual function %r " + \
+                       "has no known invoker") % (iface.name, vfunc.name)
+
+    # This function is called at the very end, before we hand back the
+    # completed namespace to the writer.  Add static analysis checks here.
+    def _validate(self, nodes):
+        for (name, node) in nodes:
+            if isinstance(node, GLibInterface):
+                self._validate_interface(node)