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