Check for AsyncReadyCallback / DestroyNotify
[gnome.gobject-introspection] / giscanner / annotationparser.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19 #
20
21 # AnnotationParser - parses gtk-doc annotations
22
23 import re
24 import sys
25
26 from .ast import (Array, Bitfield, Callback, Class, Enum, Field, Function,
27                   Interface, List, Map, Parameter, Record, Return, Type, Union,
28                   Varargs,
29                   default_array_types,
30                   BASIC_GIR_TYPES,
31                   PARAM_DIRECTION_INOUT,
32                   PARAM_DIRECTION_IN,
33                   PARAM_DIRECTION_OUT,
34                   PARAM_TRANSFER_NONE,
35                   PARAM_TRANSFER_CONTAINER,
36                   PARAM_TRANSFER_FULL,
37                   TYPE_ANY, TYPE_NONE)
38 from .odict import odict
39 from .glibast import GLibBoxed
40
41 # All gtk-doc comments needs to start with this:
42 _COMMENT_HEADER = '*\n '
43
44 # Tags - annotations applyed to comment blocks
45 TAG_VFUNC = 'virtual'
46 TAG_SINCE = 'since'
47 TAG_DEPRECATED = 'deprecated'
48 TAG_RETURNS = 'returns'
49 TAG_RETURNS_ALT = 'return value'
50 TAG_ATTRIBUTES = 'attributes'
51 TAG_RENAME_TO = 'rename to'
52
53 # Options - annotations for parameters and return values
54 OPT_ALLOW_NONE = 'allow-none'
55 OPT_ARRAY = 'array'
56 OPT_ELEMENT_TYPE = 'element-type'
57 OPT_IN = 'in'
58 OPT_INOUT = 'inout'
59 OPT_INOUT_ALT = 'in-out'
60 OPT_OUT = 'out'
61 OPT_SCOPE = 'scope'
62 OPT_TRANSFER = 'transfer'
63 OPT_TYPE = 'type'
64 OPT_CLOSURE = 'closure'
65 OPT_DESTROY = 'destroy'
66 OPT_SKIP = 'skip'
67
68 # Specific option values
69 OPT_VAL_BITFIELD = 'bitfield'
70
71 # Array options - array specific annotations
72 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
73 OPT_ARRAY_LENGTH = 'length'
74 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
75
76 OPT_SCOPE_ASYNC = 'async'
77 OPT_SCOPE_CALL = 'call'
78 OPT_SCOPE_NOTIFIED = 'notified'
79
80 class InvalidAnnotationError(Exception):
81     pass
82
83
84 class DocBlock(object):
85
86     def __init__(self, name, options):
87         self.name = name
88         self.options = options
89         self.value = None
90         self.tags = odict()
91         self.comment = None
92
93     def __repr__(self):
94         return '<DocBlock %r %r>' % (self.name, self.options)
95
96     def get(self, name):
97         if name == TAG_RETURNS:
98             value = self.tags.get(name)
99             if value is None:
100                 return self.tags.get(TAG_RETURNS_ALT)
101             else:
102                 return value
103         else:
104             return self.tags.get(name)
105
106
107 class DocTag(object):
108
109     def __init__(self, name):
110         self.name = name
111         self.options = {}
112         self.comment = None
113
114     def __repr__(self):
115         return '<DocTag %r %r>' % (self.name, self.options)
116
117 class Option(object):
118
119     def __init__(self, option):
120         self._array = []
121         self._dict = {}
122         for p in option.split(' '):
123             if '=' in p:
124                 name, value = p.split('=', 1)
125             else:
126                 name = p
127                 value = None
128             self._dict[name] = value
129             if value is None:
130                 self._array.append(name)
131             else:
132                 self._array.append((name, value))
133
134     def __repr__(self):
135         return '<Option %r>' % (self._array, )
136
137     def one(self):
138         assert len(self._array) == 1
139         return self._array[0]
140
141     def flat(self):
142         return self._array
143
144     def all(self):
145         return self._dict
146
147
148 class AnnotationParser(object):
149
150     def __init__(self, namespace, source_scanner, transformer):
151         self._blocks = {}
152         self._namespace = namespace
153         self._transformer = transformer
154         for comment in source_scanner.get_comments():
155             self._parse_comment(comment)
156
157     def parse(self):
158         aa = AnnotationApplier(self._blocks, self._transformer)
159         aa.parse(self._namespace)
160
161     def _parse_comment(self, comment):
162         # We're looking for gtk-doc comments here, they look like this:
163         # /**
164         #   * symbol:
165         #
166         # Or, alternatively, with options:
167         # /**
168         #   * symbol: (name value) ...
169         #
170         # symbol is currently one of:
171         #  - function: gtk_widget_show
172         #  - signal:   GtkWidget::destroy
173         #  - property: GtkWidget:visible
174         #
175         comment = comment.lstrip()
176         if not comment.startswith(_COMMENT_HEADER):
177             return
178         comment = comment[len(_COMMENT_HEADER):]
179         comment = comment.strip()
180         if not comment.startswith('* '):
181             return
182         comment = comment[2:]
183
184         pos = comment.find('\n ')
185         if pos == -1:
186             return
187         block_header = comment[:pos]
188         block_header = block_header.strip()
189         cpos = block_header.find(': ')
190         if cpos:
191             block_name = block_header[:cpos]
192             block_options, rest = self._parse_options(block_header[cpos+2:])
193             if rest:
194                 return
195         else:
196             block_name, block_options = block_header, {}
197         block = DocBlock(block_name, block_options)
198         comment_lines = []
199         for line in comment[pos+1:].split('\n'):
200             line = line.lstrip()
201             line = line[2:].strip() # Skip ' *'
202             if not line:
203                 continue
204             if line.startswith('@'):
205                 line = line[1:]
206             elif not ': ' in line:
207                 comment_lines.append(line)
208                 continue
209             tag_name, value = self._split_tag_namevalue(line)
210             canon_name = tag_name.lower()
211             if canon_name in block.tags:
212                 print >>sys.stderr, "Multiple definition of tag %r" \
213                     % (canon_name, )
214             block.tags[canon_name] = self._create_tag(canon_name, value)
215         block.comment = '\n'.join(comment_lines)
216         self._blocks[block.name] = block
217
218     def _split_tag_namevalue(self, raw):
219         """Split a line into tag name and value"""
220         parts = raw.split(': ', 1)
221         if len(parts) == 1:
222             tag_name = parts[0]
223             value = ''
224         else:
225             tag_name, value = parts
226         return (tag_name, value)
227
228     def _create_tag(self, tag_name, value):
229         # Tag: bar
230         # Tag: bar opt1 opt2
231         tag = DocTag(tag_name)
232         tag.value = value
233         options, rest = self._parse_options(tag.value)
234         tag.options = options
235         tag.comment = rest
236         return tag
237
238     def _parse_options(self, value):
239         # (foo)
240         # (bar opt1 opt2...)
241         opened = -1
242         options = {}
243         last = None
244         for i, c in enumerate(value):
245             if c == '(' and opened == -1:
246                 opened = i+1
247             if c == ')' and opened != -1:
248                 segment = value[opened:i]
249                 parts = segment.split(' ', 1)
250                 if len(parts) == 2:
251                     name, option = parts
252                 elif len(parts) == 1:
253                     name = parts[0]
254                     option = None
255                 else:
256                     raise AssertionError
257                 if option is not None:
258                     option = Option(option)
259                 options[name] = option
260                 last = i + 2
261                 opened = -1
262
263         if last is not None:
264             rest = value[last:].strip()
265         else:
266             rest = None
267         return options, rest
268
269
270 class AnnotationApplier(object):
271
272     def __init__(self, blocks, transformer):
273         self._blocks = blocks
274         self._transformer = transformer
275
276     def _get_tag(self, block, tag_name):
277         if block is None:
278             return None
279
280         return block.get(tag_name)
281
282     def parse(self, namespace):
283         self._namespace = namespace
284         for node in namespace.nodes[:]:
285             self._parse_node(node)
286         del self._namespace
287
288     # Boring parsing boilerplate.
289
290     def _parse_node(self, node):
291         if isinstance(node, Function):
292             self._parse_function(node)
293         elif isinstance(node, Enum):
294             self._parse_enum(node)
295         elif isinstance(node, Bitfield):
296             self._parse_bitfield(node)
297         elif isinstance(node, Class):
298             self._parse_class(node)
299         elif isinstance(node, Interface):
300             self._parse_interface(node)
301         elif isinstance(node, Callback):
302             self._parse_callback(node)
303         elif isinstance(node, Record):
304             self._parse_record(node)
305         elif isinstance(node, Union):
306             self._parse_union(node)
307         elif isinstance(node, GLibBoxed):
308             self._parse_boxed(node)
309
310     def _parse_class(self, class_):
311         block = self._blocks.get(class_.type_name)
312         self._parse_node_common(class_, block)
313         self._parse_constructors(class_.constructors)
314         self._parse_methods(class_, class_.methods)
315         self._parse_vfuncs(class_, class_.virtual_methods)
316         self._parse_methods(class_, class_.static_methods)
317         self._parse_properties(class_, class_.properties)
318         self._parse_signals(class_, class_.signals)
319         self._parse_fields(class_, class_.fields)
320         if block:
321             class_.doc = block.comment
322
323     def _parse_interface(self, interface):
324         block = self._blocks.get(interface.type_name)
325         self._parse_node_common(interface, block)
326         self._parse_methods(interface, interface.methods)
327         self._parse_vfuncs(interface, interface.virtual_methods)
328         self._parse_properties(interface, interface.properties)
329         self._parse_signals(interface, interface.signals)
330         self._parse_fields(interface, interface.fields)
331         if block:
332             interface.doc = block.comment
333
334     def _parse_record(self, record):
335         block = self._blocks.get(record.symbol)
336         self._parse_node_common(record, block)
337         self._parse_constructors(record.constructors)
338         self._parse_methods(record, record.methods)
339         self._parse_fields(record, record.fields, block)
340         if block:
341             record.doc = block.comment
342
343     def _parse_boxed(self, boxed):
344         block = self._blocks.get(boxed.name)
345         self._parse_node_common(boxed, block)
346         self._parse_constructors(boxed.constructors)
347         self._parse_methods(boxed, boxed.methods)
348         if block:
349             boxed.doc = block.comment
350
351     def _parse_union(self, union):
352         block = self._blocks.get(union.name)
353         self._parse_node_common(union, block)
354         self._parse_fields(union, union.fields, block)
355         self._parse_constructors(union.constructors)
356         self._parse_methods(union, union.methods)
357         if block:
358             union.doc = block.comment
359
360     def _parse_enum(self, enum):
361         block = self._blocks.get(enum.symbol)
362         self._parse_node_common(enum, block)
363         if block:
364             enum.doc = block.comment
365             type_opt = block.options.get(OPT_TYPE)
366             if type_opt and type_opt.one() == OPT_VAL_BITFIELD:
367                 # This is hack, but hey, it works :-)
368                 enum.__class__ = Bitfield
369
370     def _parse_bitfield(self, bitfield):
371         block = self._blocks.get(bitfield.symbol)
372         self._parse_node_common(bitfield, block)
373         if block:
374             bitfield.doc = block.comment
375
376     def _parse_constructors(self, constructors):
377         for ctor in constructors:
378             self._parse_function(ctor)
379
380     def _parse_fields(self, parent, fields, block=None):
381         for field in fields:
382             self._parse_field(parent, field, block)
383
384     def _parse_properties(self, parent, properties):
385         for prop in properties:
386             self._parse_property(parent, prop)
387
388     def _parse_methods(self, parent, methods):
389         for method in methods:
390             self._parse_method(parent, method)
391
392     def _parse_vfuncs(self, parent, vfuncs):
393         for vfunc in vfuncs:
394             self._parse_vfunc(parent, vfunc)
395
396     def _parse_signals(self, parent, signals):
397         for signal in signals:
398             self._parse_signal(parent, signal)
399
400     def _parse_property(self, parent, prop):
401         block = self._blocks.get('%s:%s' % (parent.type_name, prop.name))
402         self._parse_node_common(prop, block)
403         if block:
404             prop.doc = block.comment
405
406     def _parse_callback(self, callback):
407         block = self._blocks.get(callback.ctype)
408         self._parse_node_common(callback, block)
409         self._parse_params(callback, callback.parameters, block)
410         self._parse_return(callback, callback.retval, block)
411         if block:
412             callback.doc = block.comment
413
414     def _parse_callable(self, callable, block):
415         self._parse_node_common(callable, block)
416         for i, param in enumerate(callable.parameters):
417             if param.type.name not in ['DestroyNotify', 'GLib.DestroyNotify']:
418                 continue
419             if i < 2:
420                 break
421             callback_param = callable.parameters[i-2]
422             if callback_param.closure_index != -1:
423                 callback_param.scope = OPT_SCOPE_NOTIFIED
424                 callback_param.transfer = PARAM_TRANSFER_NONE
425
426         self._parse_params(callable, callable.parameters, block)
427         self._parse_return(callable, callable.retval, block)
428         if block:
429             callable.doc = block.comment
430
431     def _parse_function(self, func):
432         block = self._blocks.get(func.symbol)
433         self._parse_callable(func, block)
434         self._parse_rename_to_func(func, block)
435
436     def _parse_signal(self, parent, signal):
437         block = self._blocks.get('%s::%s' % (parent.type_name, signal.name))
438         self._parse_node_common(signal, block)
439         # We're only attempting to name the signal parameters if
440         # the number of parameter tags (@foo) is the same or greater
441         # than the number of signal parameters
442         if block and len(block.tags) > len(signal.parameters):
443             names = block.tags.items()
444         else:
445             names = []
446         for i, param in enumerate(signal.parameters):
447             if names:
448                 name, tag = names[i+1]
449                 param.name = name
450                 options = getattr(tag, 'options', {})
451                 param_type = options.get(OPT_TYPE)
452                 if param_type:
453                     param.type = self._resolve(param_type.one(), param.type)
454             else:
455                 tag = None
456             self._parse_param(signal, param, tag)
457         self._parse_return(signal, signal.retval, block)
458         if block:
459             signal.doc = block.comment
460
461     def _parse_method(self, parent, meth):
462         block = self._blocks.get(meth.symbol)
463         self._parse_function(meth)
464         virtual = self._get_tag(block, TAG_VFUNC)
465         if virtual:
466             invoker_name = virtual.value
467             matched = False
468             for vfunc in parent.virtual_methods:
469                 if vfunc.name == invoker_name:
470                     matched = True
471                     vfunc.invoker = meth.name
472                     break
473             if not matched:
474                 print "warning: unmatched virtual invoker %r for method %r" % \
475                     (invoker_name, meth.symbol)
476
477     def _parse_vfunc(self, parent, vfunc):
478         key = '%s::%s' % (parent.type_name, vfunc.name)
479         self._parse_callable(vfunc, self._blocks.get(key))
480
481     def _parse_field(self, parent, field, block=None):
482         if isinstance(field, Callback):
483             self._parse_callback(field)
484         else:
485             if not block:
486                 return
487             tag = block.get(field.name)
488             if not tag:
489                 return
490             t = tag.options.get('type')
491             if not t:
492                 return
493             field.type.name = self._transformer.resolve_type_name(t.one())
494
495     def _parse_params(self, parent, params, block):
496         for param in params:
497             tag = self._get_tag(block, param.name)
498             self._parse_param(parent, param, tag)
499
500     def _parse_return(self, parent, return_, block):
501         tag = self._get_tag(block, TAG_RETURNS)
502         self._parse_param_ret_common(parent, return_, tag)
503
504     def _parse_param(self, parent, param, tag):
505         options = getattr(tag, 'options', {})
506         if isinstance(parent, Function) and not param.scope:
507             scope = options.get(OPT_SCOPE)
508             if scope:
509                 param.scope = scope.one()
510                 param.transfer = PARAM_TRANSFER_NONE
511             elif param.type.name in ['AsyncReadyCallback',
512                                      'Gio.AsyncReadyCallback']:
513                 param.scope = OPT_SCOPE_ASYNC
514                 param.transfer = PARAM_TRANSFER_NONE
515
516             destroy = options.get(OPT_DESTROY)
517             if destroy:
518                 param.destroy_index = parent.get_parameter_index(destroy.one())
519                 self._fixup_param_destroy(parent, param)
520             closure = options.get(OPT_CLOSURE)
521             if closure:
522                 param.closure_index = parent.get_parameter_index(closure.one())
523                 self._fixup_param_closure(parent, param)
524         if isinstance(parent, Callback):
525             if OPT_CLOSURE in options:
526                 param.closure_index = parent.get_parameter_index(param.name)
527                 self._fixup_param_closure(parent, param)
528
529         self._parse_param_ret_common(parent, param, tag)
530
531     def _fixup_param_destroy(self, parent, param):
532         for p in parent.parameters:
533             if p is not param and p.destroy_index == param.destroy_index:
534                 p.destroy_index = None
535
536     def _fixup_param_closure(self, parent, param):
537         for p in parent.parameters:
538             if p is not param and p.closure_index == param.closure_index:
539                 p.closure_index = None
540
541     def _parse_param_ret_common(self, parent, node, tag):
542         options = getattr(tag, 'options', {})
543         node.direction = self._extract_direction(node, options)
544         container_type = self._extract_container_type(
545             parent, node, options)
546         if container_type is not None:
547             node.type = container_type
548         if node.direction is None:
549             node.direction = self._guess_direction(node)
550         node.transfer = self._extract_transfer(parent, node, options)
551         if OPT_ALLOW_NONE in options:
552             node.allow_none = True
553         param_type = options.get(OPT_TYPE)
554         if param_type:
555             node.type = self._resolve(param_type.one(), node.type)
556
557         assert node.transfer is not None
558         if tag is not None and tag.comment is not None:
559             node.doc = tag.comment
560
561     def _extract_direction(self, node, options):
562         if (OPT_INOUT in options or
563             OPT_INOUT_ALT in options):
564             direction = PARAM_DIRECTION_INOUT
565         elif OPT_OUT in options:
566             direction = PARAM_DIRECTION_OUT
567         elif OPT_IN in options:
568             direction = PARAM_DIRECTION_IN
569         else:
570             direction = node.direction
571         return direction
572
573     def _guess_array(self, node):
574         ctype = node.type.ctype
575         if ctype is None:
576             return False
577         if not ctype.endswith('*'):
578             return False
579         if node.type.canonical in default_array_types:
580             return True
581         return False
582
583     def _extract_container_type(self, parent, node, options):
584         has_element_type = OPT_ELEMENT_TYPE in options
585         has_array = OPT_ARRAY in options
586
587         # FIXME: This is a hack :-(
588         if (not isinstance(node, Field) and
589             (not has_element_type and
590              (node.direction is None
591               or isinstance(node, Return)
592               or node.direction == PARAM_DIRECTION_IN))):
593             if self._guess_array(node):
594                 has_array = True
595
596         if has_array:
597             container_type = self._parse_array(parent, node, options)
598         elif has_element_type:
599             container_type = self._parse_element_type(parent, node, options)
600         else:
601             container_type = None
602
603         return container_type
604
605     def _parse_array(self, parent, node, options):
606         array_opt = options.get(OPT_ARRAY)
607         if array_opt:
608             array_values = array_opt.all()
609         else:
610             array_values = {}
611
612         element_type = options.get(OPT_ELEMENT_TYPE)
613         if element_type is not None:
614             element_type_node = self._resolve(element_type.one())
615         else:
616             element_type_node = Type(node.type.name) # erase ctype
617
618         container_type = Array(node.type.ctype,
619                                element_type_node)
620         container_type.is_const = node.type.is_const
621         if OPT_ARRAY_ZERO_TERMINATED in array_values:
622             container_type.zeroterminated = array_values.get(
623                 OPT_ARRAY_ZERO_TERMINATED) == '1'
624         length = array_values.get(OPT_ARRAY_LENGTH)
625         if length is not None:
626             param_index = parent.get_parameter_index(length)
627             container_type.length_param_index = param_index
628             # For in parameters we're incorrectly deferring
629             # char/unsigned char to utf8 when a length annotation
630             # is specified.
631             if (isinstance(node, Parameter) and
632                 node.type.name == 'utf8' and
633                 self._guess_direction(node) == PARAM_DIRECTION_IN and
634                 element_type is None):
635                 # FIXME: unsigned char/guchar should be uint8
636                 container_type.element_type = Type('int8')
637         container_type.size = array_values.get(OPT_ARRAY_FIXED_SIZE)
638         return container_type
639
640     def _resolve(self, type_str, orig_node=None):
641         def grab_one(type_str, resolver, top_combiner, combiner):
642             """Return a complete type, and the trailing string part after it.
643             Use resolver() on each identifier, and combiner() on the parts of
644             each complete type. (top_combiner is used on the top-most type.)"""
645             bits = re.split(r'([,<>])', type_str, 1)
646             first, sep, rest = [bits[0], '', ''] if (len(bits)==1) else bits
647             args = [resolver(first)]
648             if sep == '<':
649                 while sep != '>':
650                     next, rest = grab_one(rest, resolver, combiner, combiner)
651                     args.append(next)
652                     sep, rest = rest[0], rest[1:]
653             else:
654                 rest = sep + rest
655             return top_combiner(*args), rest
656         def resolver(ident):
657             return self._transformer.resolve_param_type(Type(ident))
658         def combiner(base, *rest):
659             if not rest:
660                 return base
661             if base.name in ['GLib.List', 'GLib.SList'] and len(rest)==1:
662                 return List(base.name, base.ctype, *rest)
663             if base.name in ['GLib.HashTable'] and len(rest)==2:
664                 return Map(base.name, base.ctype, *rest)
665             print "WARNING: throwing away type parameters:", type_str
666             return base
667         def top_combiner(base, *rest):
668             """For the top most type, recycle orig_node if possible."""
669             if orig_node is not None:
670                 orig_node.name = base.name
671                 base = orig_node # preserve other properties of orig_node
672             return combiner(base, *rest)
673
674         result, rest = grab_one(type_str, resolver, top_combiner, combiner)
675         if rest:
676             print "WARNING: throwing away trailing part of type:", type_str
677         return result
678
679     def _parse_element_type(self, parent, node, options):
680         element_type_opt = options.get(OPT_ELEMENT_TYPE)
681         element_type = element_type_opt.flat()
682         if node.type.name in ['GLib.List', 'GLib.SList']:
683             assert len(element_type) == 1
684             container_type = List(
685                 node.type.name,
686                 node.type.ctype,
687                 self._resolve(element_type[0]))
688         elif node.type.name in ['GLib.HashTable']:
689             assert len(element_type) == 2
690             container_type = Map(
691                 node.type.name,
692                 node.type.ctype,
693                 self._resolve(element_type[0]),
694                 self._resolve(element_type[1]))
695         else:
696             print 'FIXME: unhandled element-type container:', node
697         return container_type
698
699     def _extract_transfer(self, parent, node, options):
700         transfer_opt = options.get(OPT_TRANSFER)
701         if transfer_opt is None:
702             transfer = self._guess_transfer(node, options)
703         else:
704             transfer = transfer_opt.one()
705             if transfer is None:
706                 transfer = PARAM_TRANSFER_FULL
707             if transfer not in [PARAM_TRANSFER_NONE,
708                                 PARAM_TRANSFER_CONTAINER,
709                                 PARAM_TRANSFER_FULL]:
710                 raise InvalidAnnotationError(
711                     "transfer for %s of %r is invalid (%r), must be one of "
712                     "none, container, full." % (node, parent.name, transfer))
713         return transfer
714
715     def _parse_node_common(self, node, block):
716         self._parse_version(node, block)
717         self._parse_deprecated(node, block)
718         self._parse_attributes(node, block)
719         self._parse_skip(node, block)
720
721     def _parse_version(self, node, block):
722         since_tag = self._get_tag(block, TAG_SINCE)
723         if since_tag is None:
724             return
725         node.version = since_tag.value
726
727     def _parse_deprecated(self, node, block):
728         deprecated_tag = self._get_tag(block, TAG_DEPRECATED)
729         if deprecated_tag is None:
730             return
731         value = deprecated_tag.value
732         if ': ' in value:
733             version, desc = value.split(': ')
734         else:
735             desc = value
736             version = None
737         node.deprecated = desc
738         if version is not None:
739             node.deprecated_version = version
740
741     def _parse_attributes(self, node, block):
742         annos_tag = self._get_tag(block, TAG_ATTRIBUTES)
743         if annos_tag is None:
744             return
745         for key, value in annos_tag.options.iteritems():
746             node.attributes.append((key, value.one()))
747
748     def _parse_skip(self, node, block):
749         if block is not None:
750             if OPT_SKIP in block.options:
751                 node.skip = True
752
753     def _parse_rename_to_func(self, node, block):
754         rename_to_tag = self._get_tag(block, TAG_RENAME_TO)
755         if rename_to_tag is None:
756             return
757         new_name = rename_to_tag.value
758
759         shadowed = []
760
761         def shadowed_filter(n):
762             if isinstance(n, Function) and n.symbol == new_name:
763                 shadowed.append(n)
764                 return False
765             return True
766
767         self._namespace.remove_matching(shadowed_filter)
768         if len(shadowed) == 1:
769             # method override; use the same (stripped) name as the overloaded
770             # method referenced.
771             # Note that 'g_timeout_add_full' may specify a new_name of
772             # 'g_timeout_add' but the *real* name desired is the stripped name
773             # of 'g_timeout_add' which is 'timeout_add' (for example).
774             node.name = shadowed[0].name
775         elif len(shadowed) == 0:
776             # literal rename, to force a particular prefix strip or whatever
777             # Example: the "nm-utils" package uses a "NM" prefix in most places
778             # but some functions have an "nm_utils_" prefix; the 'Rename To:'
779             # annotation in this case is used to strip the 'utils_' part off.
780             node.name = new_name
781         else:
782             assert False # more than two shadowed methods?  Shouldn't happen.
783
784     def _guess_direction(self, node):
785         if node.direction:
786             return node.direction
787         is_pointer = False
788         if node.type.ctype:
789             is_pointer = '*' in node.type.ctype
790
791         if is_pointer and node.type.name in BASIC_GIR_TYPES:
792             return PARAM_DIRECTION_OUT
793
794         return PARAM_DIRECTION_IN
795
796     def _guess_transfer(self, node, options):
797         if node.transfer is not None:
798             return node.transfer
799
800         # Anything with 'const' gets none
801         if node.type.is_const:
802             return PARAM_TRANSFER_NONE
803         elif node.type.name in [TYPE_NONE, TYPE_ANY]:
804             return PARAM_TRANSFER_NONE
805         elif isinstance(node.type, Varargs):
806             return PARAM_TRANSFER_NONE
807         elif isinstance(node, Parameter):
808             if node.direction in [PARAM_DIRECTION_INOUT,
809                                   PARAM_DIRECTION_OUT]:
810                 return PARAM_TRANSFER_FULL
811             # This one is a hack for compatibility; the transfer
812             # for string parameters really has no defined meaning.
813             elif node.type.canonical == 'utf8':
814                 return PARAM_TRANSFER_FULL
815             else:
816                 return PARAM_TRANSFER_NONE
817         elif isinstance(node, Return):
818             if (node.type.canonical in BASIC_GIR_TYPES or
819                 (node.type.canonical in [TYPE_NONE, TYPE_ANY] and
820                  node.type.is_const)):
821                 return PARAM_TRANSFER_NONE
822             else:
823                 return PARAM_TRANSFER_FULL
824         elif isinstance(node, Field):
825             return PARAM_TRANSFER_NONE
826         else:
827             raise AssertionError(node)