summaryrefslogtreecommitdiff
path: root/giscanner/maintransformer.py
diff options
context:
space:
mode:
Diffstat (limited to 'giscanner/maintransformer.py')
-rw-r--r--giscanner/maintransformer.py933
1 files changed, 933 insertions, 0 deletions
diff --git a/giscanner/maintransformer.py b/giscanner/maintransformer.py
new file mode 100644
index 00000000..ac180155
--- /dev/null
+++ b/giscanner/maintransformer.py
@@ -0,0 +1,933 @@
+# -*- Mode: Python -*-
+# Copyright (C) 2010 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+#
+
+import re
+
+from . import ast
+from . import glibast
+from .annotationparser import (TAG_VFUNC, TAG_SINCE, TAG_DEPRECATED, TAG_RETURNS,
+ TAG_ATTRIBUTES, TAG_RENAME_TO, TAG_TYPE, TAG_TRANSFER,
+ TAG_UNREF_FUNC, TAG_REF_FUNC, TAG_SET_VALUE_FUNC,
+ TAG_GET_VALUE_FUNC)
+from .annotationparser import (OPT_ALLOW_NONE,
+ OPT_ARRAY, OPT_ELEMENT_TYPE, OPT_IN, OPT_INOUT,
+ OPT_INOUT_ALT, OPT_OUT, OPT_SCOPE,
+ OPT_TYPE, OPT_CLOSURE, OPT_DESTROY, OPT_SKIP,
+ OPT_FOREIGN, OPT_ARRAY_FIXED_SIZE,
+ OPT_ARRAY_LENGTH, OPT_ARRAY_ZERO_TERMINATED)
+from .annotationparser import AnnotationParser
+from .utils import to_underscores, to_underscores_noprefix
+
+class MainTransformer(object):
+
+ def __init__(self, transformer, blocks):
+ self._transformer = transformer
+ self._blocks = blocks
+ self._namespace = transformer.namespace
+ self._uscore_type_names = {}
+
+ # Public API
+
+ def transform(self):
+ # We have a rough tree which should have most of of the types
+ # we know about. Let's attempt closure; walk over all of the
+ # Type() types and see if they match up with something.
+ self._namespace.walk(self._pass_type_resolution)
+
+ # Determine some default values for transfer etc.
+ # based on the current tree.
+ self._namespace.walk(self._pass_callable_defaults)
+
+ # Read in most annotations now.
+ self._namespace.walk(self._pass_read_annotations)
+
+ # Now that we've possibly seen more types from annotations,
+ # do another type resolution pass.
+ self._namespace.walk(self._pass_type_resolution)
+
+ # Generate a reverse mapping "bar_baz" -> BarBaz
+ for node in self._namespace.itervalues():
+ if isinstance(node, (ast.Class, ast.Interface, glibast.GLibBoxed)):
+ self._uscore_type_names[node.c_symbol_prefix] = node
+ elif isinstance(node, (ast.Record, ast.Union)):
+ uscored = to_underscores_noprefix(node.name).lower()
+ self._uscore_type_names[uscored] = node
+
+ for node in list(self._namespace.itervalues()):
+ if isinstance(node, ast.Function):
+ # Discover which toplevel functions are actually methods
+ self._pair_function(node)
+ if isinstance(node, (ast.Class, ast.Interface)):
+ self._pair_class_virtuals(node)
+
+ # Some annotations need to be post function pairing
+ self._namespace.walk(self._pass_read_annotations2)
+
+ # Another type resolution pass after we've parsed virtuals, etc.
+ self._namespace.walk(self._pass_type_resolution)
+
+ self._namespace.walk(self._pass3)
+
+ # TODO - merge into pass3
+ self._resolve_quarks()
+
+ # Private
+
+ def _get_validate_parameter_name(self, parent, param_name, origin):
+ try:
+ param = parent.get_parameter(param_name)
+ except ValueError, e:
+ param = None
+ if param is None:
+ if isinstance(origin, ast.Parameter):
+ origin_name = 'parameter %s' % (origin.argname, )
+ else:
+ origin_name = 'return value'
+ self._transformer.log_node_warning(parent,
+ "can't find parameter %s referenced by %s of %r"
+ % (param_name, origin_name, parent.name), fatal=True)
+
+ return param.argname
+
+ def _apply_annotation_rename_to(self, node, chain, block):
+ if not block:
+ return
+ rename_to = block.get(TAG_RENAME_TO)
+ if not rename_to:
+ return
+ rename_to = rename_to.value
+ target = self._namespace.get_by_symbol(rename_to)
+ if not target:
+ self._transformer.log_node_warning(node,
+"Can't find symbol %r referenced by Rename annotation" % (rename_to, ))
+ elif target.shadowed_by:
+ self._transformer.log_node_warning(node,
+"Function %r already shadowed by %r, can't overwrite with %r" % (target.symbol,
+ target.shadowed_by,
+ rename_to))
+ elif target.shadows:
+ self._transformer.log_node_warning(node,
+"Function %r already shadows %r, can't multiply shadow with %r" % (target.symbol,
+ target.shadows,
+ rename_to))
+ else:
+ target.shadows = node.symbol
+ node.shadowed_by = target.symbol
+
+ def _apply_annotations_function(self, node, chain):
+ block = self._blocks.get(node.symbol)
+ self._apply_annotations_callable(node, chain, block)
+ self._apply_annotation_rename_to(node, chain, block)
+
+ def _pass_callable_defaults(self, node, chain):
+ if isinstance(node, (ast.Callable, glibast.GLibSignal)):
+ for param in node.parameters:
+ if param.transfer is None:
+ param.transfer = self._get_transfer_default(node, param)
+ if node.retval.transfer is None:
+ node.retval.transfer = self._get_transfer_default(node, node.retval)
+ return True
+
+ def _pass_read_annotations(self, node, chain):
+ if not node.namespace:
+ return False
+ if isinstance(node, ast.Function):
+ self._apply_annotations_function(node, chain)
+ if isinstance(node, ast.Callback):
+ block = self._blocks.get(node.c_name)
+ self._apply_annotations_callable(node, chain, block)
+ if isinstance(node, (ast.Class, ast.Interface, ast.Record,
+ ast.Union, ast.Enum, ast.Bitfield,
+ ast.Callback)):
+ block = self._blocks.get(node.c_name)
+ self._apply_annotations_annotated(node, block)
+ if isinstance(node, (ast.Class, ast.Interface, ast.Record, ast.Union)):
+ for field in node.fields:
+ self._blocks.get('%s::%s' % (node.c_name, field.name))
+ self._apply_annotations_field(node, block, field)
+ if isinstance(node, (ast.Class, ast.Interface)):
+ for prop in node.properties:
+ self._apply_annotations_property(node, prop)
+ for sig in node.signals:
+ self._apply_annotations_signal(node, sig)
+ if isinstance(node, ast.Class):
+ block = self._blocks.get(node.c_name)
+ if block:
+ tag = block.get(TAG_UNREF_FUNC)
+ node.unref_func = tag.value if tag else None
+ tag = block.get(TAG_REF_FUNC)
+ node.ref_func = tag.value if tag else None
+ tag = block.get(TAG_SET_VALUE_FUNC)
+ node.set_value_func = tag.value if tag else None
+ tag = block.get(TAG_GET_VALUE_FUNC)
+ node.get_value_func = tag.value if tag else None
+ return True
+
+ def _adjust_container_type(self, parent, node, options):
+ has_element_type = OPT_ELEMENT_TYPE in options
+ has_array = OPT_ARRAY in options
+
+ if has_array:
+ self._apply_annotations_array(parent, node, options)
+ elif has_element_type:
+ self._apply_annotations_element_type(parent, node, options)
+
+ def _resolve(self, type_str, orig_node=None):
+ def grab_one(type_str, resolver, top_combiner, combiner):
+ """Return a complete type, and the trailing string part after it.
+ Use resolver() on each identifier, and combiner() on the parts of
+ each complete type. (top_combiner is used on the top-most type.)"""
+ bits = re.split(r'([,<>])', type_str, 1)
+ first, sep, rest = [bits[0], '', ''] if (len(bits)==1) else bits
+ args = [resolver(first)]
+ if sep == '<':
+ while sep != '>':
+ next, rest = grab_one(rest, resolver, combiner, combiner)
+ args.append(next)
+ sep, rest = rest[0], rest[1:]
+ else:
+ rest = sep + rest
+ return top_combiner(*args), rest
+ def resolver(ident):
+ res = self._transformer.create_type_from_user_string(ident)
+ return res
+ def combiner(base, *rest):
+ if not rest:
+ return base
+ if isinstance(base, ast.List) and len(rest) == 1:
+ return ast.List(base.name, *rest)
+ if isinstance(base, ast.Map) and len(rest) == 2:
+ return ast.Map(*rest)
+ self._transformer.log_warning(
+"Too many parameters in type specification %r" % (type_str, ))
+ return base
+ def top_combiner(base, *rest):
+ if orig_node is not None:
+ base.is_const = orig_node.is_const
+ return combiner(base, *rest)
+
+ result, rest = grab_one(type_str, resolver, top_combiner, combiner)
+ if rest:
+ self._transformer.log_warning(
+"Trailing components in type specification %r" % (type_str, ))
+ return result
+
+ def _apply_annotations_array(self, parent, node, options):
+ array_opt = options.get(OPT_ARRAY)
+ if array_opt:
+ array_values = array_opt.all()
+ else:
+ array_values = {}
+
+ element_type = options.get(OPT_ELEMENT_TYPE)
+ if element_type is not None:
+ element_type_node = self._resolve(element_type.one())
+ elif isinstance(node.type, ast.Array):
+ element_type_node = node.type.element_type
+ else:
+ # We're assuming here that Foo* with an (array) annotation
+ # and no (element-type) means array of Foo
+ element_type_node = node.type.clone()
+ # Explicitly erase ctype since it's no longer valid
+ element_type_node.ctype = None
+
+ if isinstance(node.type, ast.Array):
+ array_type = node.type.array_type
+ else:
+ array_type = None
+ container_type = ast.Array(array_type, element_type_node,
+ ctype=node.type.ctype,
+ is_const=node.type.is_const)
+ if OPT_ARRAY_ZERO_TERMINATED in array_values:
+ container_type.zeroterminated = array_values.get(
+ OPT_ARRAY_ZERO_TERMINATED) == '1'
+ length = array_values.get(OPT_ARRAY_LENGTH)
+ if length is not None:
+ paramname = self._get_validate_parameter_name(parent, length, node)
+ if paramname:
+ param = parent.get_parameter(paramname)
+ param.direction = node.direction
+ if param.direction == ast.PARAM_DIRECTION_OUT:
+ param.transfer = ast.PARAM_TRANSFER_FULL
+ container_type.length_param_name = param.argname
+ fixed = array_values.get(OPT_ARRAY_FIXED_SIZE)
+ if fixed:
+ container_type.size = int(fixed)
+ node.type = container_type
+
+ def _apply_annotations_element_type(self, parent, node, options):
+ element_type_opt = options.get(OPT_ELEMENT_TYPE)
+ element_type = element_type_opt.flat()
+ if isinstance(node.type, ast.List):
+ assert len(element_type) == 1
+ node.type.element_type = self._resolve(element_type[0])
+ elif isinstance(node.type, ast.Map):
+ assert len(element_type) == 2
+ node.type.key_type = self._resolve(element_type[0])
+ node.type.value_type = self._resolve(element_type[1])
+ elif isinstance(node.type, ast.Array):
+ node.type.element_type = self._resolve(element_type[0])
+ else:
+ self._transformer.log_node_warning(parent,
+ "Unknown container %r for element-type annotation" % (node.type, ))
+
+ def _get_transfer_default_param(self, parent, node):
+ if node.direction in [ast.PARAM_DIRECTION_INOUT,
+ ast.PARAM_DIRECTION_OUT]:
+ if node.caller_allocates:
+ return ast.PARAM_TRANSFER_NONE
+ return ast.PARAM_TRANSFER_FULL
+ return ast.PARAM_TRANSFER_NONE
+
+ def _get_transfer_default_returntype_basic(self, typeval):
+ if (typeval.is_equiv(ast.BASIC_GIR_TYPES)
+ or typeval.is_const
+ or typeval.is_equiv(ast.TYPE_NONE)):
+ return ast.PARAM_TRANSFER_NONE
+ elif typeval.is_equiv(ast.TYPE_STRING):
+ # Non-const strings default to FULL
+ return ast.PARAM_TRANSFER_FULL
+ elif typeval.target_fundamental:
+ # This looks like just GType right now
+ return None
+ return None
+
+ def _is_gi_subclass(self, typeval, supercls_type):
+ cls = self._transformer.lookup_typenode(typeval)
+ assert cls, str(typeval)
+ supercls = self._transformer.lookup_typenode(supercls_type)
+ assert supercls
+ if cls is supercls:
+ return True
+ if cls.parent:
+ return self._is_gi_subclass(cls.parent, supercls_type)
+ return False
+
+ def _get_transfer_default_return(self, parent, node):
+ typeval = node.type
+ basic = self._get_transfer_default_returntype_basic(typeval)
+ if basic:
+ return basic
+ if not typeval.target_giname:
+ return None
+ target = self._transformer.lookup_typenode(typeval)
+ if isinstance(target, ast.Alias):
+ return self._get_transfer_default_returntype_basic(target.target)
+ elif isinstance(target, glibast.GLibBoxed):
+ return ast.PARAM_TRANSFER_FULL
+ elif isinstance(target, (ast.Enum, ast.Bitfield)):
+ return ast.PARAM_TRANSFER_NONE
+ # Handle constructors specially here
+ elif isinstance(parent, ast.Function) and parent.is_constructor:
+ if isinstance(target, ast.Class):
+ initially_unowned_type = ast.Type(target_giname='GObject.InitiallyUnowned')
+ initially_unowned = self._transformer.lookup_typenode(initially_unowned_type)
+ if initially_unowned and self._is_gi_subclass(typeval, initially_unowned_type):
+ return ast.PARAM_TRANSFER_NONE
+ else:
+ return ast.PARAM_TRANSFER_FULL
+ elif isinstance(target, (ast.Record, ast.Union)):
+ return ast.PARAM_TRANSFER_FULL
+ else:
+ assert False, "Invalid constructor"
+ elif isinstance(target, (ast.Class, ast.Record, ast.Union)):
+ # Explicitly no default for these
+ return None
+ else:
+ return None
+
+ def _get_transfer_default(self, parent, node):
+ if node.type.is_equiv(ast.TYPE_NONE) or isinstance(node.type, ast.Varargs):
+ return ast.PARAM_TRANSFER_NONE
+ elif isinstance(node, ast.Parameter):
+ return self._get_transfer_default_param(parent, node)
+ elif isinstance(node, ast.Return):
+ return self._get_transfer_default_return(parent, node)
+ elif isinstance(node, ast.Field):
+ return ast.PARAM_TRANSFER_NONE
+ elif isinstance(node, ast.Property):
+ return ast.PARAM_TRANSFER_NONE
+ else:
+ raise AssertionError(node)
+
+ def _apply_annotations_param_ret_common(self, parent, node, tag):
+ options = getattr(tag, 'options', {})
+
+ param_type = options.get(OPT_TYPE)
+ if param_type:
+ node.type = self._resolve(param_type.one(), node.type)
+
+ caller_allocates = False
+ annotated_direction = None
+ if (OPT_INOUT in options or
+ OPT_INOUT_ALT in options):
+ annotated_direction = ast.PARAM_DIRECTION_INOUT
+ elif OPT_OUT in options:
+ subtype = options[OPT_OUT]
+ if subtype is not None:
+ subtype = subtype.one()
+ annotated_direction = ast.PARAM_DIRECTION_OUT
+ if subtype in (None, ''):
+ if node.type.target_giname and node.type.ctype:
+ caller_allocates = '**' not in node.type.ctype
+ else:
+ caller_allocates = False
+ elif subtype == 'caller-allocates':
+ caller_allocates = True
+ elif subtype == 'callee-allocates':
+ caller_allocates = False
+ else:
+ self._transformer.log_warning(
+"out allocation for %s is invalid (%r)" % (node, subtype), fatal=True)
+ elif OPT_IN in options:
+ annotated_direction = ast.PARAM_DIRECTION_IN
+
+ if (annotated_direction is not None) and (annotated_direction != node.direction):
+ node.direction = annotated_direction
+ node.caller_allocates = caller_allocates
+ # Also reset the transfer default if we're toggling direction
+ node.transfer = self._get_transfer_default(parent, node)
+
+ transfer_tag = options.get(TAG_TRANSFER)
+ if transfer_tag:
+ node.transfer = transfer_tag.one()
+
+ self._adjust_container_type(parent, node, options)
+
+ if (OPT_ALLOW_NONE in options or
+ node.type.target_giname == 'Gio.Cancellable'):
+ node.allow_none = True
+
+ if tag is not None and tag.comment is not None:
+ node.doc = tag.comment
+
+ for key in options:
+ if '.' in key:
+ value = options.get(key)
+ if value:
+ node.attributes.append((key, value.one()))
+
+ def _apply_annotations_annotated(self, node, block):
+ if block is None:
+ return
+
+ node.doc = block.comment
+
+ since_tag = block.get(TAG_SINCE)
+ if since_tag is not None:
+ node.version = since_tag.value
+
+ deprecated_tag = block.get(TAG_DEPRECATED)
+ if deprecated_tag is not None:
+ value = deprecated_tag.value
+ if ': ' in value:
+ version, desc = value.split(': ')
+ else:
+ desc = value
+ version = None
+ node.deprecated = desc
+ if version is not None:
+ node.deprecated_version = version
+
+ annos_tag = block.get(TAG_ATTRIBUTES)
+ if annos_tag is not None:
+ options = AnnotationParser.parse_options(annos_tag.value)
+ for key, value in options.iteritems():
+ if value:
+ node.attributes.append((key, value.one()))
+
+ if OPT_SKIP in block.options:
+ node.skip = True
+
+ if OPT_FOREIGN in block.options:
+ node.foreign = True
+
+ def _apply_annotations_param(self, parent, param, tag):
+ if tag:
+ options = tag.options
+ else:
+ options = {}
+ if isinstance(parent, ast.Function):
+ scope = options.get(OPT_SCOPE)
+ if scope:
+ scope = scope.one()
+ if scope not in [ast.PARAM_SCOPE_CALL,
+ ast.PARAM_SCOPE_ASYNC,
+ ast.PARAM_SCOPE_NOTIFIED]:
+ self._transformer.log_warning(parent,
+"Invalid scope %r for parameter %r" % (scope, param.name))
+ else:
+ param.scope = scope
+ param.transfer = ast.PARAM_TRANSFER_NONE
+
+ destroy = options.get(OPT_DESTROY)
+ if destroy:
+ param.destroy_name = self._get_validate_parameter_name(parent,
+ destroy.one(),
+ param)
+ if param.destroy_name is not None:
+ param.scope = ast.PARAM_SCOPE_NOTIFIED
+ destroy_param = parent.get_parameter(param.destroy_name)
+ # This is technically bogus; we're setting the scope on the destroy
+ # itself. But this helps avoid tripping a warning from finaltransformer,
+ # since we don't have a way right now to flag this callback a destroy.
+ destroy_param.scope = ast.PARAM_SCOPE_NOTIFIED
+ closure = options.get(OPT_CLOSURE)
+ if closure:
+ param.closure_name = self._get_validate_parameter_name(parent,
+ closure.one(),
+ param)
+ elif isinstance(parent, ast.Callback):
+ if OPT_CLOSURE in options:
+ # For callbacks, (closure) appears without an
+ # argument, and tags a parameter that is a closure. We
+ # represent it (weirdly) in the gir and typelib by
+ # setting param.closure_name to itself.
+ param.closure_name = param.argname
+
+ self._apply_annotations_param_ret_common(parent, param, tag)
+
+ def _apply_annotations_return(self, parent, return_, block):
+ if block:
+ tag = block.get(TAG_RETURNS)
+ else:
+ tag = None
+ self._apply_annotations_param_ret_common(parent, return_, tag)
+
+ def _apply_annotations_params(self, parent, params, block):
+ for param in params:
+ if block:
+ tag = block.get(param.argname)
+ else:
+ tag = None
+ self._apply_annotations_param(parent, param, tag)
+
+ def _apply_annotations_callable(self, node, chain, block):
+ self._apply_annotations_annotated(node, block)
+ self._apply_annotations_params(node, node.parameters, block)
+ self._apply_annotations_return(node, node.retval, block)
+
+ def _check_arg_annotations(self, parent, params, block):
+ if block is None:
+ return
+ for tag in block.tags.keys():
+ if tag == TAG_RETURNS:
+ continue
+ for param in params:
+ if param.argname == tag:
+ break
+ else:
+ self._transformer.log_warning(
+"Annotation for '%s' refers to unknown argument '%s'"
+% (parent.name, tag))
+
+ def _apply_annotations_field(self, parent, block, field):
+ if not block:
+ return
+ tag = block.get(field.name)
+ if not tag:
+ return
+ t = tag.options.get('type')
+ if not t:
+ return
+ field.type = self._transformer.create_type_from_user_string(t.one())
+
+ def _apply_annotations_property(self, parent, prop):
+ block = self._blocks.get('%s:%s' % (parent.type_name, prop.name))
+ self._apply_annotations_annotated(prop, block)
+ if not block:
+ return
+ transfer_tag = block.get(TAG_TRANSFER)
+ if transfer_tag is not None:
+ prop.transfer = transfer_tag.value
+ else:
+ prop.transfer = self._get_transfer_default(parent, prop)
+ type_tag = block.get(TAG_TYPE)
+ if type_tag:
+ prop.type = self._resolve(type_tag.value, prop.type)
+
+ def _apply_annotations_signal(self, parent, signal):
+ block = self._blocks.get('%s::%s' % (parent.type_name, signal.name))
+ self._apply_annotations_annotated(signal, block)
+ # We're only attempting to name the signal parameters if
+ # the number of parameter tags (@foo) is the same or greater
+ # than the number of signal parameters
+ if block and len(block.tags) > len(signal.parameters):
+ names = block.tags.items()
+ else:
+ names = []
+ for i, param in enumerate(signal.parameters):
+ if names:
+ name, tag = names[i+1]
+ param.name = name
+ options = getattr(tag, 'options', {})
+ param_type = options.get(OPT_TYPE)
+ if param_type:
+ param.type = self._resolve(param_type.one(), param.type)
+ else:
+ tag = None
+ self._apply_annotations_param(signal, param, tag)
+ self._apply_annotations_return(signal, signal.retval, block)
+
+ def _pass_read_annotations2(self, node, chain):
+ if isinstance(node, ast.Function):
+ self._apply_annotations2_function(node, chain)
+ return True
+
+ def _apply_annotations2_function(self, node, chain):
+ # Handle virtual invokers
+ parent = chain[-1] if chain else None
+ block = self._blocks.get(node.symbol)
+ if not (block and parent):
+ return
+ virtual = block.get(TAG_VFUNC)
+ if not virtual:
+ return
+ invoker_name = virtual.value
+ matched = False
+ for vfunc in parent.virtual_methods:
+ if vfunc.name == invoker_name:
+ matched = True
+ vfunc.invoker = node.name
+ # Also merge in annotations
+ self._apply_annotations_callable(vfunc, [parent], block)
+ break
+ if not matched:
+ self._transformer.log_symbol_warning(node.symbol,
+ "Virtual slot %r not found for %r annotation" % (invoker_name, TAG_VFUNC))
+
+ def _pass_type_resolution(self, node, chain):
+ if isinstance(node, ast.Alias):
+ self._transformer.resolve_type(node.target)
+ if isinstance(node, ast.Callable):
+ for parameter in node.parameters:
+ self._transformer.resolve_type(parameter.type)
+ self._transformer.resolve_type(node.retval.type)
+ if isinstance(node, ast.Constant):
+ self._transformer.resolve_type(node.value_type)
+ if isinstance(node, (ast.Class, ast.Interface, ast.Record, ast.Union)):
+ for field in node.fields:
+ if field.anonymous_node:
+ pass
+ else:
+ self._transformer.resolve_type(field.type)
+ if isinstance(node, (ast.Class, ast.Interface)):
+ resolved_parent = None
+ for parent in node.parent_chain:
+ try:
+ self._transformer.resolve_type(parent)
+ except ValueError, e:
+ continue
+ target = self._transformer.lookup_typenode(parent)
+ if target:
+ node.parent = parent
+ break
+ for prop in node.properties:
+ self._transformer.resolve_type(prop.type)
+ for sig in node.signals:
+ for param in sig.parameters:
+ self._transformer.resolve_type(param.type)
+ if isinstance(node, ast.Class):
+ for iface in node.interfaces:
+ self._transformer.resolve_type(iface)
+ if isinstance(node, ast.Interface):
+ for iface in node.prerequisites:
+ self._transformer.resolve_type(iface)
+ return True
+
+ def _resolve_quarks(self):
+ # self._uscore_type_names is an authoritative mapping of types
+ # to underscored versions, since it is based on get_type() methods;
+ # but only covers enums that are registered as GObject enums.
+ # Create a fallback mapping based on all known enums in this module.
+ uscore_enums = {}
+ for enum in self._namespace.itervalues():
+ if not isinstance(enum, ast.Enum):
+ continue
+ type_name = enum.symbol
+ uscored = to_underscores(type_name).lower()
+
+ uscore_enums[uscored] = enum
+
+ no_uscore_prefixed = self._transformer.strip_identifier_or_warn(type_name)
+ if no_uscore_prefixed not in uscore_enums:
+ uscore_enums[no_uscore_prefixed] = enum
+
+ for node in self._namespace.itervalues():
+ if not isinstance(node, ast.Function):
+ continue
+ if node.retval.type.target_giname != 'GLib.Quark':
+ continue
+ short = node.symbol[:-len('_quark')]
+ if short == "g_io_error":
+ # Special case; GIOError was already taken forcing GIOErrorEnum
+ assert self._namespace.name == 'Gio'
+ enum = self._namespace.get('IOErrorEnum')
+ else:
+ enum = self._uscore_type_names.get(short)
+ if enum is None:
+ enum = uscore_enums.get(short)
+ if enum is not None:
+ enum.error_quark = node.symbol
+ else:
+ self._transformer.log_node_warning(node,
+"""%s: Couldn't find corresponding enumeration""" % (node.symbol, ))
+
+ def _split_uscored_by_type(self, uscored):
+ """'uscored' should be an un-prefixed uscore string. This
+function searches through the namespace for the longest type which
+prefixes uscored, and returns (type, suffix). Example, assuming
+namespace Gtk, type is TextBuffer:
+
+_split_uscored_by_type(text_buffer_try_new) -> (ast.Class(TextBuffer), 'try_new')"""
+ node = None
+ count = 0
+ prev_split_count = -1
+ while True:
+ components = uscored.rsplit('_', count)
+ if len(components) == prev_split_count:
+ return None
+ prev_split_count = len(components)
+ type_string = components[0]
+ node = self._uscore_type_names.get(type_string)
+ if node:
+ return (node, '_'.join(components[1:]))
+ count += 1
+
+ def _pair_function(self, func):
+ """Check to see whether a toplevel function should be a
+method or constructor of some type."""
+ if func.symbol.endswith('_get_type') or func.symbol.startswith('_'):
+ return
+ (ns, subsymbol) = self._transformer.split_csymbol(func.symbol)
+ assert ns == self._namespace
+ if self._pair_constructor(func, subsymbol):
+ return
+ elif self._pair_method(func, subsymbol):
+ return
+ elif self._pair_static_method(func, subsymbol):
+ return
+
+ def _uscored_identifier_for_type(self, typeval):
+ """Given a Type(target_giname='Foo.BarBaz'), return 'bar_baz'."""
+ name = typeval.get_giname()
+ return to_underscores_noprefix(name).lower()
+
+ def _pair_method(self, func, subsymbol):
+ if not func.parameters:
+ return False
+ first = func.parameters[0]
+ target = self._transformer.lookup_typenode(first.type)
+ if not isinstance(target, (ast.Class, ast.Interface,
+ ast.Record, ast.Union,
+ glibast.GLibBoxedOther)):
+ return False
+
+ # A quick hack here...in the future we should catch C signature/GI signature
+ # mismatches in a general way in finaltransformer
+ if first.type.ctype.count('*') != 1:
+ return False
+
+ uscored = self._uscored_identifier_for_type(first.type)
+ if not subsymbol.startswith(uscored):
+ return False
+ del func.parameters[0]
+ subsym_idx = func.symbol.find(subsymbol)
+ self._namespace.float(func)
+ func.name = func.symbol[(subsym_idx + len(uscored) + 1):]
+ target.methods.append(func)
+ func.is_method = True
+ return True
+
+ def _pair_static_method(self, func, subsymbol):
+ split = self._split_uscored_by_type(subsymbol)
+ if split is None:
+ return False
+ (node, funcname) = split
+ if not isinstance(node, (ast.Class, ast.Interface,
+ ast.Record, ast.Union, glibast.GLibBoxedOther)):
+ return False
+ self._namespace.float(func)
+ func.name = funcname
+ node.static_methods.append(func)
+
+ def _pair_constructor(self, func, subsymbol):
+ if not (func.symbol.find('_new_') >= 0 or func.symbol.endswith('_new')):
+ return False
+ target = self._transformer.lookup_typenode(func.retval.type)
+ if not isinstance(target, (ast.Class, ast.Record, ast.Union, glibast.GLibBoxedOther)):
+ return False
+ new_idx = func.symbol.rfind('_new')
+ assert (new_idx >= 0)
+ prefix = func.symbol[:new_idx]
+ split = self._split_uscored_by_type(subsymbol)
+ if split is None:
+ # TODO - need a e.g. (method) annotation
+ self._transformer.log_node_warning(func,
+ "Can't find matching type for constructor; symbol=%r" % (func.symbol, ))
+ return False
+ (origin_node, funcname) = split
+ if isinstance(target, ast.Class):
+ parent = origin_node
+ while parent:
+ if parent == target:
+ break
+ if parent.parent:
+ parent = self._transformer.lookup_typenode(parent.parent)
+ else:
+ parent = None
+ if parent is None:
+ self._transformer.log_node_warning(func,
+"Return value is not superclass for constructor; symbol=%r constructed=%r return=%r"
+% (func.symbol, str(origin_node.create_type()), str(func.retval.type)))
+ return False
+ else:
+ if origin_node != target:
+ self._transformer.log_node_warning(func,
+"Constructor return type mismatch symbol=%r constructed=%r return=%r"
+% (func.symbol, str(origin_node.create_type()), str(func.retval.type)))
+ return False
+ self._namespace.float(func)
+ func.name = funcname
+ func.is_constructor = True
+ target.constructors.append(func)
+ # Constructors have default return semantics
+ if not func.retval.transfer:
+ func.retval.transfer = self._get_transfer_default_return(func, func.retval)
+ return True
+
+ def _pair_class_virtuals(self, node):
+ """Look for virtual methods from the class structure."""
+ if not node.glib_type_struct:
+ self._transformer.log_node_warning(node,
+ "Failed to find class structure for %r" % (node.name, ))
+ return
+
+ node_type = node.create_type()
+ class_struct = self._transformer.lookup_typenode(node.glib_type_struct)
+
+ # Object class fields are assumed to be read-only
+ # (see also _introspect_object and transformer.py)
+ for field in class_struct.fields:
+ if isinstance(field, ast.Field):
+ field.writable = False
+
+ # Loop through fields to determine which are virtual
+ # functions and which are signal slots by
+ # assuming everything that doesn't share a name
+ # with a known signal is a virtual slot.
+ for field in class_struct.fields:
+ if not isinstance(field.anonymous_node, ast.Callback):
+ continue
+ callback = field.anonymous_node
+ # Check the first parameter is the object
+ if len(callback.parameters) == 0:
+ continue
+ firstparam_type = callback.parameters[0].type
+ if firstparam_type != node_type:
+ continue
+ # Also double check we don't have a signal with this
+ # name.
+ matched_signal = False
+ for signal in node.signals:
+ if signal.name.replace('-', '_') == callback.name:
+ matched_signal = True
+ break
+ if matched_signal:
+ continue
+ vfunc = ast.VFunction.from_callback(callback)
+ vfunc.inherit_file_positions(callback)
+ node.virtual_methods.append(vfunc)
+
+ # Take the set of virtual methods we found, and try
+ # to pair up with any matching methods using the
+ # name+signature.
+ for vfunc in node.virtual_methods:
+ for method in node.methods:
+ if method.name != vfunc.name:
+ continue
+ if method.retval.type != vfunc.retval.type:
+ continue
+ if len(method.parameters) != len(vfunc.parameters):
+ continue
+ for i in xrange(len(method.parameters)):
+ m_type = method.parameters[i].type
+ v_type = vfunc.parameters[i].type
+ if m_type != v_type:
+ continue
+ vfunc.invoker = method.name
+ # Apply any annotations we have from the invoker to
+ # the vfunc
+ block = self._blocks.get(method.symbol)
+ self._apply_annotations_callable(vfunc, [], block)
+
+ def _pass3(self, node, chain):
+ """Pass 3 is after we've loaded GType data and performed type
+ closure."""
+ if isinstance(node, ast.Callable):
+ self._pass3_callable_callbacks(node)
+ self._pass3_callable_throws(node)
+ return True
+
+ def _pass3_callable_callbacks(self, node):
+ """Check to see if we have anything that looks like a
+ callback+user_data+GDestroyNotify set."""
+
+ params = node.parameters
+
+ # First, do defaults for well-known callback types
+ for i, param in enumerate(params):
+ argnode = self._transformer.lookup_typenode(param.type)
+ if isinstance(argnode, ast.Callback):
+ if param.type.target_giname in ('Gio.AsyncReadyCallback',
+ 'GLib.DestroyNotify'):
+ param.scope = ast.PARAM_SCOPE_ASYNC
+ param.transfer = ast.PARAM_TRANSFER_NONE
+
+ callback_param = None
+ for i, param in enumerate(params):
+ argnode = self._transformer.lookup_typenode(param.type)
+ is_destroynotify = False
+ if isinstance(argnode, ast.Callback):
+ if param.type.target_giname == 'GLib.DestroyNotify':
+ is_destroynotify = True
+ else:
+ callback_param = param
+ continue
+ if callback_param is None:
+ continue
+ if is_destroynotify:
+ callback_param.destroy_name = param.argname
+ callback_param.scope = ast.PARAM_SCOPE_NOTIFIED
+ callback_param.transfer = ast.PARAM_TRANSFER_NONE
+ elif (param.type.is_equiv(ast.TYPE_ANY) and
+ param.argname.endswith('data')):
+ callback_param.closure_name = param.argname
+
+ def _pass3_callable_throws(self, node):
+ """Check to see if we have anything that looks like a
+ callback+user_data+GDestroyNotify set."""
+ if not node.parameters:
+ return
+ last_param = node.parameters[-1]
+ # Checking type.name=='GLib.Error' generates false positives
+ # on methods that take a 'GError *'
+ if last_param.type.ctype == 'GError**':
+ node.parameters.pop()
+ node.throws = True