[scanner] Move over remaining callsites to message
[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 from . import message
23
24 class IntrospectablePass(object):
25
26     def __init__(self, transformer):
27         self._transformer = transformer
28         self._namespace = transformer.namespace
29
30     # Public API
31
32     def validate(self):
33         self._namespace.walk(self._analyze_node)
34         self._namespace.walk(self._introspectable_callable_analysis)
35         self._namespace.walk(self._introspectable_callable_analysis)
36         self._namespace.walk(self._introspectable_pass3)
37
38     def _interface_vfunc_check(self, node, stack):
39         if isinstance(node, glibast.GLibInterface):
40             for vfunc in node.virtual_methods:
41                 if not vfunc.invoker:
42                     message.warn_node(vfunc,
43 """Virtual function %r has no known invoker""" % (vfunc.name, ),
44                     context=node)
45
46     def _parameter_warning(self, parent, param, text, *args):
47         if hasattr(parent, 'symbol'):
48             prefix = '%s: ' % (parent.symbol, )
49         else:
50             prefix = ''
51         if isinstance(param, ast.Parameter):
52             context = "argument %s: " % (param.argname, )
53         else:
54             context = "return value: "
55         message.warn_node(parent, prefix + context + text, *args)
56
57     def _introspectable_param_analysis(self, parent, node):
58         is_return = isinstance(node, ast.Return)
59         is_parameter = isinstance(node, ast.Parameter)
60         assert is_return or is_parameter
61
62         if node.type.target_giname is not None:
63             target = self._transformer.lookup_typenode(node.type)
64         else:
65             target = None
66
67         if not node.type.resolved:
68             self._parameter_warning(parent, node,
69 "Unresolved type: %r" % (node.type.unresolved_string, ))
70             parent.introspectable = False
71             return
72
73         if isinstance(node.type, ast.Varargs):
74             parent.introspectable = False
75             return
76
77         if not isinstance(node.type, ast.List) and \
78                 (node.type.target_giname == 'GLib.List'):
79             self._parameter_warning(parent, node, "Missing (element-type) annotation")
80             parent.introspectable = False
81             return
82
83         if (is_parameter
84             and isinstance(target, ast.Callback)
85             and not node.type.target_giname in ('GLib.DestroyNotify',
86                                                 'Gio.AsyncReadyCallback')
87             and node.scope is None):
88                 self._parameter_warning(parent, node,
89                     ("Missing (scope) annotation for callback" +
90                      " without GDestroyNotify (valid: %s, %s)")
91                      % (ast.PARAM_SCOPE_CALL, ast.PARAM_SCOPE_ASYNC))
92                 parent.introspectable = False
93                 return
94
95         if is_return and isinstance(target, ast.Callback):
96             self._parameter_warning(parent, node, "Callbacks cannot be return values; use (skip)")
97             parent.introspectable = False
98             return
99
100         if (is_return
101             and isinstance(target, (ast.Record, ast.Union))
102             and not target.foreign
103             and not isinstance(target, glibast.GLibBoxed)):
104             if node.transfer != ast.PARAM_TRANSFER_NONE:
105                 self._parameter_warning(parent, node,
106 "Invalid non-constant return of bare structure or union; register as boxed type or (skip)")
107                 parent.introspectable = False
108             return
109
110         if node.transfer is None:
111             self._parameter_warning(parent, node, "Missing (transfer) annotation")
112             parent.introspectable = False
113             return
114
115     def _type_is_introspectable(self, typeval, warn=False):
116         if not typeval.resolved:
117             return False
118         if isinstance(typeval, ast.TypeUnknown):
119             return False
120         if isinstance(typeval, (ast.Array, ast.List)):
121             return self._type_is_introspectable(typeval.element_type)
122         elif isinstance(typeval, ast.Map):
123             return (self._type_is_introspectable(typeval.key_type)
124                     and self._type_is_introspectable(typeval.value_type))
125         if typeval.target_foreign:
126             return True
127         if typeval.target_fundamental:
128             if typeval.is_equiv(ast.TYPE_VALIST):
129                 return False
130             # Mark UCHAR as not introspectable temporarily until
131             # we're ready to land the typelib changes
132             if typeval.is_equiv(ast.TYPE_UNICHAR):
133                 return False
134             # These are not introspectable pending us adding
135             # larger type tags to the typelib (in theory these could
136             # be 128 bit or larger)
137             if typeval.is_equiv((ast.TYPE_LONG_LONG, ast.TYPE_LONG_ULONG,
138                                  ast.TYPE_LONG_DOUBLE)):
139                 return False
140             return True
141         target = self._transformer.lookup_typenode(typeval)
142         if not target:
143             return False
144         return target.introspectable and (not target.skip)
145
146     def _analyze_node(self, obj, stack):
147         if obj.skip:
148             return False
149         # Combine one-pass checks here
150         self._interface_vfunc_check(obj, stack)
151         # Our first pass for scriptability
152         if isinstance(obj, ast.Callable):
153             for param in obj.parameters:
154                 self._introspectable_param_analysis(obj, param)
155             self._introspectable_param_analysis(obj, obj.retval)
156         if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)):
157             for field in obj.fields:
158                 if field.type:
159                     if not self._type_is_introspectable(field.type):
160                         field.introspectable = False
161         return True
162
163     def _introspectable_callable_analysis(self, obj, stack):
164         if obj.skip:
165             return False
166         # Propagate introspectability of parameters to entire functions
167         if isinstance(obj, ast.Callable):
168             for param in obj.parameters:
169                 if not self._type_is_introspectable(param.type):
170                     obj.introspectable = False
171                     return True
172             if not self._type_is_introspectable(obj.retval.type):
173                 obj.introspectable = False
174                 return True
175         return True
176
177     def _introspectable_pass3(self, obj, stack):
178         if obj.skip:
179             return False
180         # Propagate introspectability for fields
181         if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)):
182             for field in obj.fields:
183                 if field.anonymous_node:
184                     if not field.anonymous_node.introspectable:
185                         field.introspectable = False
186                 else:
187                     if not self._type_is_introspectable(field.type):
188                         field.introspectable = False
189         # Propagate introspectability for properties
190         if isinstance(obj, (ast.Class, ast.Interface)):
191             for prop in obj.properties:
192                 if not self._type_is_introspectable(prop.type):
193                     prop.introspectable = False
194             for sig in obj.signals:
195                 self._introspectable_callable_analysis(sig, [obj])
196         return True