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