scanner: Always explicitely set the scope of callbacks
[gnome.gobject-introspection] / giscanner / annotationparser.py
index 8c901a2..aa04ef4 100644 (file)
@@ -20,6 +20,7 @@
 
 # AnnotationParser - parses gtk-doc annotations
 
+import re
 import sys
 
 from .ast import (Array, Bitfield, Callback, Class, Enum, Field, Function,
@@ -62,6 +63,7 @@ OPT_TRANSFER = 'transfer'
 OPT_TYPE = 'type'
 OPT_CLOSURE = 'closure'
 OPT_DESTROY = 'destroy'
+OPT_SKIP = 'skip'
 
 # Specific option values
 OPT_VAL_BITFIELD = 'bitfield'
@@ -71,6 +73,9 @@ OPT_ARRAY_FIXED_SIZE = 'fixed-size'
 OPT_ARRAY_LENGTH = 'length'
 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
 
+OPT_SCOPE_ASYNC = 'async'
+OPT_SCOPE_CALL = 'call'
+OPT_SCOPE_NOTIFIED = 'notified'
 
 class InvalidAnnotationError(Exception):
     pass
@@ -204,8 +209,9 @@ class AnnotationParser(object):
             tag_name, value = self._split_tag_namevalue(line)
             canon_name = tag_name.lower()
             if canon_name in block.tags:
-                print >>sys.stderr, "Multiple definition of tag %r" \
-                    % (canon_name, )
+                print >> sys.stderr, (
+                    "Symbol %s has multiple definition of tag %r" % (
+                    block_name, canon_name, ))
             block.tags[canon_name] = self._create_tag(canon_name, value)
         block.comment = '\n'.join(comment_lines)
         self._blocks[block.name] = block
@@ -216,6 +222,8 @@ class AnnotationParser(object):
         if len(parts) == 1:
             tag_name = parts[0]
             value = ''
+            if tag_name.endswith(':'):
+                tag_name = tag_name[:-1]
         else:
             tag_name, value = parts
         return (tag_name, value)
@@ -331,7 +339,7 @@ class AnnotationApplier(object):
         self._parse_node_common(record, block)
         self._parse_constructors(record.constructors)
         self._parse_methods(record, record.methods)
-        self._parse_fields(record, record.fields)
+        self._parse_fields(record, record.fields, block)
         if block:
             record.doc = block.comment
 
@@ -346,7 +354,7 @@ class AnnotationApplier(object):
     def _parse_union(self, union):
         block = self._blocks.get(union.name)
         self._parse_node_common(union, block)
-        self._parse_fields(union, union.fields)
+        self._parse_fields(union, union.fields, block)
         self._parse_constructors(union.constructors)
         self._parse_methods(union, union.methods)
         if block:
@@ -372,9 +380,9 @@ class AnnotationApplier(object):
         for ctor in constructors:
             self._parse_function(ctor)
 
-    def _parse_fields(self, parent, fields):
+    def _parse_fields(self, parent, fields, block=None):
         for field in fields:
-            self._parse_field(parent, field)
+            self._parse_field(parent, field, block)
 
     def _parse_properties(self, parent, properties):
         for prop in properties:
@@ -408,6 +416,17 @@ class AnnotationApplier(object):
 
     def _parse_callable(self, callable, block):
         self._parse_node_common(callable, block)
+        for i, param in enumerate(callable.parameters):
+            if (param.type.ctype != 'GDestroyNotify' and
+                param.type.name != 'GLib.DestroyNotify'):
+                continue
+            if i < 2:
+                break
+            callback_param = callable.parameters[i-2]
+            if callback_param.closure_index != -1:
+                callback_param.scope = OPT_SCOPE_NOTIFIED
+                callback_param.transfer = PARAM_TRANSFER_NONE
+
         self._parse_params(callable, callable.parameters, block)
         self._parse_return(callable, callable.retval, block)
         if block:
@@ -424,7 +443,6 @@ class AnnotationApplier(object):
         # We're only attempting to name the signal parameters if
         # the number of parameter tags (@foo) is the same or greater
         # than the number of signal parameters
-        resolve = self._transformer.resolve_param_type
         if block and len(block.tags) > len(signal.parameters):
             names = block.tags.items()
         else:
@@ -436,7 +454,7 @@ class AnnotationApplier(object):
                 options = getattr(tag, 'options', {})
                 param_type = options.get(OPT_TYPE)
                 if param_type:
-                    param.type.name = resolve(param_type.one())
+                    param.type = self._resolve(param_type.one(), param.type)
             else:
                 tag = None
             self._parse_param(signal, param, tag)
@@ -464,9 +482,19 @@ class AnnotationApplier(object):
         key = '%s::%s' % (parent.type_name, vfunc.name)
         self._parse_callable(vfunc, self._blocks.get(key))
 
-    def _parse_field(self, parent, field):
+    def _parse_field(self, parent, field, block=None):
         if isinstance(field, Callback):
             self._parse_callback(field)
+        else:
+            if not block:
+                return
+            tag = block.get(field.name)
+            if not tag:
+                return
+            t = tag.options.get('type')
+            if not t:
+                return
+            field.type.name = self._transformer.resolve_type_name(t.one())
 
     def _parse_params(self, parent, params, block):
         for param in params:
@@ -484,6 +512,11 @@ class AnnotationApplier(object):
             if scope:
                 param.scope = scope.one()
                 param.transfer = PARAM_TRANSFER_NONE
+            elif (param.type.ctype == 'GAsyncReadyCallback' or
+                  param.type.name == 'Gio.AsyncReadyCallback'):
+                param.scope = OPT_SCOPE_ASYNC
+                param.transfer = PARAM_TRANSFER_NONE
+
             destroy = options.get(OPT_DESTROY)
             if destroy:
                 param.destroy_index = parent.get_parameter_index(destroy.one())
@@ -523,8 +556,7 @@ class AnnotationApplier(object):
             node.allow_none = True
         param_type = options.get(OPT_TYPE)
         if param_type:
-            resolve = self._transformer.resolve_param_type
-            node.type.name = resolve(param_type.one())
+            node.type = self._resolve(param_type.one(), node.type)
 
         assert node.transfer is not None
         if tag is not None and tag.comment is not None:
@@ -583,12 +615,12 @@ class AnnotationApplier(object):
 
         element_type = options.get(OPT_ELEMENT_TYPE)
         if element_type is not None:
-            element_type_name = element_type.one()
+            element_type_node = self._resolve(element_type.one())
         else:
-            element_type_name = node.type.name
+            element_type_node = Type(node.type.name) # erase ctype
 
         container_type = Array(node.type.ctype,
-                               element_type_name)
+                               element_type_node)
         container_type.is_const = node.type.is_const
         if OPT_ARRAY_ZERO_TERMINATED in array_values:
             container_type.zeroterminated = array_values.get(
@@ -602,31 +634,72 @@ class AnnotationApplier(object):
             # is specified.
             if (isinstance(node, Parameter) and
                 node.type.name == 'utf8' and
-                self._guess_direction(node) == PARAM_DIRECTION_IN):
+                self._guess_direction(node) == PARAM_DIRECTION_IN and
+                element_type is None):
                 # FIXME: unsigned char/guchar should be uint8
-                container_type.element_type = 'int8'
+                container_type.element_type = Type('int8')
         container_type.size = array_values.get(OPT_ARRAY_FIXED_SIZE)
         return container_type
 
+    def _resolve(self, type_str, orig_node=None):
+        def grab_one(type_str, resolver, top_combiner, combiner):
+            """Return a complete type, and the trailing string part after it.
+            Use resolver() on each identifier, and combiner() on the parts of
+            each complete type. (top_combiner is used on the top-most type.)"""
+            bits = re.split(r'([,<>])', type_str, 1)
+            first, sep, rest = [bits[0], '', ''] if (len(bits)==1) else bits
+            args = [resolver(first)]
+            if sep == '<':
+                while sep != '>':
+                    next, rest = grab_one(rest, resolver, combiner, combiner)
+                    args.append(next)
+                    sep, rest = rest[0], rest[1:]
+            else:
+                rest = sep + rest
+            return top_combiner(*args), rest
+        def resolver(ident):
+            return self._transformer.resolve_param_type(Type(ident))
+        def combiner(base, *rest):
+            if not rest:
+                return base
+            if (base.name in ['GLib.List', 'GLib.SList'] or
+                base.ctype in ['GList*', 'GSList*']) and len(rest)==1:
+                return List(base.name, base.ctype, *rest)
+            if (base.name in ['GLib.HashTable'] or
+                base.ctype in ['GHashTable*']) and len(rest)==2:
+                return Map(base.name, base.ctype, *rest)
+            print "WARNING: throwing away type parameters:", type_str
+            return base
+        def top_combiner(base, *rest):
+            """For the top most type, recycle orig_node if possible."""
+            if orig_node is not None:
+                orig_node.name = base.name
+                base = orig_node # preserve other properties of orig_node
+            return combiner(base, *rest)
+
+        result, rest = grab_one(type_str, resolver, top_combiner, combiner)
+        if rest:
+            print "WARNING: throwing away trailing part of type:", type_str
+        return result
+
     def _parse_element_type(self, parent, node, options):
         element_type_opt = options.get(OPT_ELEMENT_TYPE)
         element_type = element_type_opt.flat()
-        if node.type.name in ['GLib.List', 'GLib.SList']:
+        if (node.type.name in ['GLib.List', 'GLib.SList'] or
+            node.type.ctype in ['GList*', 'GSList*']):
             assert len(element_type) == 1
-            etype = Type(element_type[0])
             container_type = List(
                 node.type.name,
                 node.type.ctype,
-                self._transformer.resolve_param_type(etype))
-        elif node.type.name in ['GLib.HashTable']:
+                self._resolve(element_type[0]))
+        elif (node.type.name in ['GLib.HashTable'] or
+              node.type.ctype in ['GHashTable*']):
             assert len(element_type) == 2
-            key_type = Type(element_type[0])
-            value_type = Type(element_type[1])
             container_type = Map(
                 node.type.name,
                 node.type.ctype,
-                self._transformer.resolve_param_type(key_type),
-                self._transformer.resolve_param_type(value_type))
+                self._resolve(element_type[0]),
+                self._resolve(element_type[1]))
         else:
             print 'FIXME: unhandled element-type container:', node
         return container_type
@@ -651,6 +724,7 @@ class AnnotationApplier(object):
         self._parse_version(node, block)
         self._parse_deprecated(node, block)
         self._parse_attributes(node, block)
+        self._parse_skip(node, block)
 
     def _parse_version(self, node, block):
         since_tag = self._get_tag(block, TAG_SINCE)
@@ -679,6 +753,11 @@ class AnnotationApplier(object):
         for key, value in annos_tag.options.iteritems():
             node.attributes.append((key, value.one()))
 
+    def _parse_skip(self, node, block):
+        if block is not None:
+            if OPT_SKIP in block.options:
+                node.skip = True
+
     def _parse_rename_to_func(self, node, block):
         rename_to_tag = self._get_tag(block, TAG_RENAME_TO)
         if rename_to_tag is None: