Commit fdbe3cc3 authored by Colin Walters's avatar Colin Walters

Bug 557383 - Virtual method support

Broadly speaking, this change adds the concept of <vfunc> to the .gir.
The typelib already had most of the infrastructure for virtual functions,
though there is one API addition.

The scanner assumes that any class callback slot that doesn't match
a signal name is a virtual.  In the .gir, we write out *both* the <method>
wrapper and a <vfunc>.  If we can determine an association between
them (based on the names matching, or a new Virtual: annotation),
then we notate that in the .gir.

The typelib gains an association from the vfunc to the function, if
it exists.  This will be useful for bindings since they already know
how to consume FunctionInfo.
parent b8e31724
......@@ -1527,6 +1527,63 @@ g_object_info_get_vfunc (GIObjectInfo *info,
base->typelib, offset);
}
static GIVFuncInfo *
find_vfunc (GIBaseInfo *base,
guint32 offset,
gint n_vfuncs,
const gchar *name)
{
/* FIXME hash */
Header *header = (Header *)base->typelib->data;
gint i;
for (i = 0; i < n_vfuncs; i++)
{
VFuncBlob *fblob = (VFuncBlob *)&base->typelib->data[offset];
const gchar *fname = (const gchar *)&base->typelib->data[fblob->name];
if (strcmp (name, fname) == 0)
return (GIVFuncInfo *) g_info_new (GI_INFO_TYPE_VFUNC, base,
base->typelib, offset);
offset += header->vfunc_blob_size;
}
return NULL;
}
/**
* g_object_info_find_vfunc:
* @info: An #GIObjectInfo
* @name: The name of a virtual function to find.
*
* Locate a virtual function slot with name @name. Note that the namespace
* for virtuals is distinct from that of methods; there may or may not be
* a concrete method associated for a virtual. If there is one, it may
* be retrieved using #g_vfunc_info_get_invoker. See the documentation for
* that function for more information on invoking virtuals.
*
* Return value: (transfer full): A #GIVFuncInfo, or %NULL if none with name @name.
*/
GIVFuncInfo *
g_object_info_find_vfunc (GIObjectInfo *info,
const gchar *name)
{
gint offset;
GIBaseInfo *base = (GIBaseInfo *)info;
Header *header = (Header *)base->typelib->data;
ObjectBlob *blob = (ObjectBlob *)&base->typelib->data[base->offset];
offset = base->offset + header->object_blob_size
+ (blob->n_interfaces + blob->n_interfaces % 2) * 2
+ blob->n_fields * header->field_blob_size
+ blob->n_properties * header->property_blob_size
+ blob->n_methods * header->function_blob_size
+ blob->n_signals * header->signal_blob_size;
return find_vfunc (base, offset, blob->n_vfuncs, name);
}
gint
g_object_info_get_n_constants (GIObjectInfo *info)
{
......@@ -1728,6 +1785,34 @@ g_interface_info_get_vfunc (GIInterfaceInfo *info,
base->typelib, offset);
}
/**
* g_interface_info_find_vfunc:
* @info: An #GIObjectInfo
* @name: The name of a virtual function to find.
*
* Locate a virtual function slot with name @name. See the documentation
* for #g_object_info_find_vfunc for more information on virtuals.
*
* Return value: (transfer full): A #GIVFuncInfo, or %NULL if none with name @name.
*/
GIVFuncInfo *
g_interface_info_find_vfunc (GIInterfaceInfo *info,
const gchar *name)
{
gint offset;
GIBaseInfo *base = (GIBaseInfo *)info;
Header *header = (Header *)base->typelib->data;
InterfaceBlob *blob = (InterfaceBlob *)&base->typelib->data[base->offset];
offset = base->offset + header->interface_blob_size
+ (blob->n_prerequisites + blob->n_prerequisites % 2) * 2
+ blob->n_properties * header->property_blob_size
+ blob->n_methods * header->function_blob_size
+ blob->n_signals * header->signal_blob_size;
return find_vfunc (base, offset, blob->n_vfuncs, name);
}
gint
g_interface_info_get_n_constants (GIInterfaceInfo *info)
{
......@@ -1913,6 +1998,37 @@ g_vfunc_info_get_signal (GIVFuncInfo *info)
return NULL;
}
/**
* g_vfunc_info_get_invoker:
* @info: A #GIVFuncInfo
*
* If this virtual function has an associated invoker method, this
* method will return it. An invoker method is a C entry point.
*
* Not all virtuals will have invokers.
*
* Return value: (transfer full): An invoker function, or %NULL if none known
*/
GIFunctionInfo *
g_vfunc_info_get_invoker (GIVFuncInfo *info)
{
GIBaseInfo *base = (GIBaseInfo *)info;
VFuncBlob *blob = (VFuncBlob *)&base->typelib->data[base->offset];
GIBaseInfo *container = base->container;
GIInfoType parent_type;
/* 1023 = 0x3ff is the maximum of the 10 bits for invoker index */
if (blob->invoker == 1023)
return NULL;
parent_type = g_base_info_get_type (container);
if (parent_type == GI_INFO_TYPE_OBJECT)
return g_object_info_get_method ((GIObjectInfo*)container, blob->invoker);
else if (parent_type == GI_INFO_TYPE_INTERFACE)
return g_interface_info_get_method ((GIInterfaceInfo*)container, blob->invoker);
else
g_assert_not_reached ();
}
/* GIConstantInfo functions */
GITypeInfo *
......
......@@ -470,6 +470,8 @@ GISignalInfo * g_object_info_get_signal (GIObjectInfo *in
gint g_object_info_get_n_vfuncs (GIObjectInfo *info);
GIVFuncInfo * g_object_info_get_vfunc (GIObjectInfo *info,
gint n);
GIVFuncInfo * g_object_info_find_vfunc (GIObjectInfo *info,
const gchar *name);
gint g_object_info_get_n_constants (GIObjectInfo *info);
GIConstantInfo * g_object_info_get_constant (GIObjectInfo *info,
gint n);
......@@ -495,6 +497,8 @@ GISignalInfo * g_interface_info_get_signal (GIInterfaceInfo *in
gint g_interface_info_get_n_vfuncs (GIInterfaceInfo *info);
GIVFuncInfo * g_interface_info_get_vfunc (GIInterfaceInfo *info,
gint n);
GIVFuncInfo * g_interface_info_find_vfunc (GIInterfaceInfo *info,
const gchar *name);
gint g_interface_info_get_n_constants (GIInterfaceInfo *info);
GIConstantInfo * g_interface_info_get_constant (GIInterfaceInfo *info,
gint n);
......@@ -527,6 +531,7 @@ typedef enum
GIVFuncInfoFlags g_vfunc_info_get_flags (GIVFuncInfo *info);
gint g_vfunc_info_get_offset (GIVFuncInfo *info);
GISignalInfo * g_vfunc_info_get_signal (GIVFuncInfo *info);
GIFunctionInfo * g_vfunc_info_get_invoker (GIVFuncInfo *info);
/* GIConstantInfo */
......
......@@ -265,6 +265,7 @@ g_ir_node_free (GIrNode *node)
GIrNodeVFunc *vfunc = (GIrNodeVFunc *)node;
g_free (node->name);
g_free (vfunc->invoker);
for (l = vfunc->parameters; l; l = l->next)
g_ir_node_free ((GIrNode *)l->data);
g_list_free (vfunc->parameters);
......@@ -1186,6 +1187,30 @@ g_ir_find_node (GIrModule *module,
return node != NULL;
}
static int
get_index_of_member_type (GIrNodeInterface *node,
GIrNodeTypeId type,
const char *name)
{
guint index = -1;
GList *l;
for (l = node->members; l; l = l->next)
{
GIrNode *node = l->data;
if (node->type != type)
continue;
index++;
if (strcmp (node->name, name) == 0)
break;
}
return index;
}
static void
serialize_type (GIrModule *module,
GList *modules,
......@@ -1759,6 +1784,18 @@ g_ir_node_build_typelib (GIrNode *node,
blob->class_closure = 0; /* FIXME */
blob->reserved = 0;
if (vfunc->invoker)
{
int index = get_index_of_member_type ((GIrNodeInterface*)parent, G_IR_NODE_FUNCTION, vfunc->invoker);
if (index == -1)
{
g_error ("Unknown member function %s for vfunc %s", vfunc->invoker, node->name);
}
blob->invoker = (guint) index;
}
else
blob->invoker = 0x3ff; /* max of 10 bits */
blob->struct_offset = vfunc->offset;
blob->reserved2 = 0;
blob->signature = signature;
......
......@@ -214,6 +214,8 @@ struct _GIrNodeVFunc
gboolean must_not_be_implemented;
gboolean is_class_closure;
char *invoker;
GList *parameters;
GIrNodeParam *result;
......
......@@ -2080,7 +2080,7 @@ start_vfunc (GMarkupParseContext *context,
ParseContext *ctx,
GError **error)
{
if (strcmp (element_name, "vfunc") == 0 &&
if (strcmp (element_name, "virtual-method") == 0 &&
(ctx->state == STATE_CLASS ||
ctx->state == STATE_INTERFACE))
{
......@@ -2089,12 +2089,14 @@ start_vfunc (GMarkupParseContext *context,
const gchar *override;
const gchar *is_class_closure;
const gchar *offset;
const gchar *invoker;
name = find_attribute ("name", attribute_names, attribute_values);
must_chain_up = find_attribute ("must-chain-up", attribute_names, attribute_values);
override = find_attribute ("override", attribute_names, attribute_values);
is_class_closure = find_attribute ("is-class-closure", attribute_names, attribute_values);
offset = find_attribute ("offset", attribute_names, attribute_values);
invoker = find_attribute ("invoker", attribute_names, attribute_values);
if (name == NULL)
MISSING_ATTRIBUTE (context, error, element_name, "name");
......@@ -2138,6 +2140,8 @@ start_vfunc (GMarkupParseContext *context,
else
vfunc->offset = 0;
vfunc->invoker = g_strdup (invoker);
iface = (GIrNodeInterface *)CURRENT_NODE (ctx);
iface->members = g_list_append (iface->members, vfunc);
......
......@@ -843,9 +843,10 @@ typedef struct {
* @class_closure: Set if this virtual function is the class closure of a signal.
* @signal: The index of the signal in the list of signals of the object or
* interface to which this virtual function belongs.
* @struct_offset:
* The offset of the function pointer in the class struct. The value
* @struct_offset: The offset of the function pointer in the class struct. The value
* 0xFFFF indicates that the struct offset is unknown.
* @invoker: If a method invoker for this virtual exists, this is the offset in the
* class structure of the method. If no method is known, this value will be 0x3ff.
* @signature:
* Offset of the SignatureBlob describing the parameter types and the
* return value type.
......@@ -861,7 +862,8 @@ typedef struct {
guint16 signal;
guint16 struct_offset;
guint16 reserved2;
guint16 invoker : 10; /* Number of bits matches @index in FunctionBlob */
guint16 reserved2 : 6;
guint32 reserved3;
guint32 signature;
......
......@@ -41,6 +41,7 @@ from .glibast import GLibBoxed
_COMMENT_HEADER = '*\n '
# Tags - annotations applyed to comment blocks
TAG_VFUNC = 'virtual'
TAG_SINCE = 'since'
TAG_DEPRECATED = 'deprecated'
TAG_RETURNS = 'returns'
......@@ -298,8 +299,9 @@ class AnnotationApplier(object):
block = self._blocks.get(class_.type_name)
self._parse_node_common(class_, block)
self._parse_constructors(class_.constructors)
self._parse_methods(class_.methods)
self._parse_methods(class_.static_methods)
self._parse_methods(class_, class_.methods)
self._parse_vfuncs(class_, class_.virtual_methods)
self._parse_methods(class_, class_.static_methods)
self._parse_properties(class_, class_.properties)
self._parse_signals(class_, class_.signals)
self._parse_fields(class_, class_.fields)
......@@ -309,7 +311,8 @@ class AnnotationApplier(object):
def _parse_interface(self, interface):
block = self._blocks.get(interface.type_name)
self._parse_node_common(interface, block)
self._parse_methods(interface.methods)
self._parse_methods(interface, interface.methods)
self._parse_vfuncs(interface, interface.virtual_methods)
self._parse_properties(interface, interface.properties)
self._parse_signals(interface, interface.signals)
self._parse_fields(interface, interface.fields)
......@@ -320,7 +323,7 @@ class AnnotationApplier(object):
block = self._blocks.get(record.symbol)
self._parse_node_common(record, block)
self._parse_constructors(record.constructors)
self._parse_methods(record.methods)
self._parse_methods(record, record.methods)
self._parse_fields(record, record.fields)
if block:
record.doc = block.comment
......@@ -329,7 +332,7 @@ class AnnotationApplier(object):
block = self._blocks.get(boxed.name)
self._parse_node_common(boxed, block)
self._parse_constructors(boxed.constructors)
self._parse_methods(boxed.methods)
self._parse_methods(boxed, boxed.methods)
if block:
boxed.doc = block.comment
......@@ -338,7 +341,7 @@ class AnnotationApplier(object):
self._parse_node_common(union, block)
self._parse_fields(union, union.fields)
self._parse_constructors(union.constructors)
self._parse_methods(union.methods)
self._parse_methods(union, union.methods)
if block:
union.doc = block.comment
......@@ -370,9 +373,13 @@ class AnnotationApplier(object):
for prop in properties:
self._parse_property(parent, prop)
def _parse_methods(self, methods):
def _parse_methods(self, parent, methods):
for method in methods:
self._parse_function(method)
self._parse_method(parent, method)
def _parse_vfuncs(self, parent, vfuncs):
for vfunc in vfuncs:
self._parse_vfunc(parent, vfunc)
def _parse_signals(self, parent, signals):
for signal in signals:
......@@ -392,18 +399,20 @@ class AnnotationApplier(object):
if block:
callback.doc = block.comment
def _parse_callable(self, callable, block):
self._parse_node_common(callable, block)
self._parse_params(callable, callable.parameters, block)
self._parse_return(callable, callable.retval, block)
if block:
callable.doc = block.comment
def _parse_function(self, func):
block = self._blocks.get(func.symbol)
self._parse_node_common(func, block)
self._parse_params(func, func.parameters, block)
self._parse_return(func, func.retval, block)
if block:
func.doc = block.comment
self._parse_callable(func, block)
def _parse_signal(self, parent, signal):
block = self._blocks.get('%s::%s' % (parent.type_name, signal.name))
self._parse_node_common(signal, block)
self._parse_deprecated(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
......@@ -426,6 +435,26 @@ class AnnotationApplier(object):
if block:
signal.doc = block.comment
def _parse_method(self, parent, meth):
block = self._blocks.get(meth.symbol)
self._parse_function(meth)
virtual = self._get_tag(block, TAG_VFUNC)
if virtual:
invoker_name = virtual.value
matched = False
for vfunc in parent.virtual_methods:
if vfunc.name == invoker_name:
matched = True
vfunc.invoker = meth.name
break
if not matched:
print "warning: unmatched virtual invoker %r for method %r" % \
(invoker_name, meth.symbol)
def _parse_vfunc(self, parent, vfunc):
key = '%s::%s' % (parent.type_name, vfunc.name)
self._parse_callable(vfunc, self._blocks.get(key))
def _parse_field(self, parent, field):
if isinstance(field, Callback):
self._parse_callback(field)
......
......@@ -196,15 +196,25 @@ class Include(Node):
def __str__(self):
return '%s-%s' % (self.name, self.version)
class Callable(Node):
class Function(Node):
def __init__(self, name, retval, parameters, symbol, throws=None):
def __init__(self, name, retval, parameters, throws):
Node.__init__(self, name)
self.retval = retval
self.parameters = parameters
self.symbol = symbol
self.throws = not not throws
self.doc = None
def __repr__(self):
return '%s(%r, %r, %r)' % (self.__class__.__name__,
self.name, self.retval,
self.parameters)
class Function(Callable):
def __init__(self, name, retval, parameters, symbol, throws=None):
Callable.__init__(self, name, retval, parameters, throws)
self.symbol = symbol
self.is_method = False
self.doc = None
......@@ -218,14 +228,18 @@ class Function(Node):
if parameter.name == name:
return parameter
def __repr__(self):
return '%s(%r, %r, %r)' % (self.__class__.__name__,
self.name, self.retval,
self.parameters)
class VFunction(Callable):
class VFunction(Function):
pass
def __init__(self, name, retval, parameters, throws):
Callable.__init__(self, name, retval, parameters, throws)
self.invoker = None
@classmethod
def from_callback(cls, cb):
obj = cls(cb.name, cb.retval, cb.parameters[1:],
cb.throws)
return obj
class Type(Node):
......@@ -411,6 +425,7 @@ class Class(Node):
self.glib_type_struct = None
self.is_abstract = is_abstract
self.methods = []
self.virtual_methods = []
self.static_methods = []
self.interfaces = []
self.constructors = []
......@@ -430,6 +445,7 @@ class Interface(Node):
Node.__init__(self, name)
self.parent = parent
self.methods = []
self.virtual_methods = []
self.glib_type_struct = None
self.properties = []
self.fields = []
......
......@@ -158,18 +158,22 @@ and/or use gtk-doc annotations. ''')
attrs.append(('c:type', alias.ctype))
self.write_tag('alias', attrs)
def _write_function(self, func, tag_name='function'):
attrs = [('name', func.name),
('c:identifier', func.symbol)]
if func.doc:
attrs.append(('doc', func.doc))
self._append_version(func, attrs)
self._append_deprecated(func, attrs)
self._append_throws(func, attrs)
def _write_callable(self, callable, tag_name, extra_attrs):
attrs = [('name', callable.name)]
attrs.extend(extra_attrs)
if callable.doc:
attrs.append(('doc', callable.doc))
self._append_version(callable, attrs)
self._append_deprecated(callable, attrs)
self._append_throws(callable, attrs)
with self.tagcontext(tag_name, attrs):
self._write_attributes(func)
self._write_return_type(func.retval)
self._write_parameters(func.parameters)
self._write_attributes(callable)
self._write_return_type(callable.retval)
self._write_parameters(callable.parameters)
def _write_function(self, func, tag_name='function'):
attrs = [('c:identifier', func.symbol)]
self._write_callable(func, tag_name, attrs)
def _write_method(self, method):
self._write_function(method, tag_name='method')
......@@ -354,6 +358,8 @@ and/or use gtk-doc annotations. ''')
self._write_constructor(method)
for method in node.static_methods:
self._write_static_method(method)
for vfunc in node.virtual_methods:
self._write_vfunc(vfunc)
for method in node.methods:
self._write_method(method)
for prop in node.properties:
......@@ -395,18 +401,15 @@ and/or use gtk-doc annotations. ''')
self._write_attributes(prop)
self._write_type(prop.type)
def _write_vfunc(self, vf):
attrs = []
if vf.invoker:
attrs.append(('invoker', vf.invoker))
self._write_callable(vf, 'virtual-method', attrs)
def _write_callback(self, callback):
# FIXME: reuse _write_function
attrs = [('name', callback.name), ('c:type', callback.ctype)]
if callback.doc:
attrs.append(('doc', callback.doc))
self._append_version(callback, attrs)
self._append_deprecated(callback, attrs)
self._append_throws(callback, attrs)
with self.tagcontext('callback', attrs):
self._write_attributes(callback)
self._write_return_type(callback.retval)
self._write_parameters(callback.parameters)
attrs = [('c:type', callback.ctype)]
self._write_callable(callback, 'callback', attrs)
def _boxed_attrs(self, boxed):
return [('glib:type-name', boxed.type_name),
......
......@@ -27,12 +27,13 @@ import subprocess
from .ast import (Alias, Bitfield, Callback, Constant, Enum, Function, Member,
Namespace, Parameter, Property, Record, Return, Type, Union,
Field, type_name_from_ctype,
Field, VFunction, type_name_from_ctype,
default_array_types, TYPE_UINT8, PARAM_TRANSFER_FULL)
from .transformer import Names
from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember, GLibFlags,
GLibInterface, GLibObject, GLibSignal, GLibBoxedStruct,
GLibBoxedUnion, GLibBoxedOther, GLibRecord, type_names)
GLibBoxedUnion, GLibBoxedOther, GLibRecord,
type_names)
from .utils import to_underscores, to_underscores_noprefix
default_array_types['guchar*'] = TYPE_UINT8
......@@ -159,6 +160,10 @@ class GLibTransformer(object):
except KeyError, e:
print "WARNING: DELETING node %s: %s" % (node.name, e)
self._remove_attribute(node.name)
# Another pass, since we need to have the methods parsed
# in order to correctly modify them after class/record
# pairing
for (ns, node) in nodes:
# associate GtkButtonClass with GtkButton
if isinstance(node, Record):
self._pair_class_record(node)
......@@ -167,7 +172,9 @@ class GLibTransformer(object):
self._resolve_quarks()
# Fourth pass: ensure all types are known
if not self._noclosure:
self._validate(nodes)
self._resolve_types(nodes)
self._validate(nodes)
# Create a new namespace with what we found
namespace = Namespace(self._namespace_name, self._namespace_version)
......@@ -573,10 +580,43 @@ class GLibTransformer(object):
for field in maybe_class.fields:
if isinstance(field, Field):
field.writable = False
# TODO: remove this, we should be computing vfuncs instead
if isinstance(pair_class, GLibInterface):
for field in maybe_class.fields[1:]:
pair_class.fields.append(field)
# 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 maybe_class.fields:
if not isinstance(field, Callback):
continue
# Check the first parameter is the object
if len(field.parameters) == 0:
continue
firstparam_type = field.parameters[0].type
if firstparam_type != pair_class:
continue
# Also double check we don't have a signal with this
# name.
matched_signal = False
for signal in pair_class.signals:
if signal.name.replace('-', '_') == field.name:
matched_signal = True
break
if matched_signal:
continue
vfunc = VFunction.from_callback(field)
pair_class.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 pair_class.virtual_methods:
for method in pair_class.methods:
if (method.name != vfunc.name or
method.retval != vfunc.retval or
method.parameters != vfunc.parameters):
continue
vfunc.invoker = method.name