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