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