1 #!/usr/bin/env python 2 3 """ 4 Deduce types for usage observations. 5 6 Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from common import get_attrname_from_location, get_attrnames, \ 23 init_item, make_key, sorted_output, \ 24 CommonOutput 25 from encoders import encode_attrnames, encode_access_location, \ 26 encode_constrained, encode_location, encode_usage, \ 27 get_kinds, test_for_kinds, test_for_types 28 from os.path import join 29 from referencing import combine_types, is_single_class_type, separate_types, \ 30 Reference 31 32 class Deducer(CommonOutput): 33 34 "Deduce types in a program." 35 36 def __init__(self, importer, output): 37 38 """ 39 Initialise an instance using the given 'importer' that will perform 40 deductions on the program information, writing the results to the given 41 'output' directory. 42 """ 43 44 self.importer = importer 45 self.output = output 46 47 # Descendants of classes. 48 49 self.descendants = {} 50 self.init_descendants() 51 self.init_special_attributes() 52 53 # Map locations to usage in order to determine specific types. 54 55 self.location_index = {} 56 57 # Map access locations to definition locations. 58 59 self.access_index = {} 60 61 # Map aliases to accesses that define them. 62 63 self.alias_index = {} 64 65 # Map usage observations to assigned attributes. 66 67 self.assigned_attrs = {} 68 69 # Map usage observations to objects. 70 71 self.attr_class_types = {} 72 self.attr_instance_types = {} 73 self.attr_module_types = {} 74 75 # Modified attributes from usage observations. 76 77 self.modified_attributes = {} 78 79 # Map locations to types, constrained indicators and attributes. 80 81 self.accessor_class_types = {} 82 self.accessor_instance_types = {} 83 self.accessor_module_types = {} 84 self.provider_class_types = {} 85 self.provider_instance_types = {} 86 self.provider_module_types = {} 87 self.reference_constrained = set() 88 self.access_constrained = set() 89 self.referenced_attrs = {} 90 self.referenced_objects = {} 91 92 # Accumulated information about accessors and providers. 93 94 self.accessor_general_class_types = {} 95 self.accessor_general_instance_types = {} 96 self.accessor_general_module_types = {} 97 self.accessor_all_types = {} 98 self.accessor_all_general_types = {} 99 self.provider_all_types = {} 100 self.accessor_guard_tests = {} 101 102 # Accumulated information about accessed attributes and 103 # attribute-specific accessor tests. 104 105 self.reference_class_attrs = {} 106 self.reference_instance_attrs = {} 107 self.reference_module_attrs = {} 108 self.reference_all_attrs = {} 109 self.reference_all_attrtypes = {} 110 self.reference_test_types = {} 111 self.reference_test_accessor_types = {} 112 113 # The processing workflow itself. 114 115 self.init_usage_index() 116 self.init_accessors() 117 self.init_accesses() 118 self.init_aliases() 119 self.init_attr_type_indexes() 120 self.modify_mutated_attributes() 121 self.identify_references() 122 self.classify_accessors() 123 self.classify_accesses() 124 125 def to_output(self): 126 127 "Write the output files using deduction information." 128 129 self.check_output() 130 131 self.write_mutations() 132 self.write_accessors() 133 self.write_accesses() 134 135 def write_mutations(self): 136 137 """ 138 Write mutation-related output in the following format: 139 140 qualified name " " original object type 141 142 Object type can be "<class>", "<function>" or "<var>". 143 """ 144 145 f = open(join(self.output, "mutations"), "w") 146 try: 147 attrs = self.modified_attributes.items() 148 attrs.sort() 149 150 for attr, value in attrs: 151 print >>f, attr, value 152 finally: 153 f.close() 154 155 def write_accessors(self): 156 157 """ 158 Write reference-related output in the following format for types: 159 160 location " " ( "constrained" | "deduced" ) " " attribute type " " most general type names " " number of specific types 161 162 Note that multiple lines can be given for each location, one for each 163 attribute type. 164 165 Locations have the following format: 166 167 qualified name of scope "." local name ":" name version 168 169 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 170 where the latter indicates an absence of suitable references. 171 172 Type names indicate the type providing the attributes, being either a 173 class or module qualified name. 174 175 ---- 176 177 A summary of accessor types is formatted as follows: 178 179 location " " ( "constrained" | "deduced" ) " " ( "specific" | "common" | "unguarded" ) " " most general type names " " number of specific types 180 181 This summary groups all attribute types (class, instance, module) into a 182 single line in order to determine the complexity of identifying an 183 accessor. 184 185 ---- 186 187 References that cannot be supported by any types are written to a 188 warnings file in the following format: 189 190 location 191 192 ---- 193 194 For each location where a guard would be asserted to guarantee the 195 nature of an object, the following format is employed: 196 197 location " " ( "specific" | "common" ) " " object kind " " object types 198 199 Object kind can be "<class>", "<instance>" or "<module>". 200 """ 201 202 f_type_summary = open(join(self.output, "type_summary"), "w") 203 f_types = open(join(self.output, "types"), "w") 204 f_warnings = open(join(self.output, "type_warnings"), "w") 205 f_guards = open(join(self.output, "guards"), "w") 206 207 try: 208 locations = self.accessor_class_types.keys() 209 locations.sort() 210 211 for location in locations: 212 constrained = location in self.reference_constrained 213 214 # Accessor information. 215 216 class_types = self.accessor_class_types[location] 217 instance_types = self.accessor_instance_types[location] 218 module_types = self.accessor_module_types[location] 219 220 general_class_types = self.accessor_general_class_types[location] 221 general_instance_types = self.accessor_general_instance_types[location] 222 general_module_types = self.accessor_general_module_types[location] 223 224 all_types = self.accessor_all_types[location] 225 all_general_types = self.accessor_all_general_types[location] 226 227 if class_types: 228 print >>f_types, encode_location(location), encode_constrained(constrained), "<class>", \ 229 sorted_output(general_class_types), len(class_types) 230 231 if instance_types: 232 print >>f_types, encode_location(location), encode_constrained(constrained), "<instance>", \ 233 sorted_output(general_instance_types), len(instance_types) 234 235 if module_types: 236 print >>f_types, encode_location(location), encode_constrained(constrained), "<module>", \ 237 sorted_output(general_module_types), len(module_types) 238 239 if not all_types: 240 print >>f_types, encode_location(location), "deduced", "<>", 0 241 attrnames = list(self.location_index[location]) 242 attrnames.sort() 243 print >>f_warnings, encode_location(location), "; ".join(map(encode_usage, attrnames)) 244 245 guard_test = self.accessor_guard_tests.get(location) 246 247 # Write specific type guard details. 248 249 if guard_test and guard_test.startswith("specific"): 250 print >>f_guards, encode_location(location), guard_test, \ 251 get_kinds(all_types)[0], \ 252 sorted_output(all_types) 253 254 # Write common type guard details. 255 256 elif guard_test and guard_test.startswith("common"): 257 print >>f_guards, encode_location(location), guard_test, \ 258 get_kinds(all_general_types)[0], \ 259 sorted_output(all_general_types) 260 261 print >>f_type_summary, encode_location(location), encode_constrained(constrained), \ 262 guard_test or "unguarded", sorted_output(all_general_types), len(all_types) 263 264 finally: 265 f_type_summary.close() 266 f_types.close() 267 f_warnings.close() 268 f_guards.close() 269 270 def write_accesses(self): 271 272 """ 273 Specific attribute output is produced in the following format: 274 275 location " " ( "constrained" | "deduced" ) " " attribute type " " attribute references 276 277 Note that multiple lines can be given for each location and attribute 278 name, one for each attribute type. 279 280 Locations have the following format: 281 282 qualified name of scope "." local name " " attribute name ":" access number 283 284 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 285 where the latter indicates an absence of suitable references. 286 287 Attribute references have the following format: 288 289 object type ":" qualified name 290 291 Object type can be "<class>", "<function>" or "<var>". 292 293 ---- 294 295 A summary of attributes is formatted as follows: 296 297 location " " attribute name " " ( "constrained" | "deduced" ) " " test " " attribute references 298 299 This summary groups all attribute types (class, instance, module) into a 300 single line in order to determine the complexity of each access. 301 302 Tests can be "validate", "specific", "untested", "guarded-validate" or "guarded-specific". 303 304 ---- 305 306 For each access where a test would be asserted to guarantee the 307 nature of an attribute, the following formats are employed: 308 309 location " " attribute name " " "validate" 310 location " " attribute name " " "specific" " " attribute type " " object type 311 312 ---- 313 314 References that cannot be supported by any types are written to a 315 warnings file in the following format: 316 317 location 318 """ 319 320 f_attr_summary = open(join(self.output, "attribute_summary"), "w") 321 f_attrs = open(join(self.output, "attributes"), "w") 322 f_tests = open(join(self.output, "tests"), "w") 323 f_warnings = open(join(self.output, "attribute_warnings"), "w") 324 325 try: 326 locations = self.referenced_attrs.keys() 327 locations.sort() 328 329 for location in locations: 330 constrained = location in self.access_constrained 331 332 # Attribute information, both name-based and anonymous. 333 334 referenced_attrs = self.referenced_attrs[location] 335 336 if referenced_attrs: 337 attrname = get_attrname_from_location(location) 338 339 all_accessed_attrs = self.reference_all_attrs[location] 340 341 for attrtype, attrs in self.get_referenced_attrs(location): 342 print >>f_attrs, encode_access_location(location), encode_constrained(constrained), attrtype, sorted_output(attrs) 343 344 test_type = self.reference_test_types.get(location) 345 346 # Write the need to test at run time. 347 348 if test_type == "validate": 349 print >>f_tests, encode_access_location(location), test_type 350 351 # Write any type checks for anonymous accesses. 352 353 elif test_type and self.reference_test_accessor_types.get(location): 354 print >>f_tests, encode_access_location(location), test_type, \ 355 sorted_output(all_accessed_attrs), \ 356 self.reference_test_accessor_types[location] 357 358 print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \ 359 test_type or "untested", sorted_output(all_accessed_attrs) 360 361 else: 362 print >>f_warnings, encode_access_location(location) 363 364 finally: 365 f_attr_summary.close() 366 f_attrs.close() 367 f_tests.close() 368 f_warnings.close() 369 370 def classify_accessors(self): 371 372 "For each program location, classify accessors." 373 374 # Where instance and module types are defined, class types are also 375 # defined. See: init_definition_details 376 377 locations = self.accessor_class_types.keys() 378 379 for location in locations: 380 constrained = location in self.reference_constrained 381 382 # Provider information. 383 384 class_types = self.provider_class_types[location] 385 instance_types = self.provider_instance_types[location] 386 module_types = self.provider_module_types[location] 387 388 # Collect specific and general type information. 389 390 self.provider_all_types[location] = all_types = \ 391 combine_types(class_types, instance_types, module_types) 392 393 # Accessor information. 394 395 class_types = self.accessor_class_types[location] 396 self.accessor_general_class_types[location] = \ 397 general_class_types = self.get_most_general_types(class_types) 398 399 instance_types = self.accessor_instance_types[location] 400 self.accessor_general_instance_types[location] = \ 401 general_instance_types = self.get_most_general_types(instance_types) 402 403 module_types = self.accessor_module_types[location] 404 self.accessor_general_module_types[location] = \ 405 general_module_types = self.get_most_general_module_types(module_types) 406 407 # Collect specific and general type information. 408 409 self.accessor_all_types[location] = all_types = \ 410 combine_types(class_types, instance_types, module_types) 411 412 self.accessor_all_general_types[location] = all_general_types = \ 413 combine_types(general_class_types, general_instance_types, general_module_types) 414 415 # Record guard information. 416 417 if not constrained: 418 419 # Record specific type guard details. 420 421 if len(all_types) == 1: 422 self.accessor_guard_tests[location] = test_for_types("specific", all_types) 423 elif is_single_class_type(all_types): 424 self.accessor_guard_tests[location] = "specific-object" 425 426 # Record common type guard details. 427 428 elif len(all_general_types) == 1: 429 self.accessor_guard_tests[location] = test_for_types("common", all_types) 430 elif is_single_class_type(all_general_types): 431 self.accessor_guard_tests[location] = "common-object" 432 433 # Otherwise, no convenient guard can be defined. 434 435 def classify_accesses(self): 436 437 "For each program location, classify accesses." 438 439 # Attribute accesses use potentially different locations to those of 440 # accessors. 441 442 locations = self.referenced_attrs.keys() 443 444 for location in locations: 445 constrained = location in self.access_constrained 446 447 # Determine whether the attribute access is guarded or not. 448 449 accessor_locations = self.get_accessors_for_access(location) 450 451 all_provider_types = set() 452 all_accessor_types = set() 453 all_accessor_general_types = set() 454 455 for accessor_location in accessor_locations: 456 457 # Obtain the provider types for guard-related attribute access 458 # checks. 459 460 provider_guard_types = self.provider_all_types.get(accessor_location) 461 all_provider_types.update(provider_guard_types) 462 463 # Obtain the accessor types. 464 465 accessor_guard_types = self.accessor_all_types.get(accessor_location) 466 accessor_general_guard_types = self.accessor_all_general_types.get(accessor_location) 467 all_accessor_types.update(accessor_guard_types) 468 all_accessor_general_types.update(accessor_general_guard_types) 469 470 # Determine the basis on which the access has been guarded. 471 472 guarded = ( 473 len(all_accessor_types) == 1 or 474 is_single_class_type(all_accessor_types) or 475 len(all_accessor_general_types) == 1 or 476 is_single_class_type(all_accessor_general_types) 477 ) 478 479 if guarded: 480 (guard_class_types, guard_instance_types, guard_module_types, 481 _function_types, _var_types) = separate_types(all_provider_types) 482 483 # Attribute information, both name-based and anonymous. 484 485 referenced_attrs = self.referenced_attrs[location] 486 487 if referenced_attrs: 488 489 # Record attribute information for each name used on the 490 # accessor. 491 492 attrname = get_attrname_from_location(location) 493 494 all_accessed_attrs = set() 495 all_accessed_attrtypes = set() 496 all_providers = set() 497 all_general_providers = set() 498 499 for attrtype, d, general in [ 500 ("<class>", self.reference_class_attrs, self.get_most_general_types), 501 ("<instance>", self.reference_instance_attrs, self.get_most_general_types), 502 ("<module>", self.reference_module_attrs, self.get_most_general_module_types)]: 503 504 attrs = [attr for _attrtype, object_type, attr in referenced_attrs if _attrtype == attrtype] 505 providers = [object_type for _attrtype, object_type, attr in referenced_attrs if _attrtype == attrtype] 506 general_providers = general(providers) 507 d[location] = set(attrs) 508 509 if attrs: 510 all_accessed_attrs.update(attrs) 511 all_accessed_attrtypes.add(attrtype) 512 all_providers.update(providers) 513 all_general_providers.update(general_providers) 514 515 # Determine which attributes would be provided by the 516 # accessor types upheld by a guard. 517 518 if guarded: 519 guard_attrs = [attr for _attrtype, object_type, attr in 520 self._identify_reference_attribute(attrname, guard_class_types, guard_instance_types, guard_module_types)] 521 else: 522 guard_attrs = None 523 524 self.reference_all_attrs[location] = all_accessed_attrs 525 self.reference_all_attrtypes[location] = all_accessed_attrtypes 526 527 # Suitably guarded accesses, where the nature of the 528 # accessor can be guaranteed, do not require the attribute 529 # involved to be validated. Otherwise, for unguarded 530 # accesses, access-level tests are required. 531 532 if not constrained: 533 534 # Provide informational test types. 535 536 if guarded and all_accessed_attrs.issubset(guard_attrs): 537 if len(all_accessor_types) == 1: 538 self.reference_test_types[location] = test_for_types("guarded-specific", all_accessor_types) 539 elif is_single_class_type(all_accessor_types): 540 self.reference_test_types[location] = "guarded-specific-object" 541 elif len(all_accessor_general_types) == 1: 542 self.reference_test_types[location] = test_for_types("guarded-common", all_accessor_general_types) 543 elif is_single_class_type(all_accessor_general_types): 544 self.reference_test_types[location] = "guarded-common-object" 545 546 # Provide active test types. 547 548 else: 549 # Record the need to test the type of anonymous and 550 # unconstrained accessors. 551 552 if len(all_providers) == 1: 553 provider = list(all_providers)[0] 554 if provider != '__builtins__.object': 555 all_accessor_kinds = set(get_kinds(all_accessor_types)) 556 if len(all_accessor_kinds) == 1: 557 test_type = test_for_kinds("specific", all_accessor_kinds) 558 else: 559 test_type = "specific-object" 560 self.reference_test_types[location] = test_type 561 self.reference_test_accessor_types[location] = provider 562 563 elif len(all_general_providers) == 1: 564 provider = list(all_general_providers)[0] 565 if provider != '__builtins__.object': 566 all_accessor_kinds = set(get_kinds(all_accessor_general_types)) 567 if len(all_accessor_kinds) == 1: 568 test_type = test_for_kinds("common", all_accessor_kinds) 569 else: 570 test_type = "common-object" 571 self.reference_test_types[location] = test_type 572 self.reference_test_accessor_types[location] = provider 573 574 # Record the need to test the identity of the attribute. 575 576 else: 577 self.reference_test_types[location] = "validate" 578 579 def get_referenced_attrs(self, location): 580 581 """ 582 Return attributes referenced at the given access 'location' by the given 583 'attrname' as a list of (attribute type, attribute set) tuples. 584 """ 585 586 ca = self.reference_class_attrs[location] 587 ia = self.reference_instance_attrs[location] 588 ma = self.reference_module_attrs[location] 589 590 l = [] 591 for attrtype, attrs in (("<class>", ca), ("<instance>", ia), ("<module>", ma)): 592 if attrs: 593 l.append((attrtype, attrs)) 594 return l 595 596 # Initialisation methods. 597 598 def init_descendants(self): 599 600 "Identify descendants of each class." 601 602 for name in self.importer.classes.keys(): 603 self.get_descendants_for_class(name) 604 605 def get_descendants_for_class(self, name): 606 607 """ 608 Use subclass information to deduce the descendants for the class of the 609 given 'name'. 610 """ 611 612 if not self.descendants.has_key(name): 613 descendants = set() 614 615 for subclass in self.importer.subclasses[name]: 616 descendants.update(self.get_descendants_for_class(subclass)) 617 descendants.add(subclass) 618 619 self.descendants[name] = descendants 620 621 return self.descendants[name] 622 623 def init_special_attributes(self): 624 625 "Add special attributes to the classes for inheritance-related tests." 626 627 all_class_attrs = self.importer.all_class_attrs 628 629 for name, descendants in self.descendants.items(): 630 for descendant in descendants: 631 all_class_attrs[descendant]["#%s" % name] = name 632 633 for name in all_class_attrs.keys(): 634 all_class_attrs[name]["#%s" % name] = name 635 636 def init_usage_index(self): 637 638 """ 639 Create indexes for module and function attribute usage and for anonymous 640 accesses. 641 """ 642 643 for module in self.importer.get_modules(): 644 for path, assignments in module.attr_usage.items(): 645 self.add_usage(assignments, path) 646 647 for location, all_attrnames in self.importer.all_attr_accesses.items(): 648 for attrnames in all_attrnames: 649 attrname = get_attrnames(attrnames)[-1] 650 access_location = (location, None, attrnames, 0) 651 self.add_usage_term(access_location, [attrname]) 652 653 def add_usage(self, assignments, path): 654 655 """ 656 Collect usage from the given 'assignments', adding 'path' details to 657 each record if specified. Add the usage to an index mapping to location 658 information, as well as to an index mapping locations to usages. 659 """ 660 661 for name, versions in assignments.items(): 662 for i, usages in enumerate(versions): 663 location = (path, name, None, i) 664 665 for attrnames in usages: 666 self.add_usage_term(location, attrnames) 667 668 def add_usage_term(self, location, attrnames): 669 670 """ 671 For 'location' and using 'attrnames' as a description of usage, record 672 in the usage index a mapping from the usage to 'location', and record in 673 the location index a mapping from 'location' to the usage. 674 """ 675 676 key = make_key(attrnames) 677 678 init_item(self.location_index, location, set) 679 self.location_index[location].add(key) 680 681 def init_accessors(self): 682 683 "Create indexes for module and function accessor information." 684 685 for module in self.importer.get_modules(): 686 for path, all_accesses in module.attr_accessors.items(): 687 self.add_accessors(all_accesses, path) 688 689 def add_accessors(self, all_accesses, path): 690 691 """ 692 For attribute accesses described by the mapping of 'all_accesses' from 693 name details to accessor details, record the locations of the accessors 694 for each access. 695 """ 696 697 # Get details for each access combining the given name and attribute. 698 699 for (name, attrnames), accesses in all_accesses.items(): 700 701 # Obtain the usage details using the access information. 702 703 for access_number, versions in enumerate(accesses): 704 access_location = (path, name, attrnames, access_number) 705 locations = [] 706 707 for version in versions: 708 location = (path, name, None, version) 709 locations.append(location) 710 711 self.access_index[access_location] = locations 712 713 def get_accessors_for_access(self, access_location): 714 715 "Find a definition providing accessor details, if necessary." 716 717 try: 718 return self.access_index[access_location] 719 except KeyError: 720 return [access_location] 721 722 def init_accesses(self): 723 724 """ 725 Initialise collections for accesses involving assignments. 726 """ 727 728 # For each scope, obtain access details. 729 730 for path, all_accesses in self.importer.all_attr_access_modifiers.items(): 731 732 # For each combination of name and attribute names, obtain 733 # applicable modifiers. 734 735 for (name, attrnames), modifiers in all_accesses.items(): 736 737 # For each access, determine the name versions affected by 738 # assignments. 739 740 for access_number, assignment in enumerate(modifiers): 741 if name: 742 access_location = (path, name, attrnames, access_number) 743 else: 744 access_location = (path, None, attrnames, 0) 745 746 # Associate assignments with usage. 747 748 accessor_locations = self.get_accessors_for_access(access_location) 749 750 for location in accessor_locations: 751 for usage in self.location_index[location]: 752 key = make_key(usage) 753 754 if assignment: 755 init_item(self.assigned_attrs, key, set) 756 self.assigned_attrs[key].add((path, name, attrnames)) 757 758 def init_aliases(self): 759 760 "Expand aliases so that alias-based accesses can be resolved." 761 762 # Get aliased names with details of their accesses. 763 764 for name_path, all_aliases in self.importer.all_aliased_names.items(): 765 path, name = name_path.rsplit(".", 1) 766 767 # For each version of the name, obtain the access location. 768 769 for version, (original_name, attrnames, access_number) in all_aliases.items(): 770 accessor_location = (path, name, None, version) 771 access_location = (path, original_name, attrnames, access_number) 772 init_item(self.alias_index, accessor_location, list) 773 self.alias_index[accessor_location].append(access_location) 774 775 # Get aliases in terms of non-aliases and accesses. 776 777 for accessor_location, access_locations in self.alias_index.items(): 778 self.update_aliases(accessor_location, access_locations) 779 780 def update_aliases(self, accessor_location, access_locations, visited=None): 781 782 """ 783 Update the given 'accessor_location' defining an alias, update 784 'access_locations' to refer to non-aliases, following name references 785 via the access index. 786 787 If 'visited' is specified, it contains a set of accessor locations (and 788 thus keys to the alias index) that are currently being defined. 789 """ 790 791 if visited is None: 792 visited = set() 793 794 updated_locations = set() 795 796 for access_location in access_locations: 797 (path, original_name, attrnames, access_number) = access_location 798 799 # Where an alias refers to a name access, obtain the original name 800 # version details. 801 802 if attrnames is None: 803 804 # For each name version, attempt to determine any accesses that 805 # initialise the name. 806 807 for name_accessor_location in self.access_index[access_location]: 808 809 # Already-visited aliases do not contribute details. 810 811 if name_accessor_location in visited: 812 continue 813 814 visited.add(name_accessor_location) 815 816 name_access_locations = self.alias_index.get(name_accessor_location) 817 if name_access_locations: 818 updated_locations.update(self.update_aliases(name_accessor_location, name_access_locations, visited)) 819 else: 820 updated_locations.add(name_accessor_location) 821 822 # Otherwise, record the access details. 823 824 else: 825 updated_locations.add(access_location) 826 827 self.alias_index[accessor_location] = updated_locations 828 return updated_locations 829 830 # Attribute mutation for types. 831 832 def modify_mutated_attributes(self): 833 834 "Identify known, mutated attributes and change their state." 835 836 # Usage-based accesses. 837 838 for usage, all_attrnames in self.assigned_attrs.items(): 839 if not usage: 840 continue 841 842 for path, name, attrnames in all_attrnames: 843 class_types = self.get_class_types_for_usage(usage) 844 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 845 module_types = self.get_module_types_for_usage(usage) 846 847 # Detect self usage within methods in order to narrow the scope 848 # of the mutation. 849 850 t = name == "self" and self.constrain_self_reference(path, class_types, only_instance_types) 851 if t: 852 class_types, only_instance_types, module_types, constrained = t 853 objects = set(class_types).union(only_instance_types).union(module_types) 854 855 self.mutate_attribute(objects, attrnames) 856 857 def mutate_attribute(self, objects, attrnames): 858 859 "Mutate static 'objects' with the given 'attrnames'." 860 861 for name in objects: 862 attr = "%s.%s" % (name, attrnames) 863 value = self.importer.get_object(attr) 864 865 # If the value is None, the attribute is 866 # inherited and need not be set explicitly on 867 # the class concerned. 868 869 if value: 870 self.modified_attributes[attr] = value 871 self.importer.set_object(attr, value.as_var()) 872 873 # Simplification of types. 874 875 def get_most_general_types(self, class_types): 876 877 "Return the most general types for the given 'class_types'." 878 879 class_types = set(class_types) 880 to_remove = set() 881 882 for class_type in class_types: 883 for base in self.importer.classes[class_type]: 884 base = base.get_origin() 885 descendants = self.descendants[base] 886 if base in class_types and descendants.issubset(class_types): 887 to_remove.update(descendants) 888 889 class_types.difference_update(to_remove) 890 return class_types 891 892 def get_most_general_module_types(self, module_types): 893 894 "Return the most general type for the given 'module_types'." 895 896 # Where all modules are provided, an object would provide the same 897 # attributes. 898 899 if len(module_types) == len(self.importer.modules): 900 return ["__builtins__.object"] 901 else: 902 return module_types 903 904 # Type deduction for usage. 905 906 def get_types_for_usage(self, attrnames, objects): 907 908 """ 909 Identify the types that can support the given 'attrnames', using the 910 given 'objects' as the catalogue of type details. 911 """ 912 913 types = [] 914 for name, _attrnames in objects.items(): 915 if set(attrnames).issubset(_attrnames): 916 types.append(name) 917 return types 918 919 # More efficient usage-to-type indexing and retrieval. 920 921 def init_attr_type_indexes(self): 922 923 "Identify the types that can support each attribute name." 924 925 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 926 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs) 927 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 928 929 def _init_attr_type_index(self, attr_types, attrs): 930 931 """ 932 Initialise the 'attr_types' attribute-to-types mapping using the given 933 'attrs' type-to-attributes mapping. 934 """ 935 936 for name, attrnames in attrs.items(): 937 for attrname in attrnames: 938 init_item(attr_types, attrname, set) 939 attr_types[attrname].add(name) 940 941 def get_class_types_for_usage(self, attrnames): 942 943 "Return names of classes supporting the given 'attrnames'." 944 945 return self._get_types_for_usage(attrnames, self.attr_class_types, self.importer.all_class_attrs) 946 947 def get_instance_types_for_usage(self, attrnames): 948 949 """ 950 Return names of classes whose instances support the given 'attrnames' 951 (as either class or instance attributes). 952 """ 953 954 return self._get_types_for_usage(attrnames, self.attr_instance_types, self.importer.all_combined_attrs) 955 956 def get_module_types_for_usage(self, attrnames): 957 958 "Return names of modules supporting the given 'attrnames'." 959 960 return self._get_types_for_usage(attrnames, self.attr_module_types, self.importer.all_module_attrs) 961 962 def _get_types_for_usage(self, attrnames, attr_types, attrs): 963 964 """ 965 For the given 'attrnames' representing attribute usage, return types 966 recorded in the 'attr_types' attribute-to-types mapping that support 967 such usage, with the given 'attrs' type-to-attributes mapping used to 968 quickly assess whether a type supports all of the stated attributes. 969 """ 970 971 # Where no attributes are used, any type would be acceptable. 972 973 if not attrnames: 974 return attrs.keys() 975 976 types = [] 977 978 # Obtain types supporting the first attribute name... 979 980 for name in attr_types.get(attrnames[0]) or []: 981 982 # Record types that support all of the other attributes as well. 983 984 _attrnames = attrs[name] 985 if set(attrnames).issubset(_attrnames): 986 types.append(name) 987 988 return types 989 990 # Reference identification. 991 992 def identify_references(self): 993 994 "Identify references using usage and name reference information." 995 996 # Names with associated attribute usage. 997 998 for location, usages in self.location_index.items(): 999 1000 # Obtain attribute usage associated with a name, deducing the nature 1001 # of the name. Obtain types only for branches involving attribute 1002 # usage. (In the absence of usage, any type could be involved, but 1003 # then no accesses exist to require knowledge of the type.) 1004 1005 have_usage = False 1006 have_no_usage_branch = False 1007 1008 for usage in usages: 1009 if not usage: 1010 have_no_usage_branch = True 1011 continue 1012 elif not have_usage: 1013 self.init_definition_details(location) 1014 have_usage = True 1015 self.record_types_for_usage(location, usage) 1016 1017 # Where some usage occurs, but where branches without usage also 1018 # occur, record the types for those branches anyway. 1019 1020 if have_usage and have_no_usage_branch: 1021 self.init_definition_details(location) 1022 self.record_types_for_usage(location, None) 1023 1024 # Specific name-based attribute accesses. 1025 1026 alias_accesses = set() 1027 1028 for access_location, accessor_locations in self.access_index.items(): 1029 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1030 1031 # Aliased name definitions. All aliases with usage will have been 1032 # defined, but they may be refined according to referenced accesses. 1033 1034 for accessor_location in self.alias_index.keys(): 1035 self.record_types_for_alias(accessor_location) 1036 1037 # Update accesses employing aliases. 1038 1039 for access_location in alias_accesses: 1040 self.record_types_for_access(access_location, self.access_index[access_location]) 1041 1042 # Anonymous references with attribute chains. 1043 1044 for location, accesses in self.importer.all_attr_accesses.items(): 1045 1046 # Get distinct attribute names. 1047 1048 all_attrnames = set() 1049 1050 for attrnames in accesses: 1051 all_attrnames.update(get_attrnames(attrnames)) 1052 1053 # Get attribute and accessor details for each attribute name. 1054 1055 for attrname in all_attrnames: 1056 access_location = (location, None, attrname, 0) 1057 self.record_types_for_attribute(access_location, attrname) 1058 1059 # References via constant/identified objects. 1060 1061 for location, name_accesses in self.importer.all_const_accesses.items(): 1062 1063 # A mapping from the original name and attributes to resolved access 1064 # details. 1065 1066 for original_access, access in name_accesses.items(): 1067 original_name, original_attrnames = original_access 1068 objpath, ref, attrnames = access 1069 1070 # Build an accessor combining the name and attribute names used. 1071 1072 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1073 1074 # Direct accesses to attributes. 1075 1076 if not attrnames: 1077 1078 # Build a descriptive location based on the original 1079 # details, exposing the final attribute name. 1080 1081 oa, attrname = original_accessor[:-1], original_accessor[-1] 1082 oa = ".".join(oa) 1083 1084 access_location = (location, oa, attrname, 0) 1085 accessor_location = (location, oa, None, 0) 1086 self.access_index[access_location] = [accessor_location] 1087 1088 self.init_access_details(access_location) 1089 self.init_definition_details(accessor_location) 1090 1091 # Obtain a reference for the accessor in order to properly 1092 # determine its type. 1093 1094 if ref.get_kind() != "<instance>": 1095 objpath = ref.get_origin() 1096 1097 objpath = objpath.rsplit(".", 1)[0] 1098 1099 # Where the object name conflicts with the module 1100 # providing it, obtain the module details. 1101 1102 if objpath in self.importer.modules: 1103 accessor = Reference("<module>", objpath) 1104 else: 1105 accessor = self.importer.get_object(objpath) 1106 1107 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1108 self.access_constrained.add(access_location) 1109 1110 class_types, instance_types, module_types = accessor.get_types() 1111 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1112 continue 1113 1114 # Build a descriptive location based on the original 1115 # details, employing the first remaining attribute name. 1116 1117 l = get_attrnames(attrnames) 1118 attrname = l[0] 1119 1120 oa = original_accessor[:-len(l)] 1121 oa = ".".join(oa) 1122 1123 access_location = (location, oa, attrnames, 0) 1124 accessor_location = (location, oa, None, 0) 1125 self.access_index[access_location] = [accessor_location] 1126 1127 self.init_access_details(access_location) 1128 self.init_definition_details(accessor_location) 1129 1130 class_types, instance_types, module_types = ref.get_types() 1131 1132 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1133 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1134 1135 def constrain_types(self, path, class_types, instance_types, module_types): 1136 1137 """ 1138 Using the given 'path' to an object, constrain the given 'class_types', 1139 'instance_types' and 'module_types'. 1140 1141 Return the class, instance, module types plus whether the types are 1142 constrained to a specific kind of type. 1143 """ 1144 1145 ref = self.importer.identify(path) 1146 if ref: 1147 1148 # Constrain usage suggestions using the identified object. 1149 1150 if ref.has_kind("<class>"): 1151 return ( 1152 set(class_types).intersection([ref.get_origin()]), [], [], True 1153 ) 1154 elif ref.has_kind("<module>"): 1155 return ( 1156 [], [], set(module_types).intersection([ref.get_origin()]), True 1157 ) 1158 1159 return class_types, instance_types, module_types, False 1160 1161 def get_target_types(self, location, usage): 1162 1163 """ 1164 Return the class, instance and module types constrained for the name at 1165 the given 'location' exhibiting the given 'usage'. Whether the types 1166 have been constrained using contextual information is also indicated, 1167 plus whether the types have been constrained to a specific kind of type. 1168 """ 1169 1170 unit_path, name, attrnames, version = location 1171 1172 # Detect any initialised name for the location. 1173 1174 if name: 1175 ref = self.get_initialised_name(location) 1176 if ref: 1177 (class_types, only_instance_types, module_types, 1178 _function_types, _var_types) = separate_types([ref]) 1179 return class_types, only_instance_types, module_types, True, False 1180 1181 # Retrieve the recorded types for the usage. 1182 1183 class_types = self.get_class_types_for_usage(usage) 1184 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1185 module_types = self.get_module_types_for_usage(usage) 1186 1187 # Merge usage deductions with observations to obtain reference types 1188 # for names involved with attribute accesses. 1189 1190 if not name: 1191 return class_types, only_instance_types, module_types, False, False 1192 1193 # Obtain references to known objects. 1194 1195 path = self.get_name_path(unit_path, name) 1196 1197 class_types, only_instance_types, module_types, constrained_specific = \ 1198 self.constrain_types(path, class_types, only_instance_types, module_types) 1199 1200 if constrained_specific: 1201 return class_types, only_instance_types, module_types, constrained_specific, constrained_specific 1202 1203 # Constrain "self" references. 1204 1205 if name == "self": 1206 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1207 if t: 1208 class_types, only_instance_types, module_types, constrained = t 1209 return class_types, only_instance_types, module_types, constrained, False 1210 1211 return class_types, only_instance_types, module_types, False, False 1212 1213 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1214 1215 """ 1216 Where the name "self" appears in a method, attempt to constrain the 1217 classes involved. 1218 1219 Return the class, instance, module types plus whether the types are 1220 constrained. 1221 """ 1222 1223 class_name = self.in_method(unit_path) 1224 1225 if not class_name: 1226 return None 1227 1228 classes = set([class_name]) 1229 classes.update(self.get_descendants_for_class(class_name)) 1230 1231 # Note that only instances will be expected for these references but 1232 # either classes or instances may provide the attributes. 1233 1234 return ( 1235 set(class_types).intersection(classes), 1236 set(only_instance_types).intersection(classes), 1237 [], True 1238 ) 1239 1240 def in_method(self, path): 1241 1242 "Return whether 'path' refers to a method." 1243 1244 class_name, method_name = path.rsplit(".", 1) 1245 return self.importer.classes.has_key(class_name) and class_name 1246 1247 def init_reference_details(self, location): 1248 1249 "Initialise reference-related details for 'location'." 1250 1251 self.init_definition_details(location) 1252 self.init_access_details(location) 1253 1254 def init_definition_details(self, location): 1255 1256 "Initialise name definition details for 'location'." 1257 1258 self.accessor_class_types[location] = set() 1259 self.accessor_instance_types[location] = set() 1260 self.accessor_module_types[location] = set() 1261 self.provider_class_types[location] = set() 1262 self.provider_instance_types[location] = set() 1263 self.provider_module_types[location] = set() 1264 1265 def init_access_details(self, location): 1266 1267 "Initialise access details at 'location'." 1268 1269 self.referenced_attrs[location] = {} 1270 1271 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1272 1273 """ 1274 Define types for the 'access_location' associated with the given 1275 'accessor_locations'. 1276 """ 1277 1278 path, name, attrnames, version = access_location 1279 if not attrnames: 1280 return 1281 1282 attrname = get_attrnames(attrnames)[0] 1283 1284 # Collect all suggested types for the accessors. Accesses may 1285 # require accessors from of a subset of the complete set of types. 1286 1287 class_types = set() 1288 module_types = set() 1289 instance_types = set() 1290 1291 constrained = True 1292 1293 for location in accessor_locations: 1294 1295 # Remember accesses employing aliases. 1296 1297 if alias_accesses is not None and self.alias_index.has_key(location): 1298 alias_accesses.add(access_location) 1299 1300 # Use the type information deduced for names from above. 1301 1302 if self.accessor_class_types.has_key(location): 1303 class_types.update(self.accessor_class_types[location]) 1304 module_types.update(self.accessor_module_types[location]) 1305 instance_types.update(self.accessor_instance_types[location]) 1306 1307 # Where accesses are associated with assignments but where no 1308 # attribute usage observations have caused such an association, 1309 # the attribute name is considered by itself. 1310 1311 else: 1312 self.init_definition_details(location) 1313 self.record_types_for_usage(location, [attrname]) 1314 1315 constrained = location in self.reference_constrained and constrained 1316 1317 self.init_access_details(access_location) 1318 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1319 1320 def record_types_for_usage(self, accessor_location, usage): 1321 1322 """ 1323 Record types for the given 'accessor_location' according to the given 1324 'usage' observations which may be None to indicate an absence of usage. 1325 """ 1326 1327 (class_types, 1328 instance_types, 1329 module_types, 1330 constrained, 1331 constrained_specific) = self.get_target_types(accessor_location, usage) 1332 1333 self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific) 1334 1335 def record_types_for_attribute(self, access_location, attrname): 1336 1337 """ 1338 Record types for the 'access_location' employing only the given 1339 'attrname' for type deduction. 1340 """ 1341 1342 usage = [attrname] 1343 1344 class_types = self.get_class_types_for_usage(usage) 1345 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1346 module_types = self.get_module_types_for_usage(usage) 1347 1348 self.init_reference_details(access_location) 1349 1350 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1351 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1352 1353 def record_types_for_alias(self, accessor_location): 1354 1355 """ 1356 Define types for the 'accessor_location' not having associated usage. 1357 """ 1358 1359 have_access = self.provider_class_types.has_key(accessor_location) 1360 1361 # With an access, attempt to narrow the existing selection of provider 1362 # types. 1363 1364 if have_access: 1365 provider_class_types = self.provider_class_types[accessor_location] 1366 provider_instance_types = self.provider_instance_types[accessor_location] 1367 provider_module_types = self.provider_module_types[accessor_location] 1368 1369 # Find details for any corresponding access. 1370 1371 all_class_types = set() 1372 all_instance_types = set() 1373 all_module_types = set() 1374 1375 for access_location in self.alias_index[accessor_location]: 1376 location, name, attrnames, access_number = access_location 1377 1378 # Alias references an attribute access. 1379 1380 if attrnames: 1381 1382 # Obtain attribute references for the access. 1383 1384 attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]] 1385 1386 # Separate the different attribute types. 1387 1388 (class_types, instance_types, module_types, 1389 function_types, var_types) = separate_types(attrs) 1390 1391 # Where non-accessor types are found, do not attempt to refine 1392 # the defined accessor types. 1393 1394 if function_types or var_types: 1395 return 1396 1397 class_types = set(provider_class_types).intersection(class_types) 1398 instance_types = set(provider_instance_types).intersection(instance_types) 1399 module_types = set(provider_module_types).intersection(module_types) 1400 1401 # Alias references a name, not an access. 1402 1403 else: 1404 # Attempt to refine the types using initialised names. 1405 1406 attr = self.get_initialised_name(access_location) 1407 if attr: 1408 (class_types, instance_types, module_types, 1409 _function_types, _var_types) = separate_types([attr]) 1410 1411 # Where no further information is found, do not attempt to 1412 # refine the defined accessor types. 1413 1414 else: 1415 return 1416 1417 all_class_types.update(class_types) 1418 all_instance_types.update(instance_types) 1419 all_module_types.update(module_types) 1420 1421 # Record refined type details for the alias as an accessor. 1422 1423 self.init_definition_details(accessor_location) 1424 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1425 1426 # Without an access, attempt to identify references for the alias. 1427 1428 else: 1429 refs = set() 1430 1431 for access_location in self.alias_index[accessor_location]: 1432 location, name, attrnames, access_number = access_location 1433 1434 # Alias references an attribute access. 1435 1436 if attrnames: 1437 attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]] 1438 refs.update(attrs) 1439 1440 # Alias references a name, not an access. 1441 1442 else: 1443 attr = self.get_initialised_name(access_location) 1444 attrs = attr and [attr] or [] 1445 if not attrs and self.provider_class_types.has_key(access_location): 1446 class_types = self.provider_class_types[access_location] 1447 instance_types = self.provider_instance_types[access_location] 1448 module_types = self.provider_module_types[access_location] 1449 attrs = combine_types(class_types, instance_types, module_types) 1450 if attrs: 1451 refs.update(attrs) 1452 1453 # Record reference details for the alias separately from accessors. 1454 1455 self.referenced_objects[accessor_location] = refs 1456 1457 def get_initialised_name(self, access_location): 1458 1459 """ 1460 Return references for any initialised names at 'access_location', or 1461 None if no such references exist. 1462 """ 1463 1464 location, name, attrnames, version = access_location 1465 path = self.get_name_path(location, name) 1466 1467 # Use initialiser information, if available. 1468 1469 refs = self.importer.all_initialised_names.get(path) 1470 if refs and refs.has_key(version): 1471 return refs[version] 1472 else: 1473 return None 1474 1475 def get_name_path(self, path, name): 1476 1477 "Return a suitable qualified name from the given 'path' and 'name'." 1478 1479 if "." in name: 1480 return name 1481 else: 1482 return "%s.%s" % (path, name) 1483 1484 def record_reference_types(self, location, class_types, instance_types, 1485 module_types, constrained, constrained_specific=False): 1486 1487 """ 1488 Associate attribute provider types with the given 'location', consisting 1489 of the given 'class_types', 'instance_types' and 'module_types'. 1490 1491 If 'constrained' is indicated, the constrained nature of the accessor is 1492 recorded for the location. 1493 1494 If 'constrained_specific' is indicated using a true value, instance types 1495 will not be added to class types to permit access via instances at the 1496 given location. This is only useful where a specific accessor is known 1497 to be a class. 1498 1499 Note that the specified types only indicate the provider types for 1500 attributes, whereas the recorded accessor types indicate the possible 1501 types of the actual objects used to access attributes. 1502 """ 1503 1504 # Update the type details for the location. 1505 1506 self.provider_class_types[location].update(class_types) 1507 self.provider_instance_types[location].update(instance_types) 1508 self.provider_module_types[location].update(module_types) 1509 1510 # Class types support classes and instances as accessors. 1511 # Instance-only and module types support only their own kinds as 1512 # accessors. 1513 1514 # However, the nature of accessors can be further determined. 1515 # Any self variable may only refer to an instance. 1516 1517 path, name, version, attrnames = location 1518 if name != "self" or not self.in_method(path): 1519 self.accessor_class_types[location].update(class_types) 1520 1521 if not constrained_specific: 1522 self.accessor_instance_types[location].update(class_types) 1523 1524 self.accessor_instance_types[location].update(instance_types) 1525 1526 if name != "self" or not self.in_method(path): 1527 self.accessor_module_types[location].update(module_types) 1528 1529 if constrained: 1530 self.reference_constrained.add(location) 1531 1532 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1533 1534 """ 1535 Identify reference attributes, associating them with the given 1536 'location', identifying the given 'attrname', employing the given 1537 'class_types', 'instance_types' and 'module_types'. 1538 1539 If 'constrained' is indicated, the constrained nature of the access is 1540 recorded for the location. 1541 """ 1542 1543 # Record the referenced objects. 1544 1545 self.referenced_attrs[location] = \ 1546 self._identify_reference_attribute(attrname, class_types, instance_types, module_types) 1547 1548 if constrained: 1549 self.access_constrained.add(location) 1550 1551 def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types): 1552 1553 """ 1554 Identify the reference attribute with the given 'attrname', employing 1555 the given 'class_types', 'instance_types' and 'module_types'. 1556 """ 1557 1558 attrs = set() 1559 1560 # The class types expose class attributes either directly or via 1561 # instances. 1562 1563 for object_type in class_types: 1564 ref = self.importer.get_class_attribute(object_type, attrname) 1565 if ref: 1566 attrs.add(("<class>", object_type, ref)) 1567 1568 # Add any distinct instance attributes that would be provided 1569 # by instances also providing indirect class attribute access. 1570 1571 for ref in self.importer.get_instance_attributes(object_type, attrname): 1572 attrs.add(("<instance>", object_type, ref)) 1573 1574 # The instance-only types expose instance attributes, but although 1575 # classes are excluded as potential accessors (since they do not provide 1576 # the instance attributes), the class types may still provide some 1577 # attributes. 1578 1579 for object_type in instance_types: 1580 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1581 1582 if instance_attrs: 1583 for ref in instance_attrs: 1584 attrs.add(("<instance>", object_type, ref)) 1585 else: 1586 ref = self.importer.get_class_attribute(object_type, attrname) 1587 if ref: 1588 attrs.add(("<class>", object_type, ref)) 1589 1590 # Module types expose module attributes for module accessors. 1591 1592 for object_type in module_types: 1593 ref = self.importer.get_module_attribute(object_type, attrname) 1594 if ref: 1595 attrs.add(("<module>", object_type, ref)) 1596 1597 return attrs 1598 1599 # vim: tabstop=4 expandtab shiftwidth=4