7843411682035346f9f340ae03c1f40178648cce
[platform/upstream/gobject-introspection.git] / giscanner / introspectablepass.py
1 # -*- Mode: Python -*-
2 # Copyright (C) 2010 Red Hat, Inc.
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the
16 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 # Boston, MA 02111-1307, USA.
18 #
19
20 from . import ast
21 from . import glibast
22
23 class IntrospectablePass(object):
24
25     def __init__(self, transformer):
26         self._transformer = transformer
27         self._namespace = transformer.namespace
28
29     # Public API
30
31     def validate(self):
32         self._namespace.walk(self._analyze_node)
33         self._namespace.walk(self._introspectable_callable_analysis)
34         self._namespace.walk(self._introspectable_callable_analysis)
35         self._namespace.walk(self._introspectable_pass3)
36
37     def _interface_vfunc_check(self, node, stack):
38         if isinstance(node, glibast.GLibInterface):
39             for vfunc in node.virtual_methods:
40                 if not vfunc.invoker:
41                     self._transformer.log_node_warning(vfunc,
42 """Virtual function %r has no known invoker""" % (vfunc.name, ),
43                     context=node)
44
45     def _parameter_warning(self, parent, param, text, *args):
46         if hasattr(parent, 'symbol'):
47             prefix = '%s: ' % (parent.symbol, )
48         else:
49             prefix = ''
50         if isinstance(param, ast.Parameter):
51             context = "argument %s: " % (param.argname, )
52         else:
53             context = "return value: "
54         self._transformer.log_node_warning(parent, prefix + context + text, *args)
55
56     def _introspectable_param_analysis(self, parent, node):
57         is_return = isinstance(node, ast.Return)
58         is_parameter = isinstance(node, ast.Parameter)
59         assert is_return or is_parameter
60
61         if node.type.target_giname is not None:
62             target = self._transformer.lookup_typenode(node.type)
63         else:
64             target = None
65
66         if not node.type.resolved:
67             self._parameter_warning(parent, node,
68 "Unresolved type: %r" % (node.type.unresolved_string, ))
69             parent.introspectable = False
70             return
71
72         if isinstance(node.type, ast.Varargs):
73             parent.introspectable = False
74             return
75
76         if not isinstance(node.type, ast.List) and \
77                 (node.type.target_giname == 'GLib.List'):
78             self._parameter_warning(parent, node, "Missing (element-type) annotation")
79             parent.introspectable = False
80             return
81
82         if (is_parameter
83             and isinstance(target, ast.Callback)
84             and not node.type.target_giname in ('GLib.DestroyNotify',
85                                                 'Gio.AsyncReadyCallback')
86             and node.scope is None):
87                 self._parameter_warning(parent, node,
88                     ("Missing (scope) annotation for callback" +
89                      " without GDestroyNotify (valid: %s, %s)")
90                      % (ast.PARAM_SCOPE_CALL, ast.PARAM_SCOPE_ASYNC))
91                 parent.introspectable = False
92                 return
93
94         if is_return and isinstance(target, ast.Callback):
95             self._parameter_warning(parent, node, "Callbacks cannot be return values; use (skip)")
96             parent.introspectable = False
97             return
98
99         if (is_return
100             and isinstance(target, (ast.Record, ast.Union))
101             and not target.foreign
102             and not isinstance(target, glibast.GLibBoxed)):
103             if node.transfer != ast.PARAM_TRANSFER_NONE:
104                 self._parameter_warning(parent, node,
105 "Invalid non-constant return of bare structure or union; register as boxed type or (skip)")
106                 parent.introspectable = False
107             return
108
109         if node.transfer is None:
110             self._parameter_warning(parent, node, "Missing (transfer) annotation")
111             parent.introspectable = False
112             return
113
114     def _type_is_introspectable(self, typeval, warn=False):
115         if not typeval.resolved:
116             return False
117         if isinstance(typeval, ast.TypeUnknown):
118             return False
119         if isinstance(typeval, (ast.Array, ast.List)):
120             return self._type_is_introspectable(typeval.element_type)
121         elif isinstance(typeval, ast.Map):
122             return (self._type_is_introspectable(typeval.key_type)
123                     and self._type_is_introspectable(typeval.value_type))
124         if typeval.target_foreign:
125             return True
126         if typeval.target_fundamental:
127             if typeval.is_equiv(ast.TYPE_VALIST):
128                 return False
129             # Mark UCHAR as not introspectable temporarily until
130             # we're ready to land the typelib changes
131             if typeval.is_equiv(ast.TYPE_UNICHAR):
132                 return False
133             # These are not introspectable pending us adding
134             # larger type tags to the typelib (in theory these could
135             # be 128 bit or larger)
136             if typeval.is_equiv((ast.TYPE_LONG_LONG, ast.TYPE_LONG_ULONG,
137                                  ast.TYPE_LONG_DOUBLE)):
138                 return False
139             return True
140         target = self._transformer.lookup_typenode(typeval)
141         if not target:
142             return False
143         return target.introspectable and (not target.skip)
144
145     def _analyze_node(self, obj, stack):
146         if obj.skip:
147             return False
148         # Combine one-pass checks here
149         self._interface_vfunc_check(obj, stack)
150         # Our first pass for scriptability
151         if isinstance(obj, ast.Callable):
152             for param in obj.parameters:
153                 self._introspectable_param_analysis(obj, param)
154             self._introspectable_param_analysis(obj, obj.retval)
155         if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)):
156             for field in obj.fields:
157                 if field.type:
158                     if not self._type_is_introspectable(field.type):
159                         field.introspectable = False
160         return True
161
162     def _introspectable_callable_analysis(self, obj, stack):
163         if obj.skip:
164             return False
165         # Propagate introspectability of parameters to entire functions
166         if isinstance(obj, ast.Callable):
167             for param in obj.parameters:
168                 if not self._type_is_introspectable(param.type):
169                     obj.introspectable = False
170                     return True
171             if not self._type_is_introspectable(obj.retval.type):
172                 obj.introspectable = False
173                 return True
174         return True
175
176     def _introspectable_pass3(self, obj, stack):
177         if obj.skip:
178             return False
179         # Propagate introspectability for fields
180         if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)):
181             for field in obj.fields:
182                 if field.anonymous_node:
183                     if not field.anonymous_node.introspectable:
184                         field.introspectable = False
185                 else:
186                     if not self._type_is_introspectable(field.type):
187                         field.introspectable = False
188         # Propagate introspectability for properties
189         if isinstance(obj, (ast.Class, ast.Interface)):
190             for prop in obj.properties:
191                 if not self._type_is_introspectable(prop.type):
192                     prop.introspectable = False
193             for sig in obj.signals:
194                 self._introspectable_callable_analysis(sig, [obj])
195         return True