make the tests pass again
[gnome.gobject-introspection] / giscanner / glibtransformer.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19 #
20
21 import ctypes
22 from ctypes.util import find_library
23
24 from . import cgobject
25 from .ast import (Callback, Enum, Function, Member, Namespace, Parameter,
26                   Sequence, Property, Return, Struct, Type, Alias,
27                   Union, type_name_from_ctype)
28 from .transformer import Names
29 from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember, GLibFlags,
30                       GLibInterface, GLibObject, GLibSignal, type_names)
31 from .utils import extract_libtool, to_underscores
32
33
34 class Unresolved(object):
35
36     def __init__(self, target):
37         self.target = target
38
39
40 class UnknownTypeError(Exception):
41     pass
42
43
44 class GLibTransformer(object):
45
46     def __init__(self, transformer, noclosure=False):
47         self._transformer = transformer
48         self._namespace_name = None
49         self._names = Names()
50         self._uscore_type_names = {}
51         self._libraries = []
52         self._failed_types = {}
53         self._private_internal_types = {}
54         self._noclosure = noclosure
55         self._validating = False
56
57     # Public API
58
59     def add_library(self, libname):
60         # For testing mainly.
61         if libname.endswith('.la'):
62             found_libname = extract_libtool(libname)
63         else:
64             found_libname = find_library(libname)
65         if not found_libname:
66             raise ValueError("Failed to find library: %r" % (libname, ))
67         self._libraries.append(ctypes.cdll.LoadLibrary(found_libname))
68
69     def _print_statistics(self):
70         nodes = list(self._names.names.itervalues())
71
72         def count_type(otype):
73             return len([x for x in nodes
74                         if isinstance(x[1], otype)])
75         objectcount = count_type(GLibObject)
76         ifacecount = count_type(GLibInterface)
77         enumcount = count_type(GLibEnum)
78         boxedcount = count_type(GLibBoxed)
79         print " %d nodes; %d objects, %d interfaces, %d enums, %d boxed" \
80             % (len(nodes), objectcount, ifacecount, enumcount, boxedcount)
81
82     def parse(self):
83         namespace = self._transformer.parse()
84         self._namespace_name = namespace.name
85
86         # First pass, parsing
87         for node in namespace.nodes:
88             self._parse_node(node)
89
90         # Introspection is done from within parsing
91
92         # Second pass, delete class structures, resolve
93         # all types we now know about
94         nodes = list(self._names.names.itervalues())
95         for (ns, node) in nodes:
96             try:
97                 self._resolve_node(node)
98             except KeyError, e:
99                 print "WARNING: DELETING node %s: %s" % (node.name, e)
100                 self._remove_attribute(node.name)
101             # associate GtkButtonClass with GtkButton
102             if isinstance(node, Struct):
103                 self._pair_class_struct(node)
104         for (ns, alias) in self._names.aliases.itervalues():
105             self._resolve_alias(alias)
106
107         self._print_statistics()
108         # Third pass; ensure all types are known
109         if not self._noclosure:
110             self._validate(nodes)
111
112         # Create a new namespace with what we found
113         namespace = Namespace(namespace.name)
114         namespace.nodes = map(lambda x: x[1], self._names.aliases.itervalues())
115         for (ns, x) in nodes:
116             namespace.nodes.append(x)
117         return namespace
118
119     # Private
120
121     def _add_attribute(self, node, replace=False):
122         node_name = node.name
123         if (not replace) and node_name in self._names.names:
124             return
125         self._names.names[node_name] = (None, node)
126
127     def _remove_attribute(self, name):
128         del self._names.names[name]
129
130     def _get_attribute(self, name):
131         node = self._names.names.get(name)
132         if node:
133             return node[1]
134         return None
135
136     def _register_internal_type(self, type_name, node):
137         self._names.type_names[type_name] = (None, node)
138         self._uscore_type_names[to_underscores(type_name).lower()] = node
139         # Besides the straight underscore conversion, we also try
140         # removing the underscores from the namespace as a possible C
141         # mapping; e.g. it's webkit_web_view, not web_kit_web_view
142         suffix = self._transformer.strip_namespace_object(type_name)
143         prefix = type_name[:-len(suffix)]
144         no_uscore_prefixed = (prefix + '_' + to_underscores(suffix)).lower()
145         self._uscore_type_names[no_uscore_prefixed] = node
146
147     # Helper functions
148
149     def _create_type(self, type_id):
150         ctype = cgobject.type_name(type_id)
151         type_name = type_name_from_ctype(ctype)
152         type_name = type_name.replace('*', '')
153         type_name = self._resolve_type_name(type_name)
154         return Type(type_name, ctype)
155
156     def _resolve_gtypename(self, gtype_name):
157         try:
158             return self._transformer.gtypename_to_giname(gtype_name,
159                                                          self._names)
160         except KeyError, e:
161             return Unresolved(gtype_name)
162
163     def _create_gobject(self, node):
164         type_name = 'G' + node.name
165         if type_name == 'GObject':
166             parent_gitype = None
167             symbol = 'intern'
168         else:
169             type_id = cgobject.type_from_name(type_name)
170             parent_type_name = cgobject.type_name(
171                 cgobject.type_parent(type_id))
172             parent_gitype = self._resolve_gtypename(parent_type_name)
173             symbol = to_underscores(type_name).lower() + '_get_type'
174         node = GLibObject(node.name, parent_gitype, type_name, symbol)
175         type_id = cgobject.TYPE_OBJECT
176         self._introspect_properties(node, type_id)
177         self._introspect_signals(node, type_id)
178         self._add_attribute(node)
179         self._register_internal_type(type_name, node)
180
181     # Parser
182
183     def _parse_node(self, node):
184         if isinstance(node, Enum):
185             self._parse_enum(node)
186         elif isinstance(node, Function):
187             self._parse_function(node)
188         elif isinstance(node, Struct):
189             self._parse_struct(node)
190         elif isinstance(node, Callback):
191             self._parse_callback(node)
192         elif isinstance(node, Alias):
193             self._parse_alias(node)
194         elif isinstance(node, Member):
195             # FIXME: atk_misc_instance singletons
196             pass
197         elif isinstance(node, Union):
198             self._parse_union(node)
199         else:
200             print 'GLIB Transformer: Unhandled node:', node
201
202     def _parse_alias(self, alias):
203         self._names.aliases[alias.name] = (None, alias)
204
205     def _parse_enum(self, enum):
206         self._add_attribute(enum)
207
208     def _parse_function(self, func):
209         if self._parse_get_type_function(func):
210             return
211
212         self._add_attribute(func)
213
214     def _parse_get_type_function(self, func):
215         symbol = func.symbol
216         if not symbol.endswith('_get_type'):
217             return False
218         # GType *_get_type(void)
219         # This is a bit fishy, why do we need all these aliases?
220         if func.retval.type.name not in ['Type',
221                                          'GType',
222                                          'Object.Type',
223                                          'GObject.Type',
224                                          'GObject.GType']:
225             print ("Warning: *_get_type function returns '%r'"
226                    ", not GObject.Type") % (func.retval.type.name, )
227             return False
228         if func.parameters:
229             return False
230
231         if not self._libraries:
232             print "Warning: No libraries loaded, cannot call %s" % (symbol, )
233             return False
234
235         for library in self._libraries:
236             try:
237                 func = getattr(library, symbol)
238                 break
239             except AttributeError:
240                 continue
241         else:
242             print 'Warning: could not find symbol: %s' % symbol
243             name = symbol.replace('_get_type', '')
244             self._failed_types[name] = True
245             return False
246
247         func.restype = cgobject.GType
248         func.argtypes = []
249         type_id = func()
250         self._introspect_type(type_id, symbol)
251         return True
252
253     def _name_is_internal_gtype(self, giname):
254         try:
255             node = self._get_attribute(giname)
256             return isinstance(node, (GLibObject, GLibInterface, GLibBoxed,
257                                      GLibEnum, GLibFlags))
258         except KeyError, e:
259             return False
260
261     def _parse_method(self, func):
262         if not func.parameters:
263             return False
264         return self._parse_method_common(func, True)
265
266     def _parse_constructor(self, func):
267         return self._parse_method_common(func, False)
268
269     def _parse_method_common(self, func, is_method):
270         # Skip _get_type functions, we processed them
271         # already
272         if func.symbol.endswith('_get_type'):
273             return None
274
275         if not is_method:
276             target_arg = func.retval
277         else:
278             target_arg = func.parameters[0]
279         target_arg.type = self._resolve_param_type(target_arg.type)
280
281         if is_method:
282             # Methods require their first arg to be a known class
283             # Look at the original C type (before namespace stripping), without
284             # pointers: GtkButton -> gtk_button_, so we can figure out the
285             # method name
286             argtype = target_arg.type.ctype.replace('*', '')
287             name = self._transformer.strip_namespace_object(argtype)
288             name_uscore = to_underscores(name).lower()
289             name_offset = func.symbol.find(name_uscore)
290             if name_offset < 0:
291                 return None
292             prefix = func.symbol[:name_offset+len(name_uscore)]
293         else:
294             # Constructors must have _new
295             # Take everything before that as class name
296             new_idx = func.symbol.find('_new')
297             if new_idx < 0:
298                 return None
299             # Constructors don't return basic types
300             if target_arg.type.name in type_names:
301                 return None
302             prefix = func.symbol[:new_idx]
303
304         klass = None
305
306         def valid_matching_klass(tclass):
307             return (tclass is not None and
308                     isinstance(tclass, (GLibObject, GLibBoxed,
309                                         GLibInterface)) and
310                     not isinstance(tclass, GLibEnum))
311
312         # First look for an exact match;
313         klass = self._uscore_type_names.get(prefix)
314         # Now try searching for a prefix as a last resort
315         if klass is None or not valid_matching_klass(klass):
316             for key in self._uscore_type_names:
317                 klass = None
318                 if key.startswith(prefix):
319                     klass = self._uscore_type_names.get(key)
320                     if valid_matching_klass(klass):
321                         break
322         # Enums can't have ctors or methods
323         if klass is None:
324             return
325
326         if not is_method:
327             # Interfaces can't have constructors, punt to global scope
328             if isinstance(klass, GLibInterface):
329                 return None
330             # TODO - check that the return type is a subclass of the
331             # class from the prefix
332
333         self._remove_attribute(func.name)
334         # Strip namespace and object prefix: gtk_window_new -> new
335         func.name = func.symbol[len(prefix)+1:]
336         if is_method:
337             klass.methods.append(func)
338         else:
339             klass.constructors.append(func)
340         return func
341
342     def _parse_struct(self, struct):
343         # This is a hack, but GObject is a rather fundamental piece so.
344         internal_names = ["Object", 'InitiallyUnowned']
345         g_internal_names = ["G" + x for x in internal_names]
346         if (self._namespace_name == 'GObject' and
347             struct.name in internal_names):
348             self._create_gobject(struct)
349             return
350         elif struct.name in g_internal_names:
351             # Avoid duplicates
352             return
353         node = self._names.names.get(struct.name)
354         if node is None:
355             self._add_attribute(struct, replace=True)
356             return
357         (ns, node) = node
358         node.fields = struct.fields[:]
359
360     def _parse_union(self, union):
361         node = self._names.names.get(union.name)
362         if node is None:
363             self._add_attribute(union, replace=True)
364             return
365         (ns, node) = node
366         node.fields = union.fields[:]
367
368     def _parse_callback(self, callback):
369         self._add_attribute(callback)
370
371     def _strip_class_suffix(self, name):
372         if (name.endswith('Class') or
373             name.endswith('Iface')):
374             return name[:-5]
375         elif name.endswith('Interface'):
376             return name[:-9]
377         else:
378             return name
379
380     def _arg_is_failed(self, param):
381         ctype = self._transformer.ctype_of(param).replace('*', '')
382         uscored = to_underscores(self._strip_class_suffix(ctype)).lower()
383         if uscored in self._failed_types:
384             print "Warning: failed type: %r" % (param, )
385             return True
386         return False
387
388     def _pair_class_struct(self, maybe_class):
389         name = self._strip_class_suffix(maybe_class.name)
390         if name == maybe_class.name:
391             return
392
393         if self._arg_is_failed(maybe_class):
394             print "WARNING: deleting no-type %r" % (maybe_class.name, )
395             del self._names.names[maybe_class.name]
396             return
397
398         name = self._resolve_type_name(name)
399         resolved = self._transformer.strip_namespace_object(name)
400         pair_class = self._get_attribute(resolved)
401         if pair_class and isinstance(pair_class,
402                                      (GLibObject, GLibBoxed, GLibInterface)):
403             for field in maybe_class.fields[1:]:
404                 pair_class.fields.append(field)
405             return
406         name = self._transformer.strip_namespace_object(maybe_class.name)
407         pair_class = self._get_attribute(name)
408         if pair_class and isinstance(pair_class,
409                                      (GLibObject, GLibBoxed, GLibInterface)):
410
411             del self._names.names[maybe_class.name]
412
413     # Introspection
414
415     def _introspect_type(self, type_id, symbol):
416         fundamental_type_id = cgobject.type_fundamental(type_id)
417         if (fundamental_type_id == cgobject.TYPE_ENUM or
418             fundamental_type_id == cgobject.TYPE_FLAGS):
419             self._introspect_enum(fundamental_type_id, type_id, symbol)
420         elif fundamental_type_id == cgobject.TYPE_OBJECT:
421             self._introspect_object(type_id, symbol)
422         elif fundamental_type_id == cgobject.TYPE_INTERFACE:
423             self._introspect_interface(type_id, symbol)
424         elif fundamental_type_id == cgobject.TYPE_BOXED:
425             self._introspect_boxed(type_id, symbol)
426         elif fundamental_type_id == cgobject.TYPE_BOXED:
427             self._introspect_boxed(type_id, symbol)
428         elif fundamental_type_id == cgobject.TYPE_POINTER:
429             # FIXME: Should we do something about these?
430             #        GHashTable, GValue and a few other fundamentals are
431             #        covered here
432             return
433         else:
434             print 'unhandled GType: %s(%d)' % (cgobject.type_name(type_id),
435                                                type_id)
436
437     def _introspect_enum(self, ftype_id, type_id, symbol):
438         type_class = cgobject.type_class_ref(type_id)
439         if type_class is None:
440             return
441
442         members = []
443         for enum_value in type_class.get_values():
444             members.append(GLibEnumMember(enum_value.value_nick,
445                                           enum_value.value,
446                                           enum_value.value_name,
447                                           enum_value.value_nick))
448
449         klass = (GLibFlags if ftype_id == cgobject.TYPE_FLAGS else GLibEnum)
450         type_name = cgobject.type_name(type_id)
451         enum_name = self._transformer.strip_namespace_object(type_name)
452         node = klass(enum_name, type_name, members, symbol)
453         self._add_attribute(node, replace=True)
454         self._register_internal_type(type_name, node)
455
456     def _introspect_object(self, type_id, symbol):
457         type_name = cgobject.type_name(type_id)
458         # We handle this specially above; in 2.16 and below there
459         # was no g_object_get_type, for later versions we need
460         # to skip it
461         if type_name == 'GObject':
462             return
463         parent_type_name = cgobject.type_name(cgobject.type_parent(type_id))
464         parent_gitype = self._resolve_gtypename(parent_type_name)
465         node = GLibObject(
466             self._transformer.strip_namespace_object(type_name),
467             parent_gitype,
468             type_name, symbol)
469         self._introspect_properties(node, type_id)
470         self._introspect_signals(node, type_id)
471         self._introspect_implemented_interfaces(node, type_id)
472         self._add_attribute(node, replace=True)
473         self._register_internal_type(type_name, node)
474
475     def _introspect_interface(self, type_id, symbol):
476         type_name = cgobject.type_name(type_id)
477         parent_type_name = cgobject.type_name(cgobject.type_parent(type_id))
478         if parent_type_name == 'GInterface':
479             parent_gitype = None
480         else:
481             parent_gitype = self._resolve_gtypename(parent_type_name)
482         node = GLibInterface(
483             self._transformer.strip_namespace_object(type_name),
484             parent_gitype,
485             type_name, symbol)
486         self._introspect_properties(node, type_id)
487         self._introspect_signals(node, type_id)
488         # GtkFileChooserEmbed is an example of a private interface, we
489         # just filter them out
490         if symbol.startswith('_'):
491             print "NOTICE: Marking %s as internal type" % (type_name, )
492             self._private_internal_types[type_name] = node
493         else:
494             self._add_attribute(node, replace=True)
495             self._register_internal_type(type_name, node)
496
497     def _introspect_boxed(self, type_id, symbol):
498         type_name = cgobject.type_name(type_id)
499         node = GLibBoxed(self._transformer.strip_namespace_object(type_name),
500                          type_name, symbol)
501         self._add_attribute(node, replace=True)
502         self._register_internal_type(type_name, node)
503
504     def _introspect_implemented_interfaces(self, node, type_id):
505         fundamental_type_id = cgobject.type_fundamental(type_id)
506         if fundamental_type_id != cgobject.TYPE_OBJECT:
507             raise AssertionError
508         interfaces = cgobject.type_interfaces(type_id)
509         gt_interfaces = []
510         for interface_typeid in interfaces:
511             iname = cgobject.type_name(interface_typeid)
512             gitype = self._resolve_gtypename(iname)
513             gt_interfaces.append(gitype)
514         node.interfaces = gt_interfaces
515
516     def _introspect_properties(self, node, type_id):
517         fundamental_type_id = cgobject.type_fundamental(type_id)
518         if fundamental_type_id == cgobject.TYPE_OBJECT:
519             pspecs = cgobject.object_class_list_properties(type_id)
520         elif fundamental_type_id == cgobject.TYPE_INTERFACE:
521             pspecs = cgobject.object_interface_list_properties(type_id)
522         else:
523             raise AssertionError
524
525         for pspec in pspecs:
526             if pspec.owner_type != type_id:
527                 continue
528             ctype = cgobject.type_name(pspec.value_type)
529             node.properties.append(Property(
530                 pspec.name,
531                 type_name_from_ctype(ctype),
532                 ctype,
533                 ))
534
535     def _introspect_signals(self, node, type_id):
536         for signal_info in cgobject.signal_list(type_id):
537             rtype = self._create_type(signal_info.return_type)
538             return_ = Return(rtype)
539             signal = GLibSignal(signal_info.signal_name, return_)
540             for i, parameter in enumerate(signal_info.get_params()):
541                 if i == 0:
542                     name = 'object'
543                 else:
544                     name = 'p%s' % (i-1, )
545                 ptype = self._create_type(parameter)
546                 param = Parameter(name, ptype)
547                 signal.parameters.append(param)
548             node.signals.append(signal)
549
550     # Resolver
551
552     def _resolve_type_name(self, type_name, ctype=None):
553         # Workaround glib bug #548689, to be included in 2.18.0
554         if type_name == "GParam":
555             type_name = "GObject.ParamSpec"
556
557         res = self._transformer.resolve_type_name_full
558         try:
559             return res(type_name, ctype, self._names)
560         except KeyError, e:
561             return self._transformer.resolve_type_name(type_name, ctype)
562
563     def _validate_type_name(self, name):
564         if name in type_names:
565             return True
566         if name.find('.') >= 0:
567             return True
568         if name in self._names.aliases:
569             return True
570         if name in self._names.names:
571             return True
572         return False
573
574     def _validate_type(self, ptype):
575         if isinstance(ptype, Sequence):
576             etype = ptype.element_type
577             if isinstance(etype, Sequence):
578                 return self._validate_type(etype)
579             return self._validate_type_name(etype)
580         return self._validate_type_name(ptype.name)
581
582     def _resolve_param_type_validate(self, ptype):
583         ptype = self._resolve_param_type(ptype)
584         if self._validating and not self._validate_type(ptype):
585             raise UnknownTypeError("Unknown type %r" % (ptype, ))
586         return ptype
587
588     def _resolve_param_type(self, ptype):
589         try:
590             return self._transformer.resolve_param_type_full(ptype,
591                                                              self._names)
592         except KeyError, e:
593             return self._transformer.resolve_param_type(ptype)
594         return ptype
595
596     def _resolve_node(self, node):
597         if isinstance(node, Function):
598             self._resolve_function_toplevel(node)
599
600         elif isinstance(node, Callback):
601             self._resolve_function(node)
602         elif isinstance(node, GLibObject):
603             self._resolve_glib_object(node)
604         elif isinstance(node, GLibInterface):
605             self._resolve_glib_interface(node)
606         elif isinstance(node, GLibBoxed):
607             self._resolve_glib_boxed(node)
608         elif isinstance(node, Struct):
609             self._resolve_struct(node)
610         elif isinstance(node, Union):
611             self._resolve_union(node)
612         elif isinstance(node, Alias):
613             self._resolve_alias(node)
614
615     def _resolve_function_toplevel(self, func):
616         newfunc = self._parse_constructor(func)
617         if not newfunc:
618             newfunc = self._parse_method(func)
619             if not newfunc:
620                 self._resolve_function(func)
621                 return
622         self._resolve_function(newfunc)
623
624     def _resolve_struct(self, node):
625         for field in node.fields:
626             self._resolve_field(field)
627
628     def _resolve_union(self, node):
629         for field in node.fields:
630             self._resolve_field(field)
631
632     def _force_resolve(self, item, allow_unknown=False):
633         if isinstance(item, Unresolved):
634             if item.target in self._private_internal_types:
635                 return None
636             try:
637                 return self._transformer.gtypename_to_giname(item.target,
638                                                              self._names)
639             except KeyError, e:
640                 if allow_unknown:
641                     print "WARNING: Skipping unknown interface %s" % \
642                         (item.target, )
643                     return None
644                 else:
645                     raise
646         if item in self._private_internal_types:
647             return None
648         return item
649
650     def _resolve_glib_interface(self, node):
651         node.parent = self._force_resolve(node.parent)
652         self._resolve_methods(node.methods)
653         self._resolve_properties(node.properties)
654         self._resolve_signals(node.signals)
655
656     def _resolve_glib_object(self, node):
657         node.parent = self._force_resolve(node.parent)
658         node.interfaces = filter(None,
659             [self._force_resolve(x, allow_unknown=True)
660                                     for x in node.interfaces])
661         self._resolve_constructors(node.constructors)
662         self._resolve_methods(node.methods)
663         self._resolve_properties(node.properties)
664         self._resolve_signals(node.signals)
665
666     def _resolve_glib_boxed(self, node):
667         self._resolve_constructors(node.constructors)
668         self._resolve_methods(node.methods)
669
670     def _resolve_constructors(self, constructors):
671         for ctor in constructors:
672             self._resolve_function(ctor)
673
674     def _resolve_methods(self, methods):
675         for method in methods:
676             self._resolve_function(method)
677
678     def _resolve_signals(self, signals):
679         for signal in signals:
680             self._resolve_function(signal)
681
682     def _resolve_properties(self, properties):
683         for prop in properties:
684             self._resolve_property(prop)
685
686     def _resolve_property(self, prop):
687         prop.type = self._resolve_param_type(prop.type)
688
689     def _resolve_function(self, func):
690         self._resolve_parameters(func.parameters)
691         func.retval.type = self._resolve_param_type(func.retval.type)
692
693     def _resolve_parameters(self, parameters):
694         for parameter in parameters:
695             parameter.type = self._resolve_param_type(parameter.type)
696
697     def _resolve_field(self, field):
698         if isinstance(field, Callback):
699             self._resolve_function(field)
700             return
701         field.type = self._resolve_param_type(field.type)
702
703     def _resolve_alias(self, alias):
704         alias.target = self._resolve_type_name(alias.target, alias.target)
705
706     # Validation
707
708     def _validate(self, nodes):
709         nodes = list(self._names.names.itervalues())
710         i = 0
711         self._validating = True
712         while True:
713             initlen = len(nodes)
714
715             print "Type resolution; pass=%d" % (i, )
716             nodes = list(self._names.names.itervalues())
717             for node in nodes:
718                 try:
719                     self._resolve_node(node)
720                 except UnknownTypeError, e:
721                     print "WARNING: %s: Deleting %r" % (e, node)
722                     self._remove_attribute(node.name)
723             if len(nodes) == initlen:
724                 break
725             i += 1
726             self._print_statistics()
727         self._validating = False