Fix an annotationparser bug for empty tags
[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, (
213                     "Symbol %s has multiple definition of tag %r" % (
214                     block_name, canon_name, ))
215             block.tags[canon_name] = self._create_tag(canon_name, value)
216         block.comment = '\n'.join(comment_lines)
217         self._blocks[block.name] = block
218
219     def _split_tag_namevalue(self, raw):
220         """Split a line into tag name and value"""
221         parts = raw.split(': ', 1)
222         if len(parts) == 1:
223             tag_name = parts[0]
224             value = ''
225             if tag_name.endswith(':'):
226                 tag_name = tag_name[:-1]
227         else:
228             tag_name, value = parts
229         return (tag_name, value)
230
231     def _create_tag(self, tag_name, value):
232         # Tag: bar
233         # Tag: bar opt1 opt2
234         tag = DocTag(tag_name)
235         tag.value = value
236         options, rest = self._parse_options(tag.value)
237         tag.options = options
238         tag.comment = rest
239         return tag
240
241     def _parse_options(self, value):
242         # (foo)
243         # (bar opt1 opt2...)
244         opened = -1
245         options = {}
246         last = None
247         for i, c in enumerate(value):
248             if c == '(' and opened == -1:
249                 opened = i+1
250             if c == ')' and opened != -1:
251                 segment = value[opened:i]
252                 parts = segment.split(' ', 1)
253                 if len(parts) == 2:
254                     name, option = parts
255                 elif len(parts) == 1:
256                     name = parts[0]
257                     option = None
258                 else:
259                     raise AssertionError
260                 if option is not None:
261                     option = Option(option)
262                 options[name] = option
263                 last = i + 2
264                 opened = -1
265
266         if last is not None:
267             rest = value[last:].strip()
268         else:
269             rest = None
270         return options, rest
271
272
273 class AnnotationApplier(object):
274
275     def __init__(self, blocks, transformer):
276         self._blocks = blocks
277         self._transformer = transformer
278
279     def _get_tag(self, block, tag_name):
280         if block is None:
281             return None
282
283         return block.get(tag_name)
284
285     def parse(self, namespace):
286         self._namespace = namespace
287         for node in namespace.nodes[:]:
288             self._parse_node(node)
289         del self._namespace
290
291     # Boring parsing boilerplate.
292
293     def _parse_node(self, node):
294         if isinstance(node, Function):
295             self._parse_function(node)
296         elif isinstance(node, Enum):
297             self._parse_enum(node)
298         elif isinstance(node, Bitfield):
299             self._parse_bitfield(node)
300         elif isinstance(node, Class):
301             self._parse_class(node)
302         elif isinstance(node, Interface):
303             self._parse_interface(node)
304         elif isinstance(node, Callback):
305             self._parse_callback(node)
306         elif isinstance(node, Record):
307             self._parse_record(node)
308         elif isinstance(node, Union):
309             self._parse_union(node)
310         elif isinstance(node, GLibBoxed):
311             self._parse_boxed(node)
312
313     def _parse_class(self, class_):
314         block = self._blocks.get(class_.type_name)
315         self._parse_node_common(class_, block)
316         self._parse_constructors(class_.constructors)
317         self._parse_methods(class_, class_.methods)
318         self._parse_vfuncs(class_, class_.virtual_methods)
319         self._parse_methods(class_, class_.static_methods)
320         self._parse_properties(class_, class_.properties)
321         self._parse_signals(class_, class_.signals)
322         self._parse_fields(class_, class_.fields)
323         if block:
324             class_.doc = block.comment
325
326     def _parse_interface(self, interface):
327         block = self._blocks.get(interface.type_name)
328         self._parse_node_common(interface, block)
329         self._parse_methods(interface, interface.methods)
330         self._parse_vfuncs(interface, interface.virtual_methods)
331         self._parse_properties(interface, interface.properties)
332         self._parse_signals(interface, interface.signals)
333         self._parse_fields(interface, interface.fields)
334         if block:
335             interface.doc = block.comment
336
337     def _parse_record(self, record):
338         block = self._blocks.get(record.symbol)
339         self._parse_node_common(record, block)
340         self._parse_constructors(record.constructors)
341         self._parse_methods(record, record.methods)
342         self._parse_fields(record, record.fields, block)
343         if block:
344             record.doc = block.comment
345
346     def _parse_boxed(self, boxed):
347         block = self._blocks.get(boxed.name)
348         self._parse_node_common(boxed, block)
349         self._parse_constructors(boxed.constructors)
350         self._parse_methods(boxed, boxed.methods)
351         if block:
352             boxed.doc = block.comment
353
354     def _parse_union(self, union):
355         block = self._blocks.get(union.name)
356         self._parse_node_common(union, block)
357         self._parse_fields(union, union.fields, block)
358         self._parse_constructors(union.constructors)
359         self._parse_methods(union, union.methods)
360         if block:
361             union.doc = block.comment
362
363     def _parse_enum(self, enum):
364         block = self._blocks.get(enum.symbol)
365         self._parse_node_common(enum, block)
366         if block:
367             enum.doc = block.comment
368             type_opt = block.options.get(OPT_TYPE)
369             if type_opt and type_opt.one() == OPT_VAL_BITFIELD:
370                 # This is hack, but hey, it works :-)
371                 enum.__class__ = Bitfield
372
373     def _parse_bitfield(self, bitfield):
374         block = self._blocks.get(bitfield.symbol)
375         self._parse_node_common(bitfield, block)
376         if block:
377             bitfield.doc = block.comment
378
379     def _parse_constructors(self, constructors):
380         for ctor in constructors:
381             self._parse_function(ctor)
382
383     def _parse_fields(self, parent, fields, block=None):
384         for field in fields:
385             self._parse_field(parent, field, block)
386
387     def _parse_properties(self, parent, properties):
388         for prop in properties:
389             self._parse_property(parent, prop)
390
391     def _parse_methods(self, parent, methods):
392         for method in methods:
393             self._parse_method(parent, method)
394
395     def _parse_vfuncs(self, parent, vfuncs):
396         for vfunc in vfuncs:
397             self._parse_vfunc(parent, vfunc)
398
399     def _parse_signals(self, parent, signals):
400         for signal in signals:
401             self._parse_signal(parent, signal)
402
403     def _parse_property(self, parent, prop):
404         block = self._blocks.get('%s:%s' % (parent.type_name, prop.name))
405         self._parse_node_common(prop, block)
406         if block:
407             prop.doc = block.comment
408
409     def _parse_callback(self, callback):
410         block = self._blocks.get(callback.ctype)
411         self._parse_node_common(callback, block)
412         self._parse_params(callback, callback.parameters, block)
413         self._parse_return(callback, callback.retval, block)
414         if block:
415             callback.doc = block.comment
416
417     def _parse_callable(self, callable, block):
418         self._parse_node_common(callable, block)
419         for i, param in enumerate(callable.parameters):
420             if param.type.name not in ['DestroyNotify', 'GLib.DestroyNotify']:
421                 continue
422             if i < 2:
423                 break
424             callback_param = callable.parameters[i-2]
425             if callback_param.closure_index != -1:
426                 callback_param.scope = OPT_SCOPE_NOTIFIED
427                 callback_param.transfer = PARAM_TRANSFER_NONE
428
429         self._parse_params(callable, callable.parameters, block)
430         self._parse_return(callable, callable.retval, block)
431         if block:
432             callable.doc = block.comment
433
434     def _parse_function(self, func):
435         block = self._blocks.get(func.symbol)
436         self._parse_callable(func, block)
437         self._parse_rename_to_func(func, block)
438
439     def _parse_signal(self, parent, signal):
440         block = self._blocks.get('%s::%s' % (parent.type_name, signal.name))
441         self._parse_node_common(signal, block)
442         # We're only attempting to name the signal parameters if
443         # the number of parameter tags (@foo) is the same or greater
444         # than the number of signal parameters
445         if block and len(block.tags) > len(signal.parameters):
446             names = block.tags.items()
447         else:
448             names = []
449         for i, param in enumerate(signal.parameters):
450             if names:
451                 name, tag = names[i+1]
452                 param.name = name
453                 options = getattr(tag, 'options', {})
454                 param_type = options.get(OPT_TYPE)
455                 if param_type:
456                     param.type = self._resolve(param_type.one(), param.type)
457             else:
458                 tag = None
459             self._parse_param(signal, param, tag)
460         self._parse_return(signal, signal.retval, block)
461         if block:
462             signal.doc = block.comment
463
464     def _parse_method(self, parent, meth):
465         block = self._blocks.get(meth.symbol)
466         self._parse_function(meth)
467         virtual = self._get_tag(block, TAG_VFUNC)
468         if virtual:
469             invoker_name = virtual.value
470             matched = False
471             for vfunc in parent.virtual_methods:
472                 if vfunc.name == invoker_name:
473                     matched = True
474                     vfunc.invoker = meth.name
475                     break
476             if not matched:
477                 print "warning: unmatched virtual invoker %r for method %r" % \
478                     (invoker_name, meth.symbol)
479
480     def _parse_vfunc(self, parent, vfunc):
481         key = '%s::%s' % (parent.type_name, vfunc.name)
482         self._parse_callable(vfunc, self._blocks.get(key))
483
484     def _parse_field(self, parent, field, block=None):
485         if isinstance(field, Callback):
486             self._parse_callback(field)
487         else:
488             if not block:
489                 return
490             tag = block.get(field.name)
491             if not tag:
492                 return
493             t = tag.options.get('type')
494             if not t:
495                 return
496             field.type.name = self._transformer.resolve_type_name(t.one())
497
498     def _parse_params(self, parent, params, block):
499         for param in params:
500             tag = self._get_tag(block, param.name)
501             self._parse_param(parent, param, tag)
502
503     def _parse_return(self, parent, return_, block):
504         tag = self._get_tag(block, TAG_RETURNS)
505         self._parse_param_ret_common(parent, return_, tag)
506
507     def _parse_param(self, parent, param, tag):
508         options = getattr(tag, 'options', {})
509         if isinstance(parent, Function) and not param.scope:
510             scope = options.get(OPT_SCOPE)
511             if scope:
512                 param.scope = scope.one()
513                 param.transfer = PARAM_TRANSFER_NONE
514             elif param.type.name in ['AsyncReadyCallback',
515                                      'Gio.AsyncReadyCallback']:
516                 param.scope = OPT_SCOPE_ASYNC
517                 param.transfer = PARAM_TRANSFER_NONE
518
519             destroy = options.get(OPT_DESTROY)
520             if destroy:
521                 param.destroy_index = parent.get_parameter_index(destroy.one())
522                 self._fixup_param_destroy(parent, param)
523             closure = options.get(OPT_CLOSURE)
524             if closure:
525                 param.closure_index = parent.get_parameter_index(closure.one())
526                 self._fixup_param_closure(parent, param)
527         if isinstance(parent, Callback):
528             if OPT_CLOSURE in options:
529                 param.closure_index = parent.get_parameter_index(param.name)
530                 self._fixup_param_closure(parent, param)
531
532         self._parse_param_ret_common(parent, param, tag)
533
534     def _fixup_param_destroy(self, parent, param):
535         for p in parent.parameters:
536             if p is not param and p.destroy_index == param.destroy_index:
537                 p.destroy_index = None
538
539     def _fixup_param_closure(self, parent, param):
540         for p in parent.parameters:
541             if p is not param and p.closure_index == param.closure_index:
542                 p.closure_index = None
543
544     def _parse_param_ret_common(self, parent, node, tag):
545         options = getattr(tag, 'options', {})
546         node.direction = self._extract_direction(node, options)
547         container_type = self._extract_container_type(
548             parent, node, options)
549         if container_type is not None:
550             node.type = container_type
551         if node.direction is None:
552             node.direction = self._guess_direction(node)
553         node.transfer = self._extract_transfer(parent, node, options)
554         if OPT_ALLOW_NONE in options:
555             node.allow_none = True
556         param_type = options.get(OPT_TYPE)
557         if param_type:
558             node.type = self._resolve(param_type.one(), node.type)
559
560         assert node.transfer is not None
561         if tag is not None and tag.comment is not None:
562             node.doc = tag.comment
563
564     def _extract_direction(self, node, options):
565         if (OPT_INOUT in options or
566             OPT_INOUT_ALT in options):
567             direction = PARAM_DIRECTION_INOUT
568         elif OPT_OUT in options:
569             direction = PARAM_DIRECTION_OUT
570         elif OPT_IN in options:
571             direction = PARAM_DIRECTION_IN
572         else:
573             direction = node.direction
574         return direction
575
576     def _guess_array(self, node):
577         ctype = node.type.ctype
578         if ctype is None:
579             return False
580         if not ctype.endswith('*'):
581             return False
582         if node.type.canonical in default_array_types:
583             return True
584         return False
585
586     def _extract_container_type(self, parent, node, options):
587         has_element_type = OPT_ELEMENT_TYPE in options
588         has_array = OPT_ARRAY in options
589
590         # FIXME: This is a hack :-(
591         if (not isinstance(node, Field) and
592             (not has_element_type and
593              (node.direction is None
594               or isinstance(node, Return)
595               or node.direction == PARAM_DIRECTION_IN))):
596             if self._guess_array(node):
597                 has_array = True
598
599         if has_array:
600             container_type = self._parse_array(parent, node, options)
601         elif has_element_type:
602             container_type = self._parse_element_type(parent, node, options)
603         else:
604             container_type = None
605
606         return container_type
607
608     def _parse_array(self, parent, node, options):
609         array_opt = options.get(OPT_ARRAY)
610         if array_opt:
611             array_values = array_opt.all()
612         else:
613             array_values = {}
614
615         element_type = options.get(OPT_ELEMENT_TYPE)
616         if element_type is not None:
617             element_type_node = self._resolve(element_type.one())
618         else:
619             element_type_node = Type(node.type.name) # erase ctype
620
621         container_type = Array(node.type.ctype,
622                                element_type_node)
623         container_type.is_const = node.type.is_const
624         if OPT_ARRAY_ZERO_TERMINATED in array_values:
625             container_type.zeroterminated = array_values.get(
626                 OPT_ARRAY_ZERO_TERMINATED) == '1'
627         length = array_values.get(OPT_ARRAY_LENGTH)
628         if length is not None:
629             param_index = parent.get_parameter_index(length)
630             container_type.length_param_index = param_index
631             # For in parameters we're incorrectly deferring
632             # char/unsigned char to utf8 when a length annotation
633             # is specified.
634             if (isinstance(node, Parameter) and
635                 node.type.name == 'utf8' and
636                 self._guess_direction(node) == PARAM_DIRECTION_IN and
637                 element_type is None):
638                 # FIXME: unsigned char/guchar should be uint8
639                 container_type.element_type = Type('int8')
640         container_type.size = array_values.get(OPT_ARRAY_FIXED_SIZE)
641         return container_type
642
643     def _resolve(self, type_str, orig_node=None):
644         def grab_one(type_str, resolver, top_combiner, combiner):
645             """Return a complete type, and the trailing string part after it.
646             Use resolver() on each identifier, and combiner() on the parts of
647             each complete type. (top_combiner is used on the top-most type.)"""
648             bits = re.split(r'([,<>])', type_str, 1)
649             first, sep, rest = [bits[0], '', ''] if (len(bits)==1) else bits
650             args = [resolver(first)]
651             if sep == '<':
652                 while sep != '>':
653                     next, rest = grab_one(rest, resolver, combiner, combiner)
654                     args.append(next)
655                     sep, rest = rest[0], rest[1:]
656             else:
657                 rest = sep + rest
658             return top_combiner(*args), rest
659         def resolver(ident):
660             return self._transformer.resolve_param_type(Type(ident))
661         def combiner(base, *rest):
662             if not rest:
663                 return base
664             if base.name in ['GLib.List', 'GLib.SList'] and len(rest)==1:
665                 return List(base.name, base.ctype, *rest)
666             if base.name in ['GLib.HashTable'] and len(rest)==2:
667                 return Map(base.name, base.ctype, *rest)
668             print "WARNING: throwing away type parameters:", type_str
669             return base
670         def top_combiner(base, *rest):
671             """For the top most type, recycle orig_node if possible."""
672             if orig_node is not None:
673                 orig_node.name = base.name
674                 base = orig_node # preserve other properties of orig_node
675             return combiner(base, *rest)
676
677         result, rest = grab_one(type_str, resolver, top_combiner, combiner)
678         if rest:
679             print "WARNING: throwing away trailing part of type:", type_str
680         return result
681
682     def _parse_element_type(self, parent, node, options):
683         element_type_opt = options.get(OPT_ELEMENT_TYPE)
684         element_type = element_type_opt.flat()
685         if node.type.name in ['GLib.List', 'GLib.SList']:
686             assert len(element_type) == 1
687             container_type = List(
688                 node.type.name,
689                 node.type.ctype,
690                 self._resolve(element_type[0]))
691         elif node.type.name in ['GLib.HashTable']:
692             assert len(element_type) == 2
693             container_type = Map(
694                 node.type.name,
695                 node.type.ctype,
696                 self._resolve(element_type[0]),
697                 self._resolve(element_type[1]))
698         else:
699             print 'FIXME: unhandled element-type container:', node
700         return container_type
701
702     def _extract_transfer(self, parent, node, options):
703         transfer_opt = options.get(OPT_TRANSFER)
704         if transfer_opt is None:
705             transfer = self._guess_transfer(node, options)
706         else:
707             transfer = transfer_opt.one()
708             if transfer is None:
709                 transfer = PARAM_TRANSFER_FULL
710             if transfer not in [PARAM_TRANSFER_NONE,
711                                 PARAM_TRANSFER_CONTAINER,
712                                 PARAM_TRANSFER_FULL]:
713                 raise InvalidAnnotationError(
714                     "transfer for %s of %r is invalid (%r), must be one of "
715                     "none, container, full." % (node, parent.name, transfer))
716         return transfer
717
718     def _parse_node_common(self, node, block):
719         self._parse_version(node, block)
720         self._parse_deprecated(node, block)
721         self._parse_attributes(node, block)
722         self._parse_skip(node, block)
723
724     def _parse_version(self, node, block):
725         since_tag = self._get_tag(block, TAG_SINCE)
726         if since_tag is None:
727             return
728         node.version = since_tag.value
729
730     def _parse_deprecated(self, node, block):
731         deprecated_tag = self._get_tag(block, TAG_DEPRECATED)
732         if deprecated_tag is None:
733             return
734         value = deprecated_tag.value
735         if ': ' in value:
736             version, desc = value.split(': ')
737         else:
738             desc = value
739             version = None
740         node.deprecated = desc
741         if version is not None:
742             node.deprecated_version = version
743
744     def _parse_attributes(self, node, block):
745         annos_tag = self._get_tag(block, TAG_ATTRIBUTES)
746         if annos_tag is None:
747             return
748         for key, value in annos_tag.options.iteritems():
749             node.attributes.append((key, value.one()))
750
751     def _parse_skip(self, node, block):
752         if block is not None:
753             if OPT_SKIP in block.options:
754                 node.skip = True
755
756     def _parse_rename_to_func(self, node, block):
757         rename_to_tag = self._get_tag(block, TAG_RENAME_TO)
758         if rename_to_tag is None:
759             return
760         new_name = rename_to_tag.value
761
762         shadowed = []
763
764         def shadowed_filter(n):
765             if isinstance(n, Function) and n.symbol == new_name:
766                 shadowed.append(n)
767                 return False
768             return True
769
770         self._namespace.remove_matching(shadowed_filter)
771         if len(shadowed) == 1:
772             # method override; use the same (stripped) name as the overloaded
773             # method referenced.
774             # Note that 'g_timeout_add_full' may specify a new_name of
775             # 'g_timeout_add' but the *real* name desired is the stripped name
776             # of 'g_timeout_add' which is 'timeout_add' (for example).
777             node.name = shadowed[0].name
778         elif len(shadowed) == 0:
779             # literal rename, to force a particular prefix strip or whatever
780             # Example: the "nm-utils" package uses a "NM" prefix in most places
781             # but some functions have an "nm_utils_" prefix; the 'Rename To:'
782             # annotation in this case is used to strip the 'utils_' part off.
783             node.name = new_name
784         else:
785             assert False # more than two shadowed methods?  Shouldn't happen.
786
787     def _guess_direction(self, node):
788         if node.direction:
789             return node.direction
790         is_pointer = False
791         if node.type.ctype:
792             is_pointer = '*' in node.type.ctype
793
794         if is_pointer and node.type.name in BASIC_GIR_TYPES:
795             return PARAM_DIRECTION_OUT
796
797         return PARAM_DIRECTION_IN
798
799     def _guess_transfer(self, node, options):
800         if node.transfer is not None:
801             return node.transfer
802
803         # Anything with 'const' gets none
804         if node.type.is_const:
805             return PARAM_TRANSFER_NONE
806         elif node.type.name in [TYPE_NONE, TYPE_ANY]:
807             return PARAM_TRANSFER_NONE
808         elif isinstance(node.type, Varargs):
809             return PARAM_TRANSFER_NONE
810         elif isinstance(node, Parameter):
811             if node.direction in [PARAM_DIRECTION_INOUT,
812                                   PARAM_DIRECTION_OUT]:
813                 return PARAM_TRANSFER_FULL
814             # This one is a hack for compatibility; the transfer
815             # for string parameters really has no defined meaning.
816             elif node.type.canonical == 'utf8':
817                 return PARAM_TRANSFER_FULL
818             else:
819                 return PARAM_TRANSFER_NONE
820         elif isinstance(node, Return):
821             if (node.type.canonical in BASIC_GIR_TYPES or
822                 (node.type.canonical in [TYPE_NONE, TYPE_ANY] and
823                  node.type.is_const)):
824                 return PARAM_TRANSFER_NONE
825             else:
826                 return PARAM_TRANSFER_FULL
827         elif isinstance(node, Field):
828             return PARAM_TRANSFER_NONE
829         else:
830             raise AssertionError(node)