Initial packaging for Tizen
[profile/ivi/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 message
22 from .annotationparser import TAG_RETURNS
23
24 class IntrospectablePass(object):
25
26     def __init__(self, transformer, blocks):
27         self._transformer = transformer
28         self._namespace = transformer.namespace
29         self._blocks = blocks
30
31     # Public API
32
33     def validate(self):
34         self._namespace.walk(self._introspectable_alias_analysis)
35         self._namespace.walk(self._propagate_callable_skips)
36         self._namespace.walk(self._analyze_node)
37         self._namespace.walk(self._introspectable_callable_analysis)
38         self._namespace.walk(self._introspectable_callable_analysis)
39         self._namespace.walk(self._introspectable_pass3)
40         self._namespace.walk(self._remove_non_reachable_backcompat_copies)
41
42     def _parameter_warning(self, parent, param, text, position=None):
43         # Suppress VFunctions and Callbacks warnings for now
44         # they cause more problems then they are worth
45         if isinstance(parent, (ast.VFunction, ast.Callback)):
46             return
47
48         block = None
49         if hasattr(parent, 'symbol'):
50             prefix = '%s: ' % (parent.symbol, )
51             block = self._blocks.get(parent.symbol)
52             if block:
53                 position = block.position
54         else:
55             prefix = ''
56         if isinstance(param, ast.Parameter):
57             context = "argument %s: " % (param.argname, )
58         else:
59             context = "return value: "
60             if block:
61                 return_tag = block.get(TAG_RETURNS)
62                 if return_tag:
63                     position = return_tag.position
64         message.warn_node(parent, prefix + context + text,
65                           positions=position)
66
67     def _introspectable_param_analysis(self, parent, node):
68         is_return = isinstance(node, ast.Return)
69         is_parameter = isinstance(node, ast.Parameter)
70         assert is_return or is_parameter
71
72         if node.type.target_giname is not None:
73             target = self._transformer.lookup_typenode(node.type)
74         else:
75             target = None
76
77         if node.skip:
78             return
79
80         if not node.type.resolved:
81             self._parameter_warning(parent, node,
82 "Unresolved type: %r" % (node.type.unresolved_string, ))
83             parent.introspectable = False
84             return
85
86         if isinstance(node.type, ast.Varargs):
87             parent.introspectable = False
88             return
89
90         if (isinstance(node.type, ast.List)
91             and node.type.element_type == ast.TYPE_ANY):
92             self._parameter_warning(parent, node, "Missing (element-type) annotation")
93             parent.introspectable = False
94             return
95
96         if (is_parameter
97             and isinstance(target, ast.Callback)
98             and not node.type.target_giname in ('GLib.DestroyNotify',
99                                                 'Gio.AsyncReadyCallback')
100             and node.scope is None):
101                 self._parameter_warning(parent, node,
102                     ("Missing (scope) annotation for callback" +
103                      " without GDestroyNotify (valid: %s, %s)")
104                      % (ast.PARAM_SCOPE_CALL, ast.PARAM_SCOPE_ASYNC))
105                 parent.introspectable = False
106                 return
107
108         if is_return and isinstance(target, ast.Callback):
109             self._parameter_warning(parent, node, "Callbacks cannot be return values; use (skip)")
110             parent.introspectable = False
111             return
112
113         if (is_return
114             and isinstance(target, (ast.Record, ast.Union))
115             and target.get_type is None
116             and not target.foreign):
117             if node.transfer != ast.PARAM_TRANSFER_NONE:
118                 self._parameter_warning(parent, node,
119 "Invalid non-constant return of bare structure or union; register as boxed type or (skip)")
120                 parent.introspectable = False
121             return
122
123         if node.transfer is None:
124             self._parameter_warning(parent, node, "Missing (transfer) annotation")
125             parent.introspectable = False
126             return
127
128     def _type_is_introspectable(self, typeval, warn=False):
129         if not typeval.resolved:
130             return False
131         if isinstance(typeval, ast.TypeUnknown):
132             return False
133         if isinstance(typeval, (ast.Array, ast.List)):
134             return self._type_is_introspectable(typeval.element_type)
135         elif isinstance(typeval, ast.Map):
136             return (self._type_is_introspectable(typeval.key_type)
137                     and self._type_is_introspectable(typeval.value_type))
138         if typeval.target_foreign:
139             return True
140         if typeval.target_fundamental:
141             if typeval.is_equiv(ast.TYPE_VALIST):
142                 return False
143             # These are not introspectable pending us adding
144             # larger type tags to the typelib (in theory these could
145             # be 128 bit or larger)
146             if typeval.is_equiv((ast.TYPE_LONG_LONG, ast.TYPE_LONG_ULONG,
147                                  ast.TYPE_LONG_DOUBLE)):
148                 return False
149             return True
150         target = self._transformer.lookup_typenode(typeval)
151         if not target:
152             return False
153         return target.introspectable and (not target.skip)
154
155     def _propagate_parameter_skip(self, parent, node):
156         if node.type.target_giname is not None:
157             target = self._transformer.lookup_typenode(node.type)
158             if target is None:
159                 return
160         else:
161             return
162
163         if target.skip:
164             parent.skip = True
165
166     def _introspectable_alias_analysis(self, obj, stack):
167         if isinstance(obj, ast.Alias):
168             if not self._type_is_introspectable(obj.target):
169                 obj.introspectable = False
170         return True
171
172     def _propagate_callable_skips(self, obj, stack):
173         if isinstance(obj, ast.Callable):
174             for param in obj.parameters:
175                 self._propagate_parameter_skip(obj, param)
176             self._propagate_parameter_skip(obj, obj.retval)
177         return True
178
179     def _analyze_node(self, obj, stack):
180         if obj.skip:
181             return False
182         # Our first pass for scriptability
183         if isinstance(obj, ast.Callable):
184             for param in obj.parameters:
185                 self._introspectable_param_analysis(obj, param)
186             self._introspectable_param_analysis(obj, obj.retval)
187         if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)):
188             for field in obj.fields:
189                 if field.type:
190                     if not self._type_is_introspectable(field.type):
191                         field.introspectable = False
192         return True
193
194     def _introspectable_callable_analysis(self, obj, stack):
195         if obj.skip:
196             return False
197         # Propagate introspectability of parameters to entire functions
198         if isinstance(obj, ast.Callable):
199             for param in obj.parameters:
200                 if not self._type_is_introspectable(param.type):
201                     obj.introspectable = False
202                     return True
203             if not self._type_is_introspectable(obj.retval.type):
204                 obj.introspectable = False
205                 return True
206         return True
207
208     def _introspectable_pass3(self, obj, stack):
209         if obj.skip:
210             return False
211         # Propagate introspectability for fields
212         if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)):
213             for field in obj.fields:
214                 if field.anonymous_node:
215                     if not field.anonymous_node.introspectable:
216                         field.introspectable = False
217                 else:
218                     if not self._type_is_introspectable(field.type):
219                         field.introspectable = False
220         # Propagate introspectability for properties
221         if isinstance(obj, (ast.Class, ast.Interface)):
222             for prop in obj.properties:
223                 if not self._type_is_introspectable(prop.type):
224                     prop.introspectable = False
225             for sig in obj.signals:
226                 self._introspectable_callable_analysis(sig, [obj])
227         return True
228
229     def _remove_non_reachable_backcompat_copies(self, obj, stack):
230         if obj.skip:
231             return False
232         if (isinstance(obj, ast.Function)
233             and not obj.introspectable
234             and obj.moved_to is not None):
235             self._namespace.remove(obj)
236         return True