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