[gobject-introspection] giscanner: flesh out annotation parsing and storage



commit a2b22ce75937d2d996ef90e0ab683d36031365d0
Author: Dieter Verfaillie <dieterv optionexplicit be>
Date:   Mon Aug 12 16:54:11 2013 +0200

    giscanner: flesh out annotation parsing and storage
    
    - remove annotations regex, restore proper parens parsing
    - drop weird DocOption() storage class and use lists/dicts
      as appropriate
    - make GtkDocAnnotations a simple OrderedDict subclass instead
      of a weird hybrid dict/list storage class
    - Deprecate Attribute: tag, replace with (attributes) annotation
      on the identifier

 giscanner/annotationparser.py                      |  978 ++++++++++++--------
 giscanner/maintransformer.py                       |  117 ++--
 tests/scanner/Makefile.am                          |    1 +
 tests/scanner/annotation.c                         |   14 +-
 tests/scanner/annotation.h                         |    4 +-
 .../annotationparser/gi/annotation_attributes.xml  |  395 ++++++++
 .../gi/annotation_element_type.xml                 |    2 +-
 .../annotationparser/gi/annotation_in_out.xml      |    7 +-
 .../annotationparser/gi/identifier_symbol.xml      |    6 +-
 .../scanner/annotationparser/gi/tag_deprecated.xml |    2 +-
 tests/scanner/annotationparser/gi/tag_since.xml    |    2 +-
 .../scanner/annotationparser/gi/tag_stability.xml  |    2 +-
 tests/scanner/annotationparser/test_parser.py      |   92 ++-
 tests/scanner/annotationparser/test_patterns.py    |  221 ++---
 tests/warn/invalid-element-type.h                  |   10 +-
 15 files changed, 1238 insertions(+), 615 deletions(-)
---
diff --git a/giscanner/annotationparser.py b/giscanner/annotationparser.py
index 26e001e..25445b4 100644
--- a/giscanner/annotationparser.py
+++ b/giscanner/annotationparser.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 # -*- Mode: Python -*-
+
 # GObject-Introspection - a framework for introspecting GObject libraries
 # Copyright (C) 2008-2010 Johan Dahlin
 # Copyright (C) 2012-2013 Dieter Verfaillie <dieterv optionexplicit be>
@@ -109,7 +110,7 @@ import os
 import re
 
 from .collections import OrderedDict
-from .message import Position, warn
+from .message import Position, warn, error
 
 
 # GTK-Doc comment block parts
@@ -148,7 +149,6 @@ DEPRECATED_GI_TAGS = [TAG_RETURN,
                       TAG_RETURNS_VALUE]
 
 #   4) GObject-Introspection annotation tags.
-TAG_ATTRIBUTES = 'attributes'
 TAG_GET_VALUE_FUNC = 'get value func'
 TAG_REF_FUNC = 'ref func'
 TAG_RENAME_TO = 'rename to'
@@ -159,8 +159,7 @@ TAG_UNREF_FUNC = 'unref func'
 TAG_VALUE = 'value'
 TAG_VFUNC = 'virtual'
 
-GI_ANN_TAGS = [TAG_ATTRIBUTES,
-               TAG_GET_VALUE_FUNC,
+GI_ANN_TAGS = [TAG_GET_VALUE_FUNC,
                TAG_REF_FUNC,
                TAG_RENAME_TO,
                TAG_SET_VALUE_FUNC,
@@ -170,12 +169,28 @@ GI_ANN_TAGS = [TAG_ATTRIBUTES,
                TAG_VALUE,
                TAG_VFUNC]
 
-ALL_TAGS = GTKDOC_TAGS + DEPRECATED_GTKDOC_TAGS + DEPRECATED_GI_TAGS + GI_ANN_TAGS
+#   5) Deprecated GObject-Introspection annotation tags.
+#      Accepted by old versions of this module while they should have been
+#      annotations on the identifier part instead.
+#      Note: This list can not be extended ever again. The GObject-Introspection project is not
+#            allowed to invent GTK-Doc tags. Please create new annotations instead.
+TAG_ATTRIBUTES = 'attributes'
+
+DEPRECATED_GI_ANN_TAGS = [TAG_ATTRIBUTES]
+
+ALL_TAGS = (GTKDOC_TAGS + DEPRECATED_GTKDOC_TAGS + DEPRECATED_GI_TAGS + GI_ANN_TAGS +
+            DEPRECATED_GI_ANN_TAGS)
+
+# GObject-Introspection annotation start/end tokens
+ANN_LPAR = '('
+ANN_RPAR = ')'
 
 # GObject-Introspection annotations
+#   1) Supported annotations
+#      Note: when adding new annotations, GTK-Doc project's gtkdoc-mkdb needs to be modified too!
 ANN_ALLOW_NONE = 'allow-none'
 ANN_ARRAY = 'array'
-ANN_ATTRIBUTE = 'attribute'
+ANN_ATTRIBUTES = 'attributes'
 ANN_CLOSURE = 'closure'
 ANN_CONSTRUCTOR = 'constructor'
 ANN_DESTROY = 'destroy'
@@ -183,7 +198,6 @@ ANN_ELEMENT_TYPE = 'element-type'
 ANN_FOREIGN = 'foreign'
 ANN_IN = 'in'
 ANN_INOUT = 'inout'
-ANN_INOUT_ALT = 'in-out'
 ANN_METHOD = 'method'
 ANN_OUT = 'out'
 ANN_SCOPE = 'scope'
@@ -191,24 +205,33 @@ ANN_SKIP = 'skip'
 ANN_TRANSFER = 'transfer'
 ANN_TYPE = 'type'
 
-ALL_ANNOTATIONS = [
-    ANN_ALLOW_NONE,
-    ANN_ARRAY,
-    ANN_ATTRIBUTE,
-    ANN_CLOSURE,
-    ANN_CONSTRUCTOR,
-    ANN_DESTROY,
-    ANN_ELEMENT_TYPE,
-    ANN_FOREIGN,
-    ANN_IN,
-    ANN_INOUT,
-    ANN_INOUT_ALT,
-    ANN_METHOD,
-    ANN_OUT,
-    ANN_SCOPE,
-    ANN_SKIP,
-    ANN_TRANSFER,
-    ANN_TYPE]
+GI_ANNS = [ANN_ALLOW_NONE,
+           ANN_ARRAY,
+           ANN_ATTRIBUTES,
+           ANN_CLOSURE,
+           ANN_CONSTRUCTOR,
+           ANN_DESTROY,
+           ANN_ELEMENT_TYPE,
+           ANN_FOREIGN,
+           ANN_IN,
+           ANN_INOUT,
+           ANN_METHOD,
+           ANN_OUT,
+           ANN_SCOPE,
+           ANN_SKIP,
+           ANN_TRANSFER,
+           ANN_TYPE]
+
+#   2) Deprecated GObject-Introspection annotations
+ANN_ATTRIBUTE = 'attribute'
+ANN_INOUT_ALT = 'in-out'
+
+DEPRECATED_GI_ANNS = [ANN_ATTRIBUTE,
+                      ANN_INOUT_ALT]
+
+ALL_ANNOTATIONS = GI_ANNS + DEPRECATED_GI_ANNS
+DICT_ANNOTATIONS = [ANN_ARRAY, ANN_ATTRIBUTES]
+LIST_ANNOTATIONS = [ann for ann in ALL_ANNOTATIONS if ann not in DICT_ANNOTATIONS]
 
 # (array) annotation options
 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
@@ -318,12 +341,7 @@ SECTION_RE = re.compile(
     ''',
     re.UNICODE | re.VERBOSE)
 
-# Program matching symbol (function, constant, struct and enum) identifiers.
-#
-# Results in 3 symbolic groups:
-#   - group 1 = symbol_name
-#   - group 2 = delimiter
-#   - group 3 = annotations
+# Pattern matching symbol (function, constant, struct and enum) identifiers.
 SYMBOL_RE = re.compile(
     r'''
     ^                                                    # start
@@ -332,44 +350,36 @@ SYMBOL_RE = re.compile(
     \s*                                                  # 0 or more whitespace characters
     (?P<delimiter>:?)                                    # delimiter
     \s*                                                  # 0 or more whitespace characters
-    (?P<annotations>(?:\(.*?\)\s*)*)                     # annotations
+    (?P<fields>.*?)                                      # annotations + description
+    \s*                                                  # 0 or more whitespace characters
+    :?                                                   # invalid delimiter
     \s*                                                  # 0 or more whitespace characters
     $                                                    # end
     ''',
     re.UNICODE | re.VERBOSE)
 
-# Program matching property identifiers.
-#
-# Results in 4 symbolic groups:
-#   - group 1 = class_name
-#   - group 2 = property_name
-#   - group 3 = delimiter
-#   - group 4 = annotations
+# Pattern matching property identifiers.
 PROPERTY_RE = re.compile(
     r'''
     ^                                                    # start
     \s*                                                  # 0 or more whitespace characters
     (?P<class_name>[\w]+)                                # class name
     \s*                                                  # 0 or more whitespace characters
-    :{1}                                                 # required colon
+    :{1}                                                 # 1 required colon
     \s*                                                  # 0 or more whitespace characters
     (?P<property_name>[\w-]*\w)                          # property name
     \s*                                                  # 0 or more whitespace characters
     (?P<delimiter>:?)                                    # delimiter
     \s*                                                  # 0 or more whitespace characters
-    (?P<annotations>(?:\(.*?\)\s*)*)                     # annotations
+    (?P<fields>.*?)                                      # annotations + description
+    \s*                                                  # 0 or more whitespace characters
+    :?                                                   # invalid delimiter
     \s*                                                  # 0 or more whitespace characters
     $                                                    # end
     ''',
     re.UNICODE | re.VERBOSE)
 
-# Program matching signal identifiers.
-#
-# Results in 4 symbolic groups:
-#   - group 1 = class_name
-#   - group 2 = signal_name
-#   - group 3 = delimiter
-#   - group 4 = annotations
+# Pattern matching signal identifiers.
 SIGNAL_RE = re.compile(
     r'''
     ^                                                    # start
@@ -382,19 +392,15 @@ SIGNAL_RE = re.compile(
     \s*                                                  # 0 or more whitespace characters
     (?P<delimiter>:?)                                    # delimiter
     \s*                                                  # 0 or more whitespace characters
-    (?P<annotations>(?:\(.*?\)\s*)*)                     # annotations
+    (?P<fields>.*?)                                      # annotations + description
+    \s*                                                  # 0 or more whitespace characters
+    :?                                                   # invalid delimiter
     \s*                                                  # 0 or more whitespace characters
     $                                                    # end
     ''',
     re.UNICODE | re.VERBOSE)
 
-# Program matching parameters.
-#
-# Results in 4 symbolic groups:
-#   - group 1 = parameter_name
-#   - group 2 = annotations
-#   - group 3 = delimiter
-#   - group 4 = description
+# Pattern matching parameters.
 PARAMETER_RE = re.compile(
     r'''
     ^                                                    # start
@@ -402,63 +408,30 @@ PARAMETER_RE = re.compile(
     @                                                    # @ character
     (?P<parameter_name>[\w-]*\w|.*?\.\.\.)               # parameter name
     \s*                                                  # 0 or more whitespace characters
-    :{1}                                                 # required colon
+    :{1}                                                 # 1 required delimiter
     \s*                                                  # 0 or more whitespace characters
-    (?P<annotations>(?:\(.*?\)\s*)*)                     # annotations
-    (?P<delimiter>:?)                                    # delimiter
-    \s*                                                  # 0 or more whitespace characters
-    (?P<description>.*?)                                 # description
+    (?P<fields>.*?)                                      # annotations + description
     \s*                                                  # 0 or more whitespace characters
     $                                                    # end
     ''',
     re.UNICODE | re.VERBOSE)
 
-# Program matching tags.
-#
-# Results in 4 symbolic groups:
-#   - group 1 = tag_name
-#   - group 2 = annotations
-#   - group 3 = delimiter
-#   - group 4 = description
-_all_tags = '|'.join(ALL_TAGS).replace(' ', '\\ ')
+# Pattern matching tags.
+_all_tags = '|'.join(ALL_TAGS).replace(' ', r'\s')
 TAG_RE = re.compile(
     r'''
     ^                                                    # start
     \s*                                                  # 0 or more whitespace characters
     (?P<tag_name>''' + _all_tags + r''')                 # tag name
     \s*                                                  # 0 or more whitespace characters
-    :{1}                                                 # required colon
-    \s*                                                  # 0 or more whitespace characters
-    (?P<annotations>(?:\(.*?\)\s*)*)                     # annotations
-    (?P<delimiter>:?)                                    # delimiter
+    :{1}                                                 # 1 required delimiter
     \s*                                                  # 0 or more whitespace characters
-    (?P<description>.*?)                                 # description
+    (?P<fields>.*?)                                      # annotations + value + description
     \s*                                                  # 0 or more whitespace characters
     $                                                    # end
     ''',
     re.UNICODE | re.VERBOSE | re.IGNORECASE)
 
-# Program matching multiline annotation continuations.
-# This is used on multiline parameters and tags (but not on the first line) to
-# generate warnings about invalid annotations spanning multiple lines.
-#
-# Results in 3 symbolic groups:
-#   - group 2 = annotations
-#   - group 3 = delimiter
-#   - group 4 = description
-MULTILINE_ANNOTATION_CONTINUATION_RE = re.compile(
-    r'''
-    ^                                                    # start
-    \s*                                                  # 0 or more whitespace characters
-    (?P<annotations>(?:\(.*?\)\s*)*)                     # annotations
-    (?P<delimiter>:)                                     # delimiter
-    \s*                                                  # 0 or more whitespace characters
-    (?P<description>.*?)                                 # description
-    \s*                                                  # 0 or more whitespace characters
-    $                                                    # end
-    ''',
-    re.UNICODE | re.VERBOSE)
-
 # Pattern matching value and description fields for TAG_DEPRECATED & TAG_SINCE tags.
 TAG_VALUE_VERSION_RE = re.compile(
     r'''
@@ -490,82 +463,21 @@ TAG_VALUE_STABILITY_RE = re.compile(
     re.UNICODE | re.VERBOSE | re.IGNORECASE)
 
 
-class DocOption(object):
-
-    __slots__ = ('_array', '_dict')
-
-    def __init__(self, option):
-        self._array = []
-        self._dict = OrderedDict()
-        # (annotation option1=value1 option2=value2) etc
-        for p in option.split(' '):
-            if '=' in p:
-                name, value = p.split('=', 1)
-            else:
-                name = p
-                value = None
-            self._dict[name] = value
-            if value is None:
-                self._array.append(name)
-            else:
-                self._array.append((name, value))
-
-    def __repr__(self):
-        return '<DocOption %r>' % (self._array, )
-
-    def length(self):
-        return len(self._array)
-
-    def one(self):
-        assert len(self._array) == 1
-        return self._array[0]
-
-    def flat(self):
-        return self._array
-
-    def all(self):
-        return self._dict
-
-
-class GtkDocAnnotations(object):
-
-    __slots__ = ('values', 'position')
-
-    def __init__(self):
-        self.values = []
-        self.position = None
-
-    def __repr__(self):
-        return '<GtkDocAnnotations %r>' % (self.values, )
-
-    def __getitem__(self, item):
-        for key, value in self.values:
-            if key == item:
-                return value
-        raise KeyError
-
-    def __nonzero__(self):
-        return bool(self.values)
-
-    def __iter__(self):
-        return (k for k, v in self.values)
-
-    def add(self, name, value):
-        self.values.append((name, value))
+class GtkDocAnnotations(OrderedDict):
+    '''
+    An ordered dictionary mapping annotation names to annotation options (if any). Annotation
+    options can be either a :class:`list`, a :class:`giscanner.collections.OrderedDict`
+    (depending on the annotation name)or :const:`None`.
+    '''
 
-    def get(self, item, default=None):
-        for key, value in self.values:
-            if key == item:
-                return value
-        return default
+    __slots__ = ('position')
 
-    def getall(self, item):
-        for key, value in self.values:
-            if key == item:
-                yield value
+    def __init__(self, position=None):
+        OrderedDict.__init__(self)
 
-    def items(self):
-        return iter(self.values)
+        #: A :class:`giscanner.message.Position` instance specifying the location of the
+        #: annotations in the source file or :const:`None`.
+        self.position = position
 
 
 class GtkDocTag(object):
@@ -584,7 +496,7 @@ class GtkDocTag(object):
 
     def _validate_annotation(self, ann_name, options, required=False,
                              n_params=None, choices=None):
-        if required and options is None:
+        if required and len(options) == 0:
             warn('%s annotation needs a value' % (ann_name, ), self.position)
             return
 
@@ -595,28 +507,25 @@ class GtkDocTag(object):
                 s = 'one value'
             else:
                 s = '%d values' % (n_params, )
-            if ((n_params > 0 and (options is None or options.length() != n_params))
-            or n_params == 0 and options is not None):
-                if options is None:
-                    length = 0
-                else:
-                    length = options.length()
+            if ((n_params > 0 and (len(options) == 0 or len(options) != n_params))
+            or n_params == 0 and len(options) != 0):
+                length = len(options)
                 warn('%s annotation needs %s, not %d' % (ann_name, s, length),
                      self.position)
                 return
 
         if choices is not None:
-            option = options.one()
+            option = options[0]
             if option not in choices:
                 warn('invalid %s annotation value: %r' % (ann_name, option, ),
                      self.position)
                 return
 
     def _validate_array(self, ann_name, options):
-        if options is None:
+        if len(options) == 0:
             return
 
-        for option, value in options.all().items():
+        for option, value in options.items():
             if option in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
                 try:
                     int(value)
@@ -637,29 +546,29 @@ class GtkDocTag(object):
                      self.position)
 
     def _validate_closure(self, ann_name, options):
-        if options is not None and options.length() > 1:
-            warn('closure takes at most 1 value, %d given' % (options.length(), ),
+        if len(options) != 0 and len(options) > 1:
+            warn('closure takes at most 1 value, %d given' % (len(options), ),
                  self.position)
 
     def _validate_element_type(self, ann_name, options):
         self._validate_annotation(ann_name, options, required=True)
-        if options is None:
+        if len(options) == 0:
             warn('element-type takes at least one value, none given',
                  self.position)
             return
-        if options.length() > 2:
-            warn('element-type takes at most 2 values, %d given' % (options.length(), ),
+        if len(options) > 2:
+            warn('element-type takes at most 2 values, %d given' % (len(options), ),
                  self.position)
             return
 
     def _validate_out(self, ann_name, options):
-        if options is None:
+        if len(options) == 0:
             return
-        if options.length() > 1:
-            warn('out annotation takes at most 1 value, %d given' % (options.length(), ),
+        if len(options) > 1:
+            warn('out annotation takes at most 1 value, %d given' % (len(options), ),
                  self.position)
             return
-        option = options.one()
+        option = options[0]
         if option not in [OPT_OUT_CALLEE_ALLOCATES,
                           OPT_OUT_CALLER_ALLOCATES]:
             warn("out annotation value is invalid: %r" % (option, ),
@@ -670,8 +579,11 @@ class GtkDocTag(object):
         def serialize_one(option, value, fmt, fmt2):
             if value:
                 if type(value) != str:
-                    value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
-                                      for k, v in value.all().items()))
+                    if isinstance(value, list):
+                        value = ' '.join(value)
+                    else:
+                        value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
+                                          for k, v in value.items()))
                 return fmt % (option, value)
             else:
                 return fmt2 % (option, )
@@ -698,18 +610,14 @@ class GtkDocTag(object):
                              self.description or '')
 
     def validate(self):
-        if self.name == TAG_ATTRIBUTES:
-            # The 'Attributes:' tag allows free form annotations so the
-            # validation below is most certainly going to fail.
-            return
-
         for ann_name, value in self.annotations.items():
             if ann_name == ANN_ALLOW_NONE:
                 self._validate_annotation(ann_name, value, n_params=0)
             elif ann_name == ANN_ARRAY:
                 self._validate_array(ann_name, value)
-            elif ann_name == ANN_ATTRIBUTE:
-                self._validate_annotation(ann_name, value, n_params=2)
+            elif ann_name == ANN_ATTRIBUTES:
+                # The 'attributes' annotation allows free form annotations.
+                pass
             elif ann_name == ANN_CLOSURE:
                 self._validate_closure(ann_name, value)
             elif ann_name == ANN_DESTROY:
@@ -827,73 +735,34 @@ class GtkDocCommentBlock(object):
 
 
 class GtkDocCommentBlockParser(object):
-    """
-    GTK-Doc comment block parser.
-
+    '''
     Parse GTK-Doc comment blocks into a parse tree built out of :class:`GtkDocCommentBlock`,
-    :class:`GtkDocTag`, :class:`GtkDocAnnotations` and :class:`DocOption` objects. This
-    parser tries to accept malformed input whenever possible and does not emit
-    syntax errors. However, it does emit warnings at the slightest indication
-    of malformed input when possible. It is usually a good idea to heed these
-    warnings as malformed input is known to result in invalid GTK-Doc output.
-
-    A GTK-Doc comment block can be constructed out of multiple parts that can
-    be combined to write different types of documentation.
-    See `GTK-Doc's documentation`_ to learn more about possible valid combinations.
-    Each part can be further divided into fields which are separated by `:` characters.
-
-    Possible parts and the fields they are constructed from look like the
-    following (optional fields are enclosed in square brackets):
-
-    .. code-block:: c
-        /**
-         * identifier_name [:annotations]
-         * @parameter_name [:annotations] [:description]
-         *
-         * comment_block_description
-         * tag_name [:annotations] [:description]
-         */
-
-    The order in which the different parts have to be specified is important::
-
-        - There has to be exactly 1 `identifier` part on the first line of the
-          comment block which consists of:
-              * an `identifier_name` field
-              * an optional `annotations` field
-        - Followed by 0 or more `parameters` parts, each consisting of:
-              * a `parameter_name` field
-              * an optional `annotations` field
-              * an optional `description` field
-        - Followed by at least 1 empty line signaling the beginning of
-          the `comment_block_description` part
-        - Followed by an optional `comment block description` part.
-        - Followed by 0 or more `tag` parts, each consisting of:
-              * a `tag_name` field
-              * an optional `annotations` field
-              * an optional `description` field
-
-    Additionally, the following restrictions are in effect::
-
-        - Parts can optionally be separated by an empty line, except between
-          the `parameter` parts and the `comment block description` part where
-          an empty line is required (see above).
-        - Parts and fields cannot span multiple lines, except for
-          `parameter descriptions`, `tag descriptions` and the
-          `comment_block_description` fields.
-        - `parameter descriptions` fields can not span multiple paragraphs.
-        - `tag descriptions` and `comment block description` fields can
-          span multiple paragraphs.
+    :class:`GtkDocParameter`, :class:`GtkDocTag` and :class:`GtkDocAnnotations`
+    objects. This parser tries to accept malformed input whenever possible and does
+    not cause the process to exit on syntax errors. It does however emit:
+
+        * warning messages at the slightest indication of recoverable malformed input and
+        * error messages for unrecoverable malformed input
+
+    whenever possible. Recoverable, in this context, means that we can serialize the
+    :class:`GtkDocCommentBlock` instance using a :class:`GtkDocCommentBlockWriter` without
+    information being lost. It is usually a good idea to heed these warning and error messages
+    as malformed input can result in both:
+
+        * invalid GTK-Doc output (HTML, pdf, ...) when the comment blocks are parsed
+          with GTK-Doc's gtkdoc-mkdb
+        * unexpected introspection behavior, for example missing parameters in the
+          generated .gir and .typelib files
 
     .. NOTE:: :class:`GtkDocCommentBlockParser` functionality is heavily based on gtkdoc-mkdb's
         `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
         commit `47abcd5`_.
 
-    .. _GTK-Doc's documentation:
-            http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
     .. _ScanSourceFile():
-            http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
-    .. _47abcd5: 47abcd53b8489ebceec9e394676512a181c1f1f6
-    """
+           http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
+    .. _47abcd5:
+           https://git.gnome.org/browse/gtk-doc/commit/?id=47abcd53b8489ebceec9e394676512a181c1f1f6
+    '''
 
     def parse_comment_blocks(self, comments):
         '''
@@ -1030,32 +899,40 @@ class GtkDocCommentBlockParser(object):
             ####################################################################
             # Check for GTK-Doc comment block identifier.
             ####################################################################
-            if not comment_block:
+            if comment_block is None:
                 result = SECTION_RE.match(line)
 
                 if result:
                     identifier_name = 'SECTION:%s' % (result.group('section_name'), )
-                    column = result.start('section_name') + column_offset
+                    identifier_delimiter = None
+                    identifier_fields = None
+                    identifier_fields_start = None
                 else:
                     result = PROPERTY_RE.match(line)
 
                     if result:
                         identifier_name = '%s:%s' % (result.group('class_name'),
                                                      result.group('property_name'))
-                        column = result.start('property_name') + column_offset
+                        identifier_delimiter = result.group('delimiter')
+                        identifier_fields = result.group('fields')
+                        identifier_fields_start = result.start('fields')
                     else:
                         result = SIGNAL_RE.match(line)
 
                         if result:
                             identifier_name = '%s::%s' % (result.group('class_name'),
                                                           result.group('signal_name'))
-                            column = result.start('signal_name') + column_offset
+                            identifier_delimiter = result.group('delimiter')
+                            identifier_fields = result.group('fields')
+                            identifier_fields_start = result.start('fields')
                         else:
                             result = SYMBOL_RE.match(line)
 
                             if result:
                                 identifier_name = '%s' % (result.group('symbol_name'), )
-                                column = result.start('symbol_name') + column_offset
+                                identifier_delimiter = result.group('delimiter')
+                                identifier_fields = result.group('fields')
+                                identifier_fields_start = result.start('fields')
 
                 if result:
                     in_part = PART_IDENTIFIER
@@ -1064,17 +941,26 @@ class GtkDocCommentBlockParser(object):
                     comment_block = GtkDocCommentBlock(identifier_name)
                     comment_block.position = position
 
-                    if 'annotations' in result.groupdict() and result.group('annotations') != '':
-                        comment_block.annotations = self.parse_annotations(comment_block,
-                                                                       result.group('annotations'))
+                    if identifier_fields:
+                        (a, d) = self._parse_fields(position,
+                                                    column_offset + identifier_fields_start,
+                                                    original_line,
+                                                    identifier_fields, True, False)
+                        if d:
+                            # Not an identifier due to invalid trailing description field
+                            in_part = None
+                            part_indent = None
+                            comment_block = None
+                            result = None
+                        else:
+                            comment_block.annotations = a
 
-                        if 'delimiter' in result.groupdict() and result.group('delimiter') != ':':
-                            delimiter_start = result.start('delimiter')
-                            delimiter_column = column_offset + delimiter_start
-                            marker = ' ' * delimiter_column + '^'
-                            warn("missing ':' at column %s:\n%s\n%s" %
-                                 (delimiter_column + 1, original_line, marker),
-                                 position)
+                            if not identifier_delimiter and a:
+                                marker_position = column_offset + result.start('delimiter')
+                                marker = ' ' * marker_position + '^'
+                                warn('missing ":" at column %s:\n%s\n%s' %
+                                     (marker_position + 1, original_line, marker),
+                                     position)
 
                 if not result:
                     # Emit a single warning when the identifier is not found on the first line
@@ -1091,10 +977,11 @@ class GtkDocCommentBlockParser(object):
             ####################################################################
             result = PARAMETER_RE.match(line)
             if result:
-                marker = ' ' * (result.start('parameter_name') + column_offset) + '^'
                 param_name = result.group('parameter_name')
-                param_annotations = result.group('annotations')
-                param_description = result.group('description')
+                param_name_lower = param_name.lower()
+                param_fields = result.group('fields')
+                param_fields_start = result.start('fields')
+                marker = ' ' * (result.start('parameter_name') + column_offset) + '^'
 
                 if in_part == PART_IDENTIFIER:
                     in_part = PART_PARAMETERS
@@ -1109,7 +996,7 @@ class GtkDocCommentBlockParser(object):
 
                 # Old style GTK-Doc allowed return values to be specified as
                 # parameters instead of tags.
-                if param_name.lower() == TAG_RETURNS:
+                if param_name_lower == TAG_RETURNS:
                     param_name = TAG_RETURNS
 
                     if not returns_seen:
@@ -1133,9 +1020,14 @@ class GtkDocCommentBlockParser(object):
 
                 tag = GtkDocTag(param_name)
                 tag.position = position
-                tag.description = param_description
-                if param_annotations:
-                    tag.annotations = self.parse_annotations(tag, param_annotations)
+
+                if param_fields:
+                    (a, d) = self._parse_fields(position,
+                                                column_offset + param_fields_start,
+                                                original_line, param_fields)
+                    tag.annotations = a
+                    tag.description = d
+
                 if param_name == TAG_RETURNS:
                     comment_block.tags[param_name] = tag
                 else:
@@ -1146,9 +1038,13 @@ class GtkDocCommentBlockParser(object):
             ####################################################################
             # Check for comment block description.
             #
-            # When we are parsing comment block parameters or the comment block
-            # identifier (when there are no parameters) and encounter an empty
-            # line, we must be parsing the comment block description.
+            # When we are parsing parameter parts or the identifier part (when
+            # there are no parameters) and encounter an empty line, we must be
+            # parsing the comment block description.
+            #
+            # Note: it is unclear why GTK-Doc does not allow paragraph breaks
+            #       at this location as those might be handy describing
+            #       parameters from time to time...
             ####################################################################
             if (EMPTY_LINE_RE.match(line) and in_part in [PART_IDENTIFIER, PART_PARAMETERS]):
                 in_part = PART_DESCRIPTION
@@ -1160,42 +1056,99 @@ class GtkDocCommentBlockParser(object):
             ####################################################################
             result = TAG_RE.match(line)
             if result and line_indent <= part_indent:
+                part_indent = line_indent
                 tag_name = result.group('tag_name')
-                tag_annotations = result.group('annotations')
-                tag_description = result.group('description')
-
+                tag_name_lower = tag_name.lower()
+                tag_fields = result.group('fields')
+                tag_fields_start = result.start('fields')
                 marker = ' ' * (result.start('tag_name') + column_offset) + '^'
 
-                # Deprecated GTK-Doc Description: tag
-                if tag_name.lower() == TAG_DESCRIPTION:
-                    warn("GTK-Doc tag \"Description:\" has been deprecated:\n%s\n%s" %
+                if tag_name_lower in DEPRECATED_GI_ANN_TAGS:
+                    # Deprecated GObject-Introspection specific tags.
+                    # Emit a warning and transform these into annotations on the identifier
+                    # instead, as agreed upon in http://bugzilla.gnome.org/show_bug.cgi?id=676133
+                    warn('GObject-Introspection specific GTK-Doc tag "%s" '
+                         'has been deprecated, please use annotations on the identifier '
+                         'instead:\n%s\n%s' % (tag_name, original_line, marker),
+                         position)
+
+                    # Translate deprecated tag name into corresponding annotation name
+                    ann_name = tag_name_lower.replace(' ', '-')
+
+                    if tag_name_lower == TAG_ATTRIBUTES:
+                        transformed = ''
+                        (a, d) = self._parse_fields(position,
+                                                    result.start('tag_name') + column_offset,
+                                                    line,
+                                                    tag_fields.strip(),
+                                                    False,
+                                                    False)
+
+                        if a:
+                            for annotation in a:
+                                ann_options = self._parse_annotation_options_list(position, marker,
+                                                                                  line, annotation)
+                                n_options = len(ann_options)
+                                if n_options == 1:
+                                    transformed = '%s %s' % (transformed, ann_options[0], )
+                                elif n_options == 2:
+                                    transformed = '%s %s=%s' % (transformed, ann_options[0],
+                                                                ann_options[1])
+                                else:
+                                    # Malformed Attributes: tag
+                                    error('malformed "Attributes:" tag will be ignored:\n%s\n%s' %
+                                          (original_line, marker),
+                                          position)
+                                    transformed = None
+
+                            if transformed:
+                                transformed = '%s %s' % (ann_name, transformed.strip())
+                                ann_name, docannotation = self._parse_annotation(
+                                    position,
+                                    column_offset + tag_fields_start,
+                                    original_line,
+                                    transformed)
+                                stored_annotation = comment_block.annotations.get('attributes')
+                                if stored_annotation:
+                                    error('Duplicate "Attributes:" annotation will '
+                                          'be ignored:\n%s\n%s' % (original_line, marker),
+                                          position)
+                                else:
+                                    comment_block.annotations[ann_name] = docannotation
+                    else:
+                        ann_name, options = self._parse_annotation(position,
+                                                               column_offset + tag_fields_start,
+                                                               line,
+                                                               '%s %s' % (ann_name, tag_fields))
+                        comment_block.annotations[ann_name] = options
+
+                    continue
+                elif tag_name_lower == TAG_DESCRIPTION:
+                    # Deprecated GTK-Doc Description: tag
+                    warn('GTK-Doc tag "Description:" has been deprecated:\n%s\n%s' %
                          (original_line, marker),
                          position)
 
                     in_part = PART_DESCRIPTION
-                    part_indent = line_indent
 
                     if not comment_block.description:
-                        comment_block.description = tag_description
+                        comment_block.description = tag_fields
                     else:
-                        comment_block.description += '\n' + tag_description
+                        comment_block.description += '\n' + tag_fields
                     continue
 
                 # Now that the deprecated stuff is out of the way, continue parsing real tags
                 if in_part == PART_DESCRIPTION:
                     in_part = PART_TAGS
 
-                part_indent = line_indent
-
                 if in_part != PART_TAGS:
                     column = result.start('tag_name') + column_offset
-                    marker = ' ' * column + '^'
                     warn("'%s:' tag unexpected at this location:\n%s\n%s" %
                          (tag_name, original_line, marker),
                          position)
 
-                if tag_name.lower() in [TAG_RETURN, TAG_RETURNS,
-                                        TAG_RETURN_VALUE, TAG_RETURNS_VALUE]:
+                if tag_name_lower in [TAG_RETURN, TAG_RETURNS,
+                                      TAG_RETURN_VALUE, TAG_RETURNS_VALUE]:
                     if not returns_seen:
                         returns_seen = True
                     else:
@@ -1205,44 +1158,49 @@ class GtkDocCommentBlockParser(object):
 
                     tag = GtkDocTag(TAG_RETURNS)
                     tag.position = position
-                    tag.description = tag_description
-                    if tag_annotations:
-                        tag.annotations = self.parse_annotations(tag, tag_annotations)
+
+                    if tag_fields:
+                        (a, d) = self._parse_fields(position,
+                                                    column_offset + tag_fields_start,
+                                                    original_line,
+                                                    tag_fields)
+                        tag.annotations = a
+                        tag.description = d
+
                     comment_block.tags[TAG_RETURNS] = tag
                     current_tag = tag
                     continue
                 else:
-                    if tag_name.lower() in comment_block.tags.keys():
-                        column = result.start('tag_name') + column_offset
-                        marker = ' ' * column + '^'
+                    if tag_name_lower in comment_block.tags.keys():
                         warn("multiple '%s:' tags for identifier '%s':\n%s\n%s" %
                              (tag_name, comment_block.name, original_line, marker),
                              position)
 
-                    tag = GtkDocTag(tag_name.lower())
+                    tag = GtkDocTag(tag_name_lower)
                     tag.position = position
 
-                    if tag_annotations:
-                        if tag_name.lower() == TAG_ATTRIBUTES:
-                            tag.annotations = self.parse_annotations(tag, tag_annotations)
-                        else:
-                            warn("annotations not supported for tag '%s:'." %
-                                 (tag_name, ),
-                                 position)
-
-                    if tag_name.lower() in [TAG_DEPRECATED, TAG_SINCE]:
-                        result = TAG_VALUE_VERSION_RE.match(tag_description)
-                        tag.value = result.group('value')
-                        tag.description = result.group('description')
-                    elif tag_name.lower() == TAG_STABILITY:
-                        result = TAG_VALUE_STABILITY_RE.match(tag_description)
-                        tag.value = result.group('value').capitalize()
-                        tag.description = result.group('description')
-                    elif tag_name.lower() in GI_ANN_TAGS:
-                        tag.value = tag_description
-                        tag.description = ''
-
-                    comment_block.tags[tag_name.lower()] = tag
+                    if tag_fields:
+                        (a, d) = self._parse_fields(position,
+                                                    column_offset + tag_fields_start,
+                                                    original_line,
+                                                    tag_fields)
+                        if a:
+                            error('annotations not supported for tag "%s:".' % (tag_name, ),
+                                  position)
+
+                        if tag_name_lower in [TAG_DEPRECATED, TAG_SINCE]:
+                            result = TAG_VALUE_VERSION_RE.match(d)
+                            tag.value = result.group('value')
+                            tag.description = result.group('description')
+                        elif tag_name_lower == TAG_STABILITY:
+                            result = TAG_VALUE_STABILITY_RE.match(d)
+                            tag.value = result.group('value').capitalize()
+                            tag.description = result.group('description')
+                        elif tag_name_lower in GI_ANN_TAGS:
+                            tag.value = d
+                            tag.description = ''
+
+                    comment_block.tags[tag_name_lower] = tag
                     current_tag = tag
                     continue
 
@@ -1257,16 +1215,22 @@ class GtkDocCommentBlockParser(object):
                     comment_block.description += '\n' + line
                 continue
             elif in_part == PART_PARAMETERS:
-                self._validate_multiline_annotation_continuation(line, original_line,
-                                                                 column_offset, position)
+                if not current_param.description:
+                    self._validate_multiline_annotation_continuation(line, original_line,
+                                                                     column_offset, position)
                 # Append to parameter description.
-                current_param.description += ' ' + line.strip()
+                if current_param.description is None:
+                    current_param.description = line
+                else:
+                    current_param.description += ' ' + line.strip()
                 continue
             elif in_part == PART_TAGS:
-                self._validate_multiline_annotation_continuation(line, original_line,
-                                                                 column_offset, position)
+                if not current_tag.description:
+                    self._validate_multiline_annotation_continuation(line, original_line,
+                                                                     column_offset, position)
+
+                # Append to tag description.
                 current_tag.description += ' ' + line.strip()
-                continue
 
         ########################################################################
         # Finished parsing this comment block.
@@ -1303,48 +1267,328 @@ class GtkDocCommentBlockParser(object):
     def _validate_multiline_annotation_continuation(self, line, original_line,
                                                     column_offset, position):
         '''
-        Validate parameters and tags (except the first line) and generate
-        warnings about invalid annotations spanning multiple lines.
+        Validate annotatable parts' source text ensuring annotations don't span multiple lines.
+        For example, the following comment block would result in a warning being emitted for
+        the forth line::
+
+            /**
+             * shiny_function:
+             * @array_: (out caller-allocates) (array)
+             *          (element-type utf8) (transfer full): A beautiful array
+             */
+
+        :param line: line to validate, stripped from  ("``*/``") at start of the line.
+        :param original_line: original line (including  ("``*/``"))  being validated
+        :param column_offset: number of characters stripped from `line` when   ("``*/``")
+                              was removed
+        :param position: :class:`giscanner.message.Position` of `line` in the source file
+        '''
+
+        success, annotations, start_pos, end_pos = self._parse_annotations(position, column_offset,
+                                                                           original_line, line,
+                                                                           False)
+        if annotations:
+            marker = ' ' * (start_pos + column_offset) + '^'
+            warn('ignoring invalid multiline annotation continuation:\n%s\n%s' %
+                 (original_line, marker),
+                 position)
 
-        :param line: line to validate, stripped from ' * ' at start of the line.
-        :param original_line: original line to validate (used in warning messages)
-        :param column_offset: column width of ' * ' at the time it was stripped from `line`
-        :param position: position of `line` in the source file
+    def _parse_annotation_options_list(self, position, column, line, options):
+        '''
+        Parse annotation options into a list. For example::
+
+            ┌──────────────────────────────────────────────────────────────┐
+            │ 'option1 option2 option3'                                    │ ─▷ source
+            ├──────────────────────────────────────────────────────────────┤
+            │ ['option1', 'option2', 'option3']                            │ ◁─ parsed options
+            └──────────────────────────────────────────────────────────────┘
+
+        :param position: :class:`giscanner.message.Position` of `line` in the source file
+        :param column: start column of the `options` in the source file
+        :param line: complete source line
+        :param options: annotation options to parse
+        :returns: a list of annotation options
         '''
 
-        result = MULTILINE_ANNOTATION_CONTINUATION_RE.match(line)
-        if result:
-            column = result.start('annotations') + column_offset
-            marker = ' ' * column + '^'
-            warn('ignoring invalid multiline annotation continuation:\n'
-                 '%s\n%s' % (original_line, marker),
+        parsed = []
+
+        if options:
+            result = options.find('=')
+            if result >= 0:
+                marker = ' ' * (column + result) + '^'
+                warn('invalid annotation options: expected a "list" but '
+                     'received "key=value pairs":\n%s\n%s' % (line, marker),
+                     position)
+                parsed = self._parse_annotation_options_unknown(position, column, line, options)
+            else:
+                parsed = options.split(' ')
+
+        return parsed
+
+    def _parse_annotation_options_dict(self, position, column, line, options):
+        '''
+        Parse annotation options into a dict. For example::
+
+            ┌──────────────────────────────────────────────────────────────┐
+            │ 'option1=value1 option2 option3=value2'                      │ ─▷ source
+            ├──────────────────────────────────────────────────────────────┤
+            │ {'option1': 'value1', 'option2': None, 'option3': 'value2'}  │ ◁─ parsed options
+            └──────────────────────────────────────────────────────────────┘
+
+        :param position: :class:`giscanner.message.Position` of `line` in the source file
+        :param column: start column of the `options` in the source file
+        :param line: complete source line
+        :param options: annotation options to parse
+        :returns: an ordered dictionary of annotation options
+        '''
+
+        parsed = OrderedDict()
+
+        if options:
+            for p in options.split(' '):
+                parts = p.split('=', 1)
+                key = parts[0]
+                value = parts[1] if len(parts) == 2 else None
+                parsed[key] = value
+
+        return parsed
+
+    def _parse_annotation_options_unknown(self, position, column, line, options):
+        '''
+        Parse annotation options into a list holding a single item. This is used when the
+        annotation options to parse in not known to be a list nor dict. For example::
+
+            ┌──────────────────────────────────────────────────────────────┐
+            │ '   option1 option2   option3=value1   '                     │ ─▷ source
+            ├──────────────────────────────────────────────────────────────┤
+            │ ['option1 option2   option3=value1']                         │ ◁─ parsed options
+            └──────────────────────────────────────────────────────────────┘
+
+        :param position: :class:`giscanner.message.Position` of `line` in the source file
+        :param column: start column of the `options` in the source file
+        :param line: complete source line
+        :param options: annotation options to parse
+        :returns: a list of annotation options
+        '''
+
+        if options:
+            return [options.strip()]
+
+    def _parse_annotation(self, position, column, line, annotation):
+        '''
+        Parse an annotation into the annotation name and a list or dict (depending on the
+        name of the annotation) holding the options. For example::
+
+            ┌──────────────────────────────────────────────────────────────┐
+            │ 'name opt1=value1 opt2=value2 opt3'                          │ ─▷ source
+            ├──────────────────────────────────────────────────────────────┤
+            │ 'name', {'opt1': 'value1', 'opt2':'value2', 'opt3':None}     │ ◁─ parsed annotation
+            └──────────────────────────────────────────────────────────────┘
+
+            ┌──────────────────────────────────────────────────────────────┐
+            │ 'name   opt1 opt2'                                           │ ─▷ source
+            ├──────────────────────────────────────────────────────────────┤
+            │ 'name', ['opt1', 'opt2']                                     │ ◁─ parsed annotation
+            └──────────────────────────────────────────────────────────────┘
+
+            ┌──────────────────────────────────────────────────────────────┐
+            │ 'unkownname   unknown list of options'                       │ ─▷ source
+            ├──────────────────────────────────────────────────────────────┤
+            │ 'unkownname', ['unknown list of options']                    │ ◁─ parsed annotation
+            └──────────────────────────────────────────────────────────────┘
+
+        :param position: :class:`giscanner.message.Position` of `line` in the source file
+        :param column: start column of the `annotation` in the source file
+        :param line: complete source line
+        :param annotation: annotation to parse
+        :returns: a tuple containing the annotation name and options
+        '''
+
+        # Transform deprecated type syntax "tokens"
+        annotation = annotation.replace('<', ANN_LPAR).replace('>', ANN_RPAR)
+
+        parts = annotation.split(' ', 1)
+        ann_name = parts[0].lower()
+        ann_options = parts[1] if len(parts) == 2 else None
+
+        if ann_name == ANN_INOUT_ALT:
+            marker = ' ' * (column) + '^'
+            warn('"%s" annotation has been deprecated, please use "%s" instead:\n%s\n%s' %
+                 (ANN_INOUT_ALT, ANN_INOUT, line, marker),
                  position)
 
-    @classmethod
-    def parse_annotations(cls, tag, value):
-        # (annotation)
-        # (annotation opt1 opt2 ...)
-        # (annotation opt1=value1 opt2=value2 ...)
-        opened = -1
-        annotations = GtkDocAnnotations()
-        annotations.position = tag.position
-
-        for i, c in enumerate(value):
-            if c == '(' and opened == -1:
-                opened = i + 1
-            if c == ')' and opened != -1:
-                segment = value[opened:i]
-                parts = segment.split(' ', 1)
-                if len(parts) == 2:
-                    name, option = parts
-                elif len(parts) == 1:
-                    name = parts[0]
-                    option = None
+            ann_name = ANN_INOUT
+        elif ann_name == ANN_ATTRIBUTE:
+            marker = ' ' * (column) + '^'
+            warn('"%s" annotation has been deprecated, please use "%s" instead:\n%s\n%s' %
+                 (ANN_ATTRIBUTE, ANN_ATTRIBUTES, line, marker),
+                 position)
+
+            ann_name = ANN_ATTRIBUTES
+            ann_options = self._parse_annotation_options_list(position, column, line, ann_options)
+            n_options = len(ann_options)
+            if n_options == 1:
+                ann_options = ann_options[0]
+            elif n_options == 2:
+                ann_options = '%s=%s' % (ann_options[0], ann_options[1])
+            else:
+                marker = ' ' * (column) + '^'
+                error('malformed "(attribute)" annotation will be ignored:\n%s\n%s' %
+                      (line, marker),
+                      position)
+                return None, None
+
+        column += len(ann_name) + 2
+
+        if ann_name in LIST_ANNOTATIONS:
+            ann_options = self._parse_annotation_options_list(position, column, line, ann_options)
+        elif ann_name in DICT_ANNOTATIONS:
+            ann_options = self._parse_annotation_options_dict(position, column, line, ann_options)
+        else:
+            ann_options = self._parse_annotation_options_unknown(position, column, line,
+                                                                 ann_options)
+
+        return ann_name, ann_options
+
+    def _parse_annotations(self, position, column, line, fields, parse_options=True):
+        '''
+        Parse annotations into a :class:`GtkDocAnnotations` object.
+
+        :param position: :class:`giscanner.message.Position` of `line` in the source file
+        :param column: start column of the `annotations` in the source file
+        :param line: complete source line
+        :param fields: string containing the fields to parse
+        :param parse_options: whether options will be parsed into a :class:`GtkDocAnnotations`
+                              object or into a :class:`list`
+        :returns: if `parse_options` evaluates to True a :class:`GtkDocAnnotations` object,
+                  a :class:`list` otherwise. If `line` does not contain any annotations,
+                  :const:`None`
+        '''
+
+        if parse_options:
+            parsed_annotations = GtkDocAnnotations(position)
+        else:
+            parsed_annotations = []
+
+        i = 0
+        parens_level = 0
+        prev_char = ''
+        char_buffer = []
+        start_pos = 0
+        end_pos = 0
+
+        for i, cur_char in enumerate(fields):
+            cur_char_is_space = cur_char.isspace()
+
+            if cur_char == ANN_LPAR:
+                parens_level += 1
+
+                if parens_level == 1:
+                    start_pos = i
+
+                if prev_char == ANN_LPAR:
+                    marker = ' ' * (column + i) + '^'
+                    error('unexpected parentheses, annotations will be ignored:\n%s\n%s' %
+                          (line, marker),
+                          position)
+                    return (False, None, None, None)
+                elif parens_level > 1:
+                    char_buffer.append(cur_char)
+            elif cur_char == ANN_RPAR:
+                parens_level -= 1
+
+                if prev_char == ANN_LPAR:
+                    marker = ' ' * (column + i) + '^'
+                    error('unexpected parentheses, annotations will be ignored:\n%s\n%s' %
+                          (line, marker),
+                          position)
+                    return (False, None, None, None)
+                elif parens_level < 0:
+                    marker = ' ' * (column + i) + '^'
+                    error('unbalanced parentheses, annotations will be ignored:\n%s\n%s' %
+                          (line, marker),
+                          position)
+                    return (False, None, None, None)
+                elif parens_level == 0:
+                    end_pos = i + 1
+
+                    if parse_options is True:
+                        name, options = self._parse_annotation(position,
+                                                               column + start_pos,
+                                                               line,
+                                                               ''.join(char_buffer).strip())
+                        if name is not None:
+                            if name in parsed_annotations:
+                                marker = ' ' * (column + i) + '^'
+                                error('multiple "%s" annotations:\n%s\n%s' %
+                                      (name, line, marker), position)
+                            parsed_annotations[name] = options
+                    else:
+                        parsed_annotations.append(''.join(char_buffer).strip())
+
+                    char_buffer = []
+                else:
+                    char_buffer.append(cur_char)
+            elif cur_char_is_space:
+                if parens_level > 0:
+                    char_buffer.append(cur_char)
+            else:
+                if parens_level == 0:
+                    break
                 else:
-                    raise AssertionError
-                if option is not None:
-                    option = DocOption(option)
-                annotations.add(name, option)
-                opened = -1
+                    char_buffer.append(cur_char)
+
+            prev_char = cur_char
+
+        if parens_level > 0:
+            marker = ' ' * (column + i) + '^'
+            error('unbalanced parentheses, annotations will be ignored:\n%s\n%s' %
+                  (line, marker),
+                  position)
+            return (False, None, None, None)
+        else:
+            return (True, parsed_annotations, start_pos, end_pos)
+
+    def _parse_fields(self, position, column, line, fields, parse_options=True,
+                      validate_description_field=True):
+        '''
+        Parse annotations out of field data. For example::
+
+            ┌──────────────────────────────────────────────────────────────┐
+            │ '(skip): description of some parameter                       │ ─▷ source
+            ├──────────────────────────────────────────────────────────────┤
+            │ ({'skip': []}, 'description of some parameter')              │ ◁─ annotations and
+            └──────────────────────────────────────────────────────────────┘    remaining fields
+
+        :param position: :class:`giscanner.message.Position` of `line` in the source file
+        :param column: start column of `fields` in the source file
+        :param line: complete source line
+        :param fields: string containing the fields to parse
+        :param parse_options: whether options will be parsed into a :class:`GtkDocAnnotations`
+                              object or into a :class:`list`
+        :param validate_description_field: :const:`True` to validate the description field
+        :returns: if `parse_options` evaluates to True a :class:`GtkDocAnnotations` object,
+                  a :class:`list` otherwise. If `line` does not contain any annotations,
+                  :const:`None` and a string holding the remaining fields
+        '''
+        description_field = ''
+        success, annotations, start_pos, end_pos = self._parse_annotations(position,
+                                                                           column,
+                                                                           line,
+                                                                           fields,
+                                                                           parse_options)
+        if success:
+            description_field = fields[end_pos:].strip()
+
+            if description_field and validate_description_field:
+                if description_field.startswith(':'):
+                    description_field = description_field[1:]
+                else:
+                    if end_pos > 0:
+                        marker_position = column + end_pos
+                        marker = ' ' * marker_position + '^'
+                        warn('missing ":" at column %s:\n%s\n%s' %
+                             (marker_position + 1, line, marker),
+                             position)
 
-        return annotations
+        return (annotations, description_field)
diff --git a/giscanner/maintransformer.py b/giscanner/maintransformer.py
index af9a0a1..6dd35bc 100644
--- a/giscanner/maintransformer.py
+++ b/giscanner/maintransformer.py
@@ -26,7 +26,7 @@ from .annotationparser import (TAG_VFUNC, TAG_SINCE, TAG_DEPRECATED, TAG_RETURNS
                                TAG_UNREF_FUNC, TAG_REF_FUNC, TAG_SET_VALUE_FUNC,
                                TAG_GET_VALUE_FUNC, TAG_VALUE, TAG_TRANSFER,
                                TAG_STABILITY)
-from .annotationparser import (ANN_ALLOW_NONE, ANN_ARRAY, ANN_ATTRIBUTE,
+from .annotationparser import (ANN_ALLOW_NONE, ANN_ARRAY, ANN_ATTRIBUTES,
                                ANN_ELEMENT_TYPE, ANN_IN, ANN_INOUT,
                                ANN_INOUT_ALT, ANN_OUT, ANN_SCOPE,
                                ANN_TYPE, ANN_CLOSURE, ANN_DESTROY, ANN_TRANSFER, ANN_SKIP,
@@ -140,6 +140,8 @@ class MainTransformer(object):
         if not rename_to:
             return
         rename_to = rename_to.value
+        if not rename_to:
+            return
         target = self._namespace.get_by_symbol(rename_to)
         if not target:
             message.warn_node(node,
@@ -349,15 +351,9 @@ class MainTransformer(object):
                              annotations.position)
 
     def _apply_annotations_array(self, parent, node, annotations):
-        array_opt = annotations.get(ANN_ARRAY)
-        if array_opt:
-            array_values = array_opt.all()
-        else:
-            array_values = {}
-
-        element_type = annotations.get(ANN_ELEMENT_TYPE)
-        if element_type is not None:
-            element_type_node = self._resolve(element_type.one(),
+        element_type_options = annotations.get(ANN_ELEMENT_TYPE)
+        if element_type_options:
+            element_type_node = self._resolve(element_type_options[0],
                                               node.type, node, parent)
         elif isinstance(node.type, ast.Array):
             element_type_node = node.type.element_type
@@ -366,24 +362,24 @@ class MainTransformer(object):
             # and no (element-type) means array of Foo
             element_type_node = node.type.clone()
             # The element's ctype is the array's dereferenced
-            if element_type_node.ctype is not None and \
-                    element_type_node.ctype.endswith('*'):
+            if element_type_node.ctype is not None and element_type_node.ctype.endswith('*'):
                 element_type_node.ctype = element_type_node.ctype[:-1]
 
         if isinstance(node.type, ast.Array):
             array_type = node.type.array_type
         else:
             array_type = None
-        container_type = ast.Array(array_type, element_type_node,
-                               ctype=node.type.ctype,
-                               is_const=node.type.is_const)
-        if OPT_ARRAY_ZERO_TERMINATED in array_values:
-            container_type.zeroterminated = array_values.get(
-                OPT_ARRAY_ZERO_TERMINATED) == '1'
+
+        array_options = annotations.get(ANN_ARRAY)
+        container_type = ast.Array(array_type, element_type_node, ctype=node.type.ctype,
+                                   is_const=node.type.is_const)
+        if OPT_ARRAY_ZERO_TERMINATED in array_options:
+            container_type.zeroterminated = array_options.get(OPT_ARRAY_ZERO_TERMINATED) == '1'
         else:
             container_type.zeroterminated = False
-        length = array_values.get(OPT_ARRAY_LENGTH)
-        if length is not None:
+
+        length = array_options.get(OPT_ARRAY_LENGTH)
+        if length:
             paramname = self._get_validate_parameter_name(parent, length, node)
             if paramname:
                 param = parent.get_parameter(paramname)
@@ -391,11 +387,11 @@ class MainTransformer(object):
                 if param.direction == ast.PARAM_DIRECTION_OUT:
                     param.transfer = ast.PARAM_TRANSFER_FULL
                 container_type.length_param_name = param.argname
-        fixed = array_values.get(OPT_ARRAY_FIXED_SIZE)
+        fixed = array_options.get(OPT_ARRAY_FIXED_SIZE)
         if fixed:
             try:
                 container_type.size = int(fixed)
-            except ValueError:
+            except (TypeError, ValueError):
                 # Already warned in annotationparser.py
                 return
         node.type = container_type
@@ -410,34 +406,33 @@ class MainTransformer(object):
             return
 
         if isinstance(node.type, ast.List):
-            if element_type_opt.length() != 1:
+            if len(element_type_opt) != 1:
                 message.warn(
                     'element-type annotation for a list must have exactly '
-                    'one option, not %d options' % (element_type_opt.length(), ),
+                    'one option, not %d options' % (len(element_type_opt), ),
                     annotations.position)
                 return
-            node.type.element_type = self._resolve(element_type_opt.one(),
+            node.type.element_type = self._resolve(element_type_opt[0],
                                                    node.type, node, parent)
         elif isinstance(node.type, ast.Map):
-            if element_type_opt.length() != 2:
+            if len(element_type_opt) != 2:
                 message.warn(
                     'element-type annotation for a hash table must have exactly '
-                    'two options, not %d option(s)' % (element_type_opt.length(), ),
+                    'two options, not %d option(s)' % (len(element_type_opt), ),
                     annotations.position)
                 return
-            element_type = element_type_opt.flat()
-            node.type.key_type = self._resolve(element_type[0],
+            node.type.key_type = self._resolve(element_type_opt[0],
                                                node.type, node, parent)
-            node.type.value_type = self._resolve(element_type[1],
+            node.type.value_type = self._resolve(element_type_opt[1],
                                                  node.type, node, parent)
         elif isinstance(node.type, ast.Array):
-            if element_type_opt.length() != 1:
+            if len(element_type_opt) != 1:
                 message.warn(
                     'element-type annotation for an array must have exactly '
-                    'one option, not %d options' % (element_type_opt.length(), ),
+                    'one option, not %d options' % (len(element_type_opt), ),
                     annotations.position)
                 return
-            node.type.element_type = self._resolve(element_type_opt.one(),
+            node.type.element_type = self._resolve(element_type_opt[0],
                                                    node.type, node, parent)
         else:
             message.warn_node(parent,
@@ -529,19 +524,18 @@ class MainTransformer(object):
 
         param_type = annotations.get(ANN_TYPE)
         if param_type:
-            node.type = self._resolve_toplevel(param_type.one(),
+            node.type = self._resolve_toplevel(param_type[0],
                                                node.type, node, parent)
 
         caller_allocates = False
         annotated_direction = None
-        if (ANN_INOUT in annotations or ANN_INOUT_ALT in annotations):
+        if ANN_INOUT in annotations:
             annotated_direction = ast.PARAM_DIRECTION_INOUT
         elif ANN_OUT in annotations:
-            subtype = annotations[ANN_OUT]
-            if subtype is not None:
-                subtype = subtype.one()
             annotated_direction = ast.PARAM_DIRECTION_OUT
-            if subtype in (None, ''):
+
+            options = annotations[ANN_OUT]
+            if len(options) == 0:
                 if node.type.target_giname and node.type.ctype:
                     target = self._transformer.lookup_giname(node.type.target_giname)
                     target = self._transformer.resolve_aliases(target)
@@ -550,10 +544,12 @@ class MainTransformer(object):
                     caller_allocates = (not has_double_indirection and is_structure_or_union)
                 else:
                     caller_allocates = False
-            elif subtype == OPT_OUT_CALLER_ALLOCATES:
-                caller_allocates = True
-            elif subtype == OPT_OUT_CALLEE_ALLOCATES:
-                caller_allocates = False
+            else:
+                option = options[0]
+                if option == OPT_OUT_CALLER_ALLOCATES:
+                    caller_allocates = True
+                elif option == OPT_OUT_CALLEE_ALLOCATES:
+                    caller_allocates = False
         elif ANN_IN in annotations:
             annotated_direction = ast.PARAM_DIRECTION_IN
 
@@ -564,8 +560,8 @@ class MainTransformer(object):
             node.transfer = self._get_transfer_default(parent, node)
 
         transfer_tag = annotations.get(ANN_TRANSFER)
-        if transfer_tag and transfer_tag.length() == 1:
-            transfer = transfer_tag.one()
+        if transfer_tag and len(transfer_tag) == 1:
+            transfer = transfer_tag[0]
             if transfer == OPT_TRANSFER_FLOATING:
                 transfer = OPT_TRANSFER_NONE
             node.transfer = transfer
@@ -584,8 +580,11 @@ class MainTransformer(object):
             node.skip = True
 
         if annotations:
-            for attribute in annotations.getall(ANN_ATTRIBUTE):
-                node.attributes.append(attribute.flat())
+            attributes_annotation = annotations.get(ANN_ATTRIBUTES)
+            if attributes_annotation is not None:
+                for key, value in attributes_annotation.items():
+                    if value:
+                        node.attributes.append((key, value))
 
     def _apply_annotations_annotated(self, node, block):
         if block is None:
@@ -610,11 +609,11 @@ class MainTransformer(object):
             if stability_tag.value:
                 node.stability = stability_tag.value
 
-        annos_tag = block.tags.get(TAG_ATTRIBUTES)
-        if annos_tag is not None:
-            for ann_name, options in annos_tag.annotations.items():
-                if options:
-                    node.attributes.append((ann_name, options.one()))
+        attributes_annotation = block.annotations.get(ANN_ATTRIBUTES)
+        if attributes_annotation is not None:
+            for key, value in attributes_annotation.items():
+                if value:
+                    node.attributes.append((key, value))
 
         if ANN_SKIP in block.annotations:
             node.skip = True
@@ -637,14 +636,14 @@ class MainTransformer(object):
 
         if isinstance(parent, (ast.Function, ast.VFunction)):
             scope = annotations.get(ANN_SCOPE)
-            if scope and scope.length() == 1:
-                param.scope = scope.one()
+            if scope and len(scope) == 1:
+                param.scope = scope[0]
                 param.transfer = ast.PARAM_TRANSFER_NONE
 
             destroy = annotations.get(ANN_DESTROY)
             if destroy:
                 param.destroy_name = self._get_validate_parameter_name(parent,
-                                                                       destroy.one(),
+                                                                       destroy[0],
                                                                        param)
                 if param.destroy_name is not None:
                     param.scope = ast.PARAM_SCOPE_NOTIFIED
@@ -654,9 +653,9 @@ class MainTransformer(object):
                     # since we don't have a way right now to flag this callback a destroy.
                     destroy_param.scope = ast.PARAM_SCOPE_NOTIFIED
             closure = annotations.get(ANN_CLOSURE)
-            if closure and closure.length() == 1:
+            if closure and len(closure) == 1:
                 param.closure_name = self._get_validate_parameter_name(parent,
-                                                                       closure.one(),
+                                                                       closure[0],
                                                                        param)
         elif isinstance(parent, ast.Callback):
             if ANN_CLOSURE in annotations:
@@ -721,7 +720,7 @@ class MainTransformer(object):
             return
         t = tag.annotations.get(ANN_TYPE)
         if t:
-            field.type = self._transformer.create_type_from_user_string(t.one())
+            field.type = self._transformer.create_type_from_user_string(t[0])
 
         try:
             self._adjust_container_type(parent, field, tag.annotations)
@@ -775,7 +774,7 @@ class MainTransformer(object):
                 annotations = getattr(tag, 'annotations', {})
                 param_type = annotations.get(ANN_TYPE)
                 if param_type:
-                    param.type = self._resolve_toplevel(param_type.one(), param.type,
+                    param.type = self._resolve_toplevel(param_type[0], param.type,
                                                         param, parent)
             else:
                 tag = None
diff --git a/tests/scanner/Makefile.am b/tests/scanner/Makefile.am
index 0957a5f..aa5b55a 100644
--- a/tests/scanner/Makefile.am
+++ b/tests/scanner/Makefile.am
@@ -202,6 +202,7 @@ EXTRA_DIST += \
        annotationparser/tests.xsd                              \
        annotationparser/gi/annotation_allow_none.xml           \
        annotationparser/gi/annotation_array.xml                \
+       annotationparser/gi/annotation_attributes.xml           \
        annotationparser/gi/annotation_closure.xml              \
        annotationparser/gi/annotation_constructor.xml          \
        annotationparser/gi/annotation_destroy.xml              \
diff --git a/tests/scanner/annotation.c b/tests/scanner/annotation.c
index 0a92d3a..32187d3 100644
--- a/tests/scanner/annotation.c
+++ b/tests/scanner/annotation.c
@@ -128,12 +128,12 @@ regress_annotation_object_class_init (RegressAnnotationObjectClass *klass)
   /**
    * RegressAnnotationObject::attribute-signal:
    * @regress_annotation: the regress_annotation object
-   * @arg1: (attribute some.annotation.foo1 val1): a value
-   * @arg2: (attribute some.annotation.foo2 val2): another value
+   * @arg1: (attributes some.annotation.foo1=val1): a value
+   * @arg2: (attributes some.annotation.foo2=val2): another value
    *
    * This signal tests a signal with attributes.
    *
-   * Returns: (attribute some.annotation.foo3 val3): the return value
+   * Returns: (attributes some.annotation.foo3=val3): the return value
    */
   regress_annotation_object_signals[ATTRIBUTE_SIGNAL] =
     g_signal_new ("attribute-signal",
@@ -707,9 +707,7 @@ regress_annotation_string_array_length (guint n_properties, const gchar * const
 }
 
 /**
- * regress_annotation_object_extra_annos:
- *
- * Attributes: (org.foobar testvalue)
+ * regress_annotation_object_extra_annos: (attributes org.foobar=testvalue)
  */
 void
 regress_annotation_object_extra_annos (RegressAnnotationObject *object)
@@ -763,9 +761,9 @@ regress_annotation_ptr_array (GPtrArray *array)
 /**
  * regress_annotation_attribute_func:
  * @object: A #RegressAnnotationObject.
- * @data: (attribute some.annotation value) (attribute another.annotation blahvalue): Some data.
+ * @data: (attributes some.annotation=value another.annotation=blahvalue): Some data.
  *
- * Returns: (attribute some.other.annotation value2) (attribute yet.another.annotation another_value): The 
return value.
+ * Returns: (attributes some.other.annotation=value2 yet.another.annotation=another_value): The return value.
  */
 gint
 regress_annotation_attribute_func (RegressAnnotationObject *object,
diff --git a/tests/scanner/annotation.h b/tests/scanner/annotation.h
index 66470cb..b982a5b 100644
--- a/tests/scanner/annotation.h
+++ b/tests/scanner/annotation.h
@@ -37,11 +37,9 @@ typedef GList* (*RegressAnnotationListCallback) (GList *in);
 typedef void (*RegressAnnotationNotifyFunc) (gpointer data);
 
 /**
- * RegressAnnotationObject:
+ * RegressAnnotationObject: (attributes org.example.Test=cows)
  *
  * This is an object used to test annotations.
- *
- * Attributes: (org.example.Test cows)
  */
 typedef struct _RegressAnnotationObject          RegressAnnotationObject;
 typedef struct _RegressAnnotationObjectClass     RegressAnnotationObjectClass;
diff --git a/tests/scanner/annotationparser/gi/annotation_attributes.xml 
b/tests/scanner/annotationparser/gi/annotation_attributes.xml
new file mode 100644
index 0000000..e7c4689
--- /dev/null
+++ b/tests/scanner/annotationparser/gi/annotation_attributes.xml
@@ -0,0 +1,395 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<tests xmlns="http://schemas.gnome.org/gobject-introspection/2013/test";>
+
+<test>
+  <input>/**
+ * AnnotationObject: (attributes org.example.test1=horses org.example.test2 org.example.test3=cows)
+ *
+ * This is an object used to test annotations.
+ */</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>AnnotationObject</name>
+        <annotations>
+          <annotation>
+            <name>attributes</name>
+            <options>
+              <option>
+                <name>org.example.test1</name>
+                <value>horses</value>
+              </option>
+              <option>
+                <name>org.example.test2</name>
+              </option>
+              <option>
+                <name>org.example.test3</name>
+                <value>cows</value>
+              </option>
+            </options>
+          </annotation>
+        </annotations>
+      </identifier>
+      <description>This is an object used to test annotations.</description>
+    </docblock>
+  </parser>
+</test>
+
+<test>
+  <!--
+  Deprecated "Attributes:" tag
+  -->
+  <input>/**
+ * AnnotationObject:
+ *
+ * This is an object used to test annotations.
+ *
+ * Attributes: (org.example.test1 horses) (org.example.test2) (org.example.test3 cows)
+ */</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>AnnotationObject</name>
+        <annotations>
+          <annotation>
+            <name>attributes</name>
+            <options>
+              <option>
+                <name>org.example.test1</name>
+                <value>horses</value>
+              </option>
+              <option>
+                <name>org.example.test2</name>
+              </option>
+              <option>
+                <name>org.example.test3</name>
+                <value>cows</value>
+              </option>
+            </options>
+          </annotation>
+        </annotations>
+      </identifier>
+      <description>This is an object used to test annotations.</description>
+    </docblock>
+    <messages>
+      <message>6: Warning: Test: GObject-Introspection specific GTK-Doc tag "Attributes" has been 
deprecated, please use annotations on the identifier instead:
+ * Attributes: (org.example.test1 horses) (org.example.test2) (org.example.test3 cows)
+   ^</message>
+    </messages>
+  </parser>
+</test>
+
+<test>
+  <!--
+  (attributes) annotation on the identifier together with a
+  deprecated "Attributes:" tag.
+  -->
+  <input>/**
+ * AnnotationObject: (attributes org.example.test1=horses)
+ *
+ * This is an object used to test annotations.
+ *
+ * Attributes: (org.example.test1 horses) (org.example.test2 cows)
+ */</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>AnnotationObject</name>
+        <annotations>
+          <annotation>
+            <name>attributes</name>
+            <options>
+              <option>
+                <name>org.example.test1</name>
+                <value>horses</value>
+              </option>
+            </options>
+          </annotation>
+        </annotations>
+      </identifier>
+      <description>This is an object used to test annotations.</description>
+    </docblock>
+    <messages>
+      <message>6: Warning: Test: GObject-Introspection specific GTK-Doc tag "Attributes" has been 
deprecated, please use annotations on the identifier instead:
+ * Attributes: (org.example.test1 horses) (org.example.test2 cows)
+   ^</message>
+      <message>6: Error: Test: Duplicate "Attributes:" annotation will be ignored:
+ * Attributes: (org.example.test1 horses) (org.example.test2 cows)
+   ^</message>
+    </messages>
+  </parser>
+</test>
+
+<test>
+  <!--
+  Deprecated "Attributes:" tag in the wrong location
+  -->
+  <input>/**
+ * AnnotationObject:
+ *
+ * Attributes: (org.example.Test horses) (org.example.test2 cows)
+ *
+ * This is an object used to test annotations.
+ */</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>AnnotationObject</name>
+        <annotations>
+          <annotation>
+            <name>attributes</name>
+            <options>
+              <option>
+                <name>org.example.Test</name>
+                <value>horses</value>
+              </option>
+              <option>
+                <name>org.example.test2</name>
+                <value>cows</value>
+              </option>
+            </options>
+          </annotation>
+        </annotations>
+      </identifier>
+      <description>This is an object used to test annotations.</description>
+    </docblock>
+    <messages>
+      <message>4: Warning: Test: GObject-Introspection specific GTK-Doc tag "Attributes" has been 
deprecated, please use annotations on the identifier instead:
+ * Attributes: (org.example.Test horses) (org.example.test2 cows)
+   ^</message>
+    </messages>
+  </parser>
+</test>
+
+<test>
+  <input>/**
+ * AnnotationObject:
+ *
+ * Attributes: (org.example.Test horses cows)
+ *
+ * This is an object used to test annotations.
+ */</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>AnnotationObject</name>
+      </identifier>
+      <description>This is an object used to test annotations.</description>
+    </docblock>
+    <messages>
+      <message>4: Warning: Test: GObject-Introspection specific GTK-Doc tag "Attributes" has been 
deprecated, please use annotations on the identifier instead:
+ * Attributes: (org.example.Test horses cows)
+   ^</message>
+      <message>4: Error: Test: malformed "Attributes:" tag will be ignored:
+ * Attributes: (org.example.Test horses cows)
+   ^</message>
+    </messages>
+  </parser>
+</test>
+
+<test>
+  <input>/**
+ * AnnotationObject::attribute-signal:
+ * @annotation: the annotation object
+ * @arg1: (attributes some.annotation.foo1=val1): a value
+ * @arg2: (attributes some.annotation.foo2=val2): another value
+ * @arg3: (array fixed-size=2): a third value
+ *
+ * This signal tests a signal with attributes.
+ *
+ * Returns: (attributes some.annotation.foo3=val3): the return value
+ */</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>AnnotationObject::attribute-signal</name>
+      </identifier>
+      <parameters>
+        <parameter>
+          <name>annotation</name>
+          <description>the annotation object</description>
+        </parameter>
+        <parameter>
+          <name>arg1</name>
+          <annotations>
+            <annotation>
+              <name>attributes</name>
+              <options>
+                <option>
+                  <name>some.annotation.foo1</name>
+                  <value>val1</value>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>a value</description>
+        </parameter>
+        <parameter>
+          <name>arg2</name>
+          <annotations>
+            <annotation>
+              <name>attributes</name>
+              <options>
+                <option>
+                  <name>some.annotation.foo2</name>
+                  <value>val2</value>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>another value</description>
+        </parameter>
+        <parameter>
+          <name>arg3</name>
+          <annotations>
+            <annotation>
+              <name>array</name>
+              <options>
+                <option>
+                  <name>fixed-size</name>
+                  <value>2</value>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>a third value</description>
+        </parameter>
+      </parameters>
+      <description>This signal tests a signal with attributes.</description>
+      <tags>
+        <tag>
+          <name>returns</name>
+          <annotations>
+            <annotation>
+              <name>attributes</name>
+              <options>
+                <option>
+                  <name>some.annotation.foo3</name>
+                  <value>val3</value>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>the return value</description>
+        </tag>
+      </tags>
+    </docblock>
+  </parser>
+</test>
+
+<test>
+  <!--
+  Deprecated (attribute) annotation.
+  -->
+  <input>/**
+ * AnnotationObject::attribute-signal:
+ * @annotation: the annotation object
+ * @arg1: (attribute some.annotation.foo1): a value
+ * @arg2: (attribute some.annotation.foo2 val2): another value
+ * @arg3: (attribute x y z): something special
+ * @arg4: (array fixed-size=2): a third value
+ *
+ * This signal tests a signal with attributes.
+ *
+ * Returns: (attribute some.annotation.foo3 val3): the return value
+ */</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>AnnotationObject::attribute-signal</name>
+      </identifier>
+      <parameters>
+        <parameter>
+          <name>annotation</name>
+          <description>the annotation object</description>
+        </parameter>
+        <parameter>
+          <name>arg1</name>
+          <annotations>
+            <annotation>
+              <name>attributes</name>
+              <options>
+                <option>
+                  <name>some.annotation.foo1</name>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>a value</description>
+        </parameter>
+        <parameter>
+          <name>arg2</name>
+          <annotations>
+            <annotation>
+              <name>attributes</name>
+              <options>
+                <option>
+                  <name>some.annotation.foo2</name>
+                  <value>val2</value>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>another value</description>
+        </parameter>
+        <parameter>
+          <name>arg3</name>
+          <description>something special</description>
+        </parameter>
+        <parameter>
+          <name>arg4</name>
+          <annotations>
+            <annotation>
+              <name>array</name>
+              <options>
+                <option>
+                  <name>fixed-size</name>
+                  <value>2</value>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>a third value</description>
+        </parameter>
+      </parameters>
+      <description>This signal tests a signal with attributes.</description>
+      <tags>
+        <tag>
+          <name>returns</name>
+          <annotations>
+            <annotation>
+              <name>attributes</name>
+              <options>
+                <option>
+                  <name>some.annotation.foo3</name>
+                  <value>val3</value>
+                </option>
+              </options>
+            </annotation>
+          </annotations>
+          <description>the return value</description>
+        </tag>
+      </tags>
+    </docblock>
+    <messages>
+    <message>4: Warning: Test: "attribute" annotation has been deprecated, please use "attributes" instead:
+ * @arg1: (attribute some.annotation.foo1): a value
+          ^</message>
+    <message>5: Warning: Test: "attribute" annotation has been deprecated, please use "attributes" instead:
+ * @arg2: (attribute some.annotation.foo2 val2): another value
+          ^</message>
+    <message>6: Warning: Test: "attribute" annotation has been deprecated, please use "attributes" instead:
+ * @arg3: (attribute x y z): something special
+          ^</message>
+    <message>6: Error: Test: malformed "(attribute)" annotation will be ignored:
+ * @arg3: (attribute x y z): something special
+          ^</message>
+    <message>11: Warning: Test: "attribute" annotation has been deprecated, please use "attributes" instead:
+ * Returns: (attribute some.annotation.foo3 val3): the return value
+            ^</message>
+    </messages>
+  </parser>
+</test>
+
+</tests>
diff --git a/tests/scanner/annotationparser/gi/annotation_element_type.xml 
b/tests/scanner/annotationparser/gi/annotation_element_type.xml
index 30fffaf..84337b7 100644
--- a/tests/scanner/annotationparser/gi/annotation_element_type.xml
+++ b/tests/scanner/annotationparser/gi/annotation_element_type.xml
@@ -110,7 +110,7 @@ element-type annotation.</description>
                   <name>utf8</name>
                 </option>
                 <option>
-                  <name><![CDATA[GLib.HashTable<utf8,utf8>]]></name>
+                  <name>GLib.HashTable(utf8,utf8)</name>
                 </option>
               </options>
             </annotation>
diff --git a/tests/scanner/annotationparser/gi/annotation_in_out.xml 
b/tests/scanner/annotationparser/gi/annotation_in_out.xml
index cb48957..5530a6a 100644
--- a/tests/scanner/annotationparser/gi/annotation_in_out.xml
+++ b/tests/scanner/annotationparser/gi/annotation_in_out.xml
@@ -36,7 +36,7 @@
           <name>inoutarg2</name>
           <annotations>
             <annotation>
-              <name>in-out</name>
+              <name>inout</name>
             </annotation>
           </annotations>
           <description>This is an argument test</description>
@@ -50,6 +50,11 @@
         </tag>
       </tags>
     </docblock>
+    <messages>
+      <message>5: Warning: Test: "in-out" annotation has been deprecated, please use "inout" instead:
+ * @inoutarg2: (in-out): This is an argument test
+               ^</message>
+    </messages>
   </parser>
 </test>
 
diff --git a/tests/scanner/annotationparser/gi/identifier_symbol.xml 
b/tests/scanner/annotationparser/gi/identifier_symbol.xml
index 7e31776..4a97936 100644
--- a/tests/scanner/annotationparser/gi/identifier_symbol.xml
+++ b/tests/scanner/annotationparser/gi/identifier_symbol.xml
@@ -74,7 +74,7 @@
       <description>Missing colon will result in a warning.</description>
     </docblock>
     <messages>
-      <message>2: Warning: Test: missing ':' at column 30:
+      <message>2: Warning: Test: missing ":" at column 30:
  * test_symbol_missing_colon (skip)
                              ^</message>
     </messages>
@@ -153,7 +153,7 @@
       <description>Missing colon will result in a warning.</description>
     </docblock>
     <messages>
-      <message>2: Warning: Test: missing ':' at column 42:
+      <message>2: Warning: Test: missing ":" at column 42:
  * GtkWidget:test_property_missing_colon (skip)
                                          ^</message>
     </messages>
@@ -232,7 +232,7 @@
       <description>Missing colon will result in a warning.</description>
     </docblock>
     <messages>
-      <message>2: Warning: Test: missing ':' at column 41:
+      <message>2: Warning: Test: missing ":" at column 41:
  * GtkWidget::test_signal_missing_colon (skip)
                                         ^</message>
     </messages>
diff --git a/tests/scanner/annotationparser/gi/tag_deprecated.xml 
b/tests/scanner/annotationparser/gi/tag_deprecated.xml
index b33509c..fbd342b 100644
--- a/tests/scanner/annotationparser/gi/tag_deprecated.xml
+++ b/tests/scanner/annotationparser/gi/tag_deprecated.xml
@@ -67,7 +67,7 @@
       </tags>
     </docblock>
     <messages>
-      <message>6: Warning: Test: annotations not supported for tag 'Deprecated:'.</message>
+      <message>6: Error: Test: annotations not supported for tag "Deprecated:".</message>
     </messages>
   </parser>
 </test>
diff --git a/tests/scanner/annotationparser/gi/tag_since.xml b/tests/scanner/annotationparser/gi/tag_since.xml
index e736aba..502d8b6 100644
--- a/tests/scanner/annotationparser/gi/tag_since.xml
+++ b/tests/scanner/annotationparser/gi/tag_since.xml
@@ -66,7 +66,7 @@
       </tags>
     </docblock>
     <messages>
-      <message>6: Warning: Test: annotations not supported for tag 'Since:'.</message>
+      <message>6: Error: Test: annotations not supported for tag "Since:".</message>
     </messages>
   </parser>
 </test>
diff --git a/tests/scanner/annotationparser/gi/tag_stability.xml 
b/tests/scanner/annotationparser/gi/tag_stability.xml
index 27976fd..ec87b02 100644
--- a/tests/scanner/annotationparser/gi/tag_stability.xml
+++ b/tests/scanner/annotationparser/gi/tag_stability.xml
@@ -108,7 +108,7 @@
       </tags>
     </docblock>
     <messages>
-      <message>6: Warning: Test: annotations not supported for tag 'Stability:'.</message>
+      <message>6: Error: Test: annotations not supported for tag "Stability:".</message>
     </messages>
   </parser>
 </test>
diff --git a/tests/scanner/annotationparser/test_parser.py b/tests/scanner/annotationparser/test_parser.py
index 98ae787..c7ff532 100644
--- a/tests/scanner/annotationparser/test_parser.py
+++ b/tests/scanner/annotationparser/test_parser.py
@@ -130,20 +130,25 @@ class TestCommentBlock(unittest.TestCase):
 
             parsed += '  <identifier>\n'
             parsed += '    <name>%s</name>\n' % (docblock.name, )
-            if docblock.annotations.values:
+            if docblock.annotations:
                 parsed += '    <annotations>\n'
-                for key, value in docblock.annotations.values:
+                for ann_name, ann_options in docblock.annotations.items():
                     parsed += '      <annotation>\n'
-                    parsed += '        <name>%s</name>\n' % (key, )
-                    if value is not None:
-                        options = value.all()
+                    parsed += '        <name>%s</name>\n' % (ann_name, )
+                    if ann_options:
                         parsed += '        <options>\n'
-                        for option in options:
-                            parsed += '          <option>\n'
-                            parsed += '            <name>%s</name>\n' % (option, )
-                            if options[option] is not None:
-                                parsed += '            <value>%s</value>\n' % (options[option], )
-                            parsed += '          </option>\n'
+                        if isinstance(ann_options, list):
+                            for option in ann_options:
+                                parsed += '          <option>\n'
+                                parsed += '            <name>%s</name>\n' % (option, )
+                                parsed += '          </option>\n'
+                        else:
+                            for (option, value) in ann_options.items():
+                                parsed += '          <option>\n'
+                                parsed += '            <name>%s</name>\n' % (option, )
+                                if value:
+                                    parsed += '            <value>%s</value>\n' % (value, )
+                                parsed += '          </option>\n'
                         parsed += '        </options>\n'
                     parsed += '      </annotation>\n'
                 parsed += '    </annotations>\n'
@@ -155,20 +160,25 @@ class TestCommentBlock(unittest.TestCase):
                     param = docblock.params.get(param_name)
                     parsed += '    <parameter>\n'
                     parsed += '      <name>%s</name>\n' % (param_name, )
-                    if param.annotations.values:
+                    if param.annotations:
                         parsed += '      <annotations>\n'
-                        for key, value in param.annotations.values:
+                        for ann_name, ann_options in param.annotations.items():
                             parsed += '        <annotation>\n'
-                            parsed += '          <name>%s</name>\n' % (key, )
-                            if value is not None:
-                                options = value.all()
+                            parsed += '          <name>%s</name>\n' % (ann_name, )
+                            if ann_options:
                                 parsed += '          <options>\n'
-                                for option in options:
-                                    parsed += '            <option>\n'
-                                    parsed += '              <name>%s</name>\n' % (option, )
-                                    if options[option] is not None:
-                                        parsed += '              <value>%s</value>\n' % (options[option], )
-                                    parsed += '            </option>\n'
+                                if isinstance(ann_options, list):
+                                    for option in ann_options:
+                                        parsed += '            <option>\n'
+                                        parsed += '              <name>%s</name>\n' % (option, )
+                                        parsed += '            </option>\n'
+                                else:
+                                    for (option, value) in ann_options.items():
+                                        parsed += '            <option>\n'
+                                        parsed += '              <name>%s</name>\n' % (option, )
+                                        if value:
+                                            parsed += '              <value>%s</value>\n' % (value, )
+                                        parsed += '            </option>\n'
                                 parsed += '          </options>\n'
                             parsed += '        </annotation>\n'
                         parsed += '      </annotations>\n'
@@ -186,20 +196,25 @@ class TestCommentBlock(unittest.TestCase):
                     tag = docblock.tags.get(tag_name)
                     parsed += '    <tag>\n'
                     parsed += '      <name>%s</name>\n' % (tag_name, )
-                    if tag.annotations.values:
+                    if tag.annotations:
                         parsed += '      <annotations>\n'
-                        for key, value in tag.annotations.values:
+                        for ann_name, ann_options in tag.annotations.items():
                             parsed += '        <annotation>\n'
-                            parsed += '          <name>%s</name>\n' % (key, )
-                            if value is not None:
-                                options = value.all()
+                            parsed += '          <name>%s</name>\n' % (ann_name, )
+                            if ann_options:
                                 parsed += '          <options>\n'
-                                for option in options:
-                                    parsed += '            <option>\n'
-                                    parsed += '              <name>%s</name>\n' % (option, )
-                                    if options[option] is not None:
-                                        parsed += '              <value>%s</value>\n' % (options[option], )
-                                    parsed += '            </option>\n'
+                                if isinstance(ann_options, list):
+                                    for option in ann_options:
+                                        parsed += '            <option>\n'
+                                        parsed += '              <name>%s</name>\n' % (option, )
+                                        parsed += '            </option>\n'
+                                else:
+                                    for (option, value) in ann_options.items():
+                                        parsed += '            <option>\n'
+                                        parsed += '              <name>%s</name>\n' % (option, )
+                                        if value:
+                                            parsed += '              <value>%s</value>\n' % (value, )
+                                        parsed += '            </option>\n'
                                 parsed += '          </options>\n'
                             parsed += '        </annotation>\n'
                         parsed += '      </annotations>\n'
@@ -235,8 +250,9 @@ class TestCommentBlock(unittest.TestCase):
                             expected += '        <options>\n'
                             for option in annotation.findall(ns('{}options/{}option')):
                                 expected += '          <option>\n'
-                                expected += '            <name>%s</name>\n' % 
(option.find(ns('{}name')).text, )
-                                if option.find('value') is not None:
+                                if option.find(ns('{}name')) is not None:
+                                    expected += '            <name>%s</name>\n' % 
(option.find(ns('{}name')).text, )
+                                if option.find(ns('{}value')) is not None:
                                     expected += '            <value>%s</value>\n' % 
(option.find(ns('{}value')).text, )
                                 expected += '          </option>\n'
                             expected += '        </options>\n'
@@ -260,7 +276,8 @@ class TestCommentBlock(unittest.TestCase):
                                 expected += '          <options>\n'
                                 for option in annotation.findall(ns('{}options/{}option')):
                                     expected += '            <option>\n'
-                                    expected += '              <name>%s</name>\n' % 
(option.find(ns('{}name')).text, )
+                                    if option.find(ns('{}name')) is not None:
+                                        expected += '              <name>%s</name>\n' % 
(option.find(ns('{}name')).text, )
                                     if option.find(ns('{}value')) is not None:
                                         expected += '              <value>%s</value>\n' % 
(option.find(ns('{}value')).text, )
                                     expected += '            </option>\n'
@@ -292,7 +309,8 @@ class TestCommentBlock(unittest.TestCase):
                                 expected += '          <options>\n'
                                 for option in annotation.findall(ns('{}options/{}option')):
                                     expected += '            <option>\n'
-                                    expected += '              <name>%s</name>\n' % 
(option.find(ns('{}name')).text, )
+                                    if option.find(ns('{}name')) is not None:
+                                        expected += '              <name>%s</name>\n' % 
(option.find(ns('{}name')).text, )
                                     if option.find(ns('{}value')) is not None:
                                         expected += '              <value>%s</value>\n' % 
(option.find(ns('{}value')).text, )
                                     expected += '            </option>\n'
diff --git a/tests/scanner/annotationparser/test_patterns.py b/tests/scanner/annotationparser/test_patterns.py
index d121ae6..023f61f 100644
--- a/tests/scanner/annotationparser/test_patterns.py
+++ b/tests/scanner/annotationparser/test_patterns.py
@@ -85,182 +85,182 @@ identifier_symbol_tests = [
     (SYMBOL_RE, 'GBaseFinalizeFunc:',
          {'delimiter': ':',
           'symbol_name': 'GBaseFinalizeFunc',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, 'gtk_widget_show  ',
          {'delimiter': '',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, '  gtk_widget_show',
          {'delimiter': '',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, '  gtk_widget_show  ',
          {'delimiter': '',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, 'gtk_widget_show:',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, 'gtk_widget_show :',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, 'gtk_widget_show:  ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, 'gtk_widget_show :  ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, '  gtk_widget_show:',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, '  gtk_widget_show :',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, '  gtk_widget_show:  ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, '  gtk_widget_show :  ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': ''}),
+          'fields': ''}),
     (SYMBOL_RE, 'gtk_widget_show (skip)',
          {'delimiter': '',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, 'gtk_widget_show: (skip)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, 'gtk_widget_show : (skip)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, 'gtk_widget_show:  (skip)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, 'gtk_widget_show :  (skip)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, '  gtk_widget_show:(skip)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, '  gtk_widget_show :(skip)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, '  gtk_widget_show:  (skip)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, '  gtk_widget_show :  (skip)    \t    ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)    \t    '}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, '  gtk_widget_show  :  (skip)   \t    ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)   \t    '}),
+          'fields': '(skip)'}),
     (SYMBOL_RE, 'gtk_widget_show:(skip)(test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)(test1)'}),
+          'fields': '(skip)(test1)'}),
     (SYMBOL_RE, 'gtk_widget_show (skip)(test1)',
          {'delimiter': '',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)(test1)'}),
+          'fields': '(skip)(test1)'}),
     (SYMBOL_RE, 'gtk_widget_show: (skip) (test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)'}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, 'gtk_widget_show : (skip) (test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)'}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, 'gtk_widget_show:  (skip) (test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)'}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, 'gtk_widget_show :  (skip) (test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)'}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, '  gtk_widget_show:(skip) (test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)'}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, '  gtk_widget_show :(skip) (test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)'}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, '  gtk_widget_show:  (skip) (test1)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)'}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, '  gtk_widget_show :  (skip) (test1)  ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1)  '}),
+          'fields': '(skip) (test1)'}),
     (SYMBOL_RE, 'gtk_widget_show: (skip) (test1) (test-2)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)'}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, 'gtk_widget_show : (skip) (test1) (test-2)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)'}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, 'gtk_widget_show:  (skip) (test1) (test-2)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)'}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, 'gtk_widget_show :  (skip) (test1) (test-2)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)'}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, '  gtk_widget_show:(skip) (test1) (test-2)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)'}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, '  gtk_widget_show :(skip) (test1) (test-2)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)'}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, '  gtk_widget_show:  (skip) (test1) (test-2)',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)'}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, '  gtk_widget_show :  (skip) (test1) (test-2)  ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip) (test1) (test-2)  '}),
+          'fields': '(skip) (test1) (test-2)'}),
     (SYMBOL_RE, '  gtk_widget_show  :  (skip)  (test1)  (test-2)  ',
          {'delimiter': ':',
           'symbol_name': 'gtk_widget_show',
-          'annotations': '(skip)  (test1)  (test-2)  '}),
+          'fields': '(skip)  (test1)  (test-2)'}),
     # constants
     (SYMBOL_RE, 'MY_CONSTANT:',
          {'delimiter': ':',
           'symbol_name': 'MY_CONSTANT',
-          'annotations': ''}),
+          'fields': ''}),
     # structs
     (SYMBOL_RE, 'FooWidget:',
          {'delimiter': ':',
           'symbol_name': 'FooWidget',
-          'annotations': ''}),
+          'fields': ''}),
     # enums
     (SYMBOL_RE, 'Something:',
          {'delimiter': ':',
           'symbol_name': 'Something',
-          'annotations': ''})]
+          'fields': ''})]
 
 identifier_property_tests = [
     # simple property name
@@ -268,57 +268,57 @@ identifier_property_tests = [
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': '',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (PROPERTY_RE, 'GtkWidget:name',
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, ' GtkWidget :name',
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'GtkWidget: name ',
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, '  GtkWidget  :  name  ',
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'GtkWidget:name:',
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'GtkWidget:name:  ',
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, '  GtkWidget:name:',
          {'class_name': 'GtkWidget',
           'property_name': 'name',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'Something:name:',
          {'class_name': 'Something',
           'property_name': 'name',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'Something:name:  ',
          {'class_name': 'Something',
           'property_name': 'name',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, '  Something:name:',
          {'class_name': 'Something',
           'property_name': 'name',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'Weird-thing:name:',
          None),
     (PROPERTY_RE, 'really-weird_thing:name:',
@@ -327,63 +327,63 @@ identifier_property_tests = [
          {'class_name': 'GWin32InputStream',
           'property_name': 'handle',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     # property name that contains a dash
     (PROPERTY_RE, 'GtkWidget:double-buffered (skip)',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': '',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (PROPERTY_RE, 'GtkWidget:double-buffered',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, ' GtkWidget :double-buffered',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'GtkWidget: double-buffered ',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, '  GtkWidget  :  double-buffered  ',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': '',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'GtkWidget:double-buffered:',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'GtkWidget:double-buffered:  ',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, '  GtkWidget:double-buffered:',
          {'class_name': 'GtkWidget',
           'property_name': 'double-buffered',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'Something:double-buffered:',
          {'class_name': 'Something',
           'property_name': 'double-buffered',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'Something:double-buffered:  ',
          {'class_name': 'Something',
           'property_name': 'double-buffered',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, '  Something:double-buffered:',
          {'class_name': 'Something',
           'property_name': 'double-buffered',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (PROPERTY_RE, 'Weird-thing:double-buffered:',
          None),
     (PROPERTY_RE, 'really-weird_thing:double-buffered:',
@@ -392,7 +392,7 @@ identifier_property_tests = [
          {'class_name': 'GMemoryOutputStream',
           'property_name': 'realloc-function',
           'delimiter': ':',
-          'annotations': '(skip)'})]
+          'fields': '(skip)'})]
 
 identifier_signal_tests = [
     # simple property name
@@ -400,17 +400,17 @@ identifier_signal_tests = [
          {'class_name': 'GtkWidget',
           'signal_name': 'changed',
           'delimiter': ':',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SIGNAL_RE, 'GtkWidget::changed:',
          {'class_name': 'GtkWidget',
           'signal_name': 'changed',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (SIGNAL_RE, 'Something::changed:',
          {'class_name': 'Something',
           'signal_name': 'changed',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (SIGNAL_RE, 'Weird-thing::changed:',
          None),
     (SIGNAL_RE, 'really-weird_thing::changed:',
@@ -420,17 +420,17 @@ identifier_signal_tests = [
          {'class_name': 'GtkWidget',
           'signal_name': 'hierarchy-changed',
           'delimiter': ':',
-          'annotations': '(skip)'}),
+          'fields': '(skip)'}),
     (SIGNAL_RE, 'GtkWidget::hierarchy-changed:',
          {'class_name': 'GtkWidget',
           'signal_name': 'hierarchy-changed',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (SIGNAL_RE, 'Something::hierarchy-changed:',
          {'class_name': 'Something',
           'signal_name': 'hierarchy-changed',
           'delimiter': ':',
-          'annotations': ''}),
+          'fields': ''}),
     (SIGNAL_RE, 'Weird-thing::hierarchy-changed:',
          None),
     (SIGNAL_RE, 'really-weird_thing::hierarchy-changed:',
@@ -439,106 +439,71 @@ identifier_signal_tests = [
 parameter_tests = [
     (PARAMETER_RE, '@Short_description: Base class for all widgets  ',
          {'parameter_name': 'Short_description',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'Base class for all widgets'}),
+          'fields': 'Base class for all widgets'}),
     (PARAMETER_RE, '@...: the value of the first property, followed optionally by more',
          {'parameter_name': '...',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'the value of the first property, followed optionally by more'}),
+          'fields': 'the value of the first property, followed optionally by more'}),
     (PARAMETER_RE, '@widget: a #GtkWidget',
          {'parameter_name': 'widget',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'a #GtkWidget'}),
+          'fields': 'a #GtkWidget'}),
     (PARAMETER_RE, '@widget_pointer: (inout) (transfer none): '
                    'address of a variable that contains @widget',
          {'parameter_name': 'widget_pointer',
-          'annotations': '(inout) (transfer none)',
-          'delimiter': ':',
-          'description': 'address of a variable that contains @widget'}),
+          'fields': '(inout) (transfer none): address of a variable that contains @widget'}),
     (PARAMETER_RE, '@weird_thing: (inout) (transfer none) (allow-none) (attribute) (destroy) '
                    '(foreign) (inout) (out) (transfer) (skip) (method): some weird @thing',
          {'parameter_name': 'weird_thing',
-          'annotations': '(inout) (transfer none) (allow-none) (attribute) (destroy) '
-                            '(foreign) (inout) (out) (transfer) (skip) (method)',
-          'delimiter': ':',
-          'description': 'some weird @thing'}),
+          'fields': '(inout) (transfer none) (allow-none) (attribute) (destroy) '
+                  '(foreign) (inout) (out) (transfer) (skip) (method): '
+                  'some weird @thing'}),
     (PARAMETER_RE, '@data: a pointer to the element data. The data may be moved as elements '
                    'are added to the #GByteArray.',
          {'parameter_name': 'data',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'a pointer to the element data. The data may be moved as elements '
+          'fields': 'a pointer to the element data. The data may be moved as elements '
                             'are added to the #GByteArray.'}),
     (PARAMETER_RE, '@a: a #GSequenceIter',
          {'parameter_name': 'a',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'a #GSequenceIter'}),
+          'fields': 'a #GSequenceIter'}),
     (PARAMETER_RE, '@keys: (array length=n_keys) (element-type GQuark) (allow-none):',
          {'parameter_name': 'keys',
-          'annotations': '(array length=n_keys) (element-type GQuark) (allow-none)',
-          'delimiter': ':',
-          'description': ''})]
+          'fields': '(array length=n_keys) (element-type GQuark) (allow-none):'})]
 
 tag_tests = [
     (TAG_RE, 'Since 3.0',
          None),
     (TAG_RE, 'Since: 3.0',
          {'tag_name': 'Since',
-          'annotations': '',
-          'delimiter': '',
-          'description': '3.0'}),
+          'fields': '3.0'}),
     (TAG_RE, 'Attributes: (inout) (transfer none): some note about attributes',
          {'tag_name': 'Attributes',
-          'annotations': '(inout) (transfer none)',
-          'delimiter': ':',
-          'description': 'some note about attributes'}),
+          'fields': '(inout) (transfer none): some note about attributes'}),
     (TAG_RE, 'Rename to: something_else',
          {'tag_name': 'Rename to',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'something_else'}),
+          'fields': 'something_else'}),
     (TAG_RE, '@Deprecated: Since 2.8, reference counting is done atomically',
          None),
     (TAG_RE, 'Returns %TRUE and does weird things',
          None),
     (TAG_RE, 'Returns: a #GtkWidget',
          {'tag_name': 'Returns',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'a #GtkWidget'}),
+          'fields': 'a #GtkWidget'}),
     (TAG_RE, 'Return value: (transfer none): The binary data that @text responds. '
              'This pointer',
          {'tag_name': 'Return value',
-          'annotations': '(transfer none)',
-          'delimiter': ':',
-          'description': 'The binary data that @text responds. This pointer'}),
+          'fields': '(transfer none): The binary data that @text responds. This pointer'}),
     (TAG_RE, 'Return value: (transfer full) (array length=out_len) (element-type guint8):',
          {'tag_name': 'Return value',
-          'annotations': '(transfer full) (array length=out_len) (element-type guint8)',
-          'delimiter': ':',
-          'description': ''}),
+          'fields': '(transfer full) (array length=out_len) (element-type guint8):'}),
     (TAG_RE, 'Returns: A boolean value, but let me tell you a bit about this boolean.  It',
          {'tag_name': 'Returns',
-          'annotations': '',
-          'delimiter': '',
-          'description': 'A boolean value, but let me tell you a bit about this boolean.  '
-                         'It'}),
+          'fields': 'A boolean value, but let me tell you a bit about this boolean.  It'}),
     (TAG_RE, 'Returns: (transfer container) (element-type GObject.ParamSpec): a',
          {'tag_name': 'Returns',
-          'annotations': '(transfer container) (element-type GObject.ParamSpec)',
-          'delimiter': ':',
-          'description': 'a'}),
+          'fields': '(transfer container) (element-type GObject.ParamSpec): a'}),
     (TAG_RE, 'Return value: (type GLib.HashTable<utf8,GLib.HashTable<utf8,utf8>>) '
              '(transfer full):',
          {'tag_name': 'Return value',
-          'annotations': '(type GLib.HashTable<utf8,GLib.HashTable<utf8,utf8>>) '
-                         '(transfer full)',
-          'delimiter': ':',
-          'description': ''})]
+          'fields': '(type GLib.HashTable<utf8,GLib.HashTable<utf8,utf8>>) (transfer full):'})]
 
 tag_value_version_tests = [
     (TAG_VALUE_VERSION_RE, ' abc',
diff --git a/tests/warn/invalid-element-type.h b/tests/warn/invalid-element-type.h
index bcd9123..97b8281 100644
--- a/tests/warn/invalid-element-type.h
+++ b/tests/warn/invalid-element-type.h
@@ -74,16 +74,16 @@ GList* test_unresolved_element_type(void);
 GPtrArray* test_unresolved_value_element_type(void);
 
 
-// EXPECT:5: Warning: Test: element-type annotation takes at least one option, none given
+// EXPECT:5: Warning: Test: element-type annotation for a list must have exactly one option, not 0 options
 // EXPECT:6: Warning: Test: element-type annotation for a list must have exactly one option, not 2 options
-// EXPECT:16: Warning: Test: element-type annotation takes at least one option, none given
 // EXPECT:20: Warning: Test: Unknown container Type(target_fundamental=utf8, ctype=char*) for element-type 
annotation
-// EXPECT:27: Warning: Test: element-type annotation takes at least one option, none given
+// EXPECT:20: Warning: Test: Unknown container Type(target_fundamental=utf8, ctype=char*) for element-type 
annotation
+// EXPECT:27: Warning: Test: element-type annotation for a hash table must have exactly two options, not 0 
option(s)
 // EXPECT:28: Warning: Test: element-type annotation for a hash table must have exactly two options, not 1 
option(s)
 // EXPECT:29: Warning: Test: element-type annotation for a hash table must have exactly two options, not 3 
option(s)
-// EXPECT:40: Warning: Test: element-type annotation takes at least one option, none given
+// EXPECT:40: Warning: Test: element-type annotation for an array must have exactly one option, not 0 options
 // EXPECT:41: Warning: Test: invalid (element-type) for a GByteArray, must be one of guint8, gint8 or gchar
-// EXPECT:51: Warning: Test: element-type annotation takes at least one option, none given
+// EXPECT:51: Warning: Test: element-type annotation for an array must have exactly one option, not 0 options
 // EXPECT:52: Warning: Test: invalid (element-type) for a GPtrArray, must be a pointer
 // EXPECT:63: Warning: Test: test_unresolved_element_type: Unknown type: 'Unresolved'
 // EXPECT:71: Warning: Test: test_unresolved_value_element_type: Unknown type: 'GLib.Value'


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]