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 # Obtain basic properties of the types involved in the access. 499 500 single_accessor_type = len(all_accessor_types) == 1 501 single_accessor_class_type = is_single_class_type(all_accessor_types) 502 single_accessor_general_type = len(all_accessor_general_types) == 1 503 single_accessor_general_class_type = is_single_class_type(all_accessor_general_types) 504 505 # Determine whether the attribute access is guarded or not. 506 507 guarded = ( 508 single_accessor_type or single_accessor_class_type or 509 single_accessor_general_type or single_accessor_general_class_type 510 ) 511 512 if guarded: 513 (guard_class_types, guard_instance_types, guard_module_types, 514 _function_types, _var_types) = separate_types(all_provider_types) 515 516 self.reference_all_accessor_types[location] = all_accessor_types 517 self.reference_all_accessor_general_types[location] = all_accessor_general_types 518 519 # Attribute information, both name-based and anonymous. 520 521 referenced_attrs = self.referenced_attrs[location] 522 523 if referenced_attrs: 524 525 # Record attribute information for each name used on the 526 # accessor. 527 528 attrname = get_attrname_from_location(location) 529 530 all_accessed_attrs = set() 531 all_providers = set() 532 533 # Obtain provider and attribute details for this kind of 534 # object. 535 536 for attrtype, object_type, attr in referenced_attrs: 537 all_accessed_attrs.add(attr) 538 all_providers.add(object_type) 539 540 all_general_providers = self.get_most_general_types(all_providers) 541 542 # Determine which attributes would be provided by the 543 # accessor types upheld by a guard. 544 545 if guarded: 546 guard_attrs = set() 547 for _attrtype, object_type, attr in \ 548 self._identify_reference_attribute(attrname, guard_class_types, guard_instance_types, guard_module_types): 549 guard_attrs.add(attr) 550 else: 551 guard_attrs = None 552 553 self.reference_all_attrs[location] = all_accessed_attrs 554 555 # Constrained accesses guarantee the nature of the accessor. 556 # However, there may still be many types involved. 557 558 if constrained: 559 if single_accessor_type: 560 self.reference_test_types[location] = test_for_type("constrained-specific", first(all_accessor_types)) 561 elif single_accessor_class_type: 562 self.reference_test_types[location] = "constrained-specific-object" 563 elif single_accessor_general_type: 564 self.reference_test_types[location] = test_for_type("constrained-common", first(all_accessor_general_types)) 565 elif single_accessor_general_class_type: 566 self.reference_test_types[location] = "constrained-common-object" 567 else: 568 self.reference_test_types[location] = "constrained-many" 569 570 # Suitably guarded accesses, where the nature of the 571 # accessor can be guaranteed, do not require the attribute 572 # involved to be validated. Otherwise, for unguarded 573 # accesses, access-level tests are required. 574 575 elif guarded and all_accessed_attrs.issubset(guard_attrs): 576 if single_accessor_type: 577 self.reference_test_types[location] = test_for_type("guarded-specific", first(all_accessor_types)) 578 elif single_accessor_class_type: 579 self.reference_test_types[location] = "guarded-specific-object" 580 elif single_accessor_general_type: 581 self.reference_test_types[location] = test_for_type("guarded-common", first(all_accessor_general_types)) 582 elif single_accessor_general_class_type: 583 self.reference_test_types[location] = "guarded-common-object" 584 585 # Record the need to test the type of anonymous and 586 # unconstrained accessors. 587 588 elif len(all_providers) == 1: 589 provider = first(all_providers) 590 if provider != '__builtins__.object': 591 all_accessor_kinds = set(get_kinds(all_accessor_types)) 592 if len(all_accessor_kinds) == 1: 593 test_type = test_for_kinds("specific", all_accessor_kinds) 594 else: 595 test_type = "specific-object" 596 self.reference_test_types[location] = test_type 597 self.reference_test_accessor_types[location] = provider 598 599 elif len(all_general_providers) == 1: 600 provider = first(all_general_providers) 601 if provider != '__builtins__.object': 602 all_accessor_kinds = set(get_kinds(all_accessor_general_types)) 603 if len(all_accessor_kinds) == 1: 604 test_type = test_for_kinds("common", all_accessor_kinds) 605 else: 606 test_type = "common-object" 607 self.reference_test_types[location] = test_type 608 self.reference_test_accessor_types[location] = provider 609 610 # Record the need to test the identity of the attribute. 611 612 else: 613 self.reference_test_types[location] = "validate" 614 615 def initialise_access_plans(self): 616 617 "Define attribute access plans." 618 619 for location in self.referenced_attrs.keys(): 620 self.access_plans[location] = self.get_access_plan(location) 621 622 def get_referenced_attrs(self, location): 623 624 """ 625 Return attributes referenced at the given access 'location' by the given 626 'attrname' as a list of (attribute type, attribute set) tuples. 627 """ 628 629 d = {} 630 for attrtype, objtype, attr in self.referenced_attrs[location]: 631 init_item(d, attrtype, set) 632 d[attrtype].add(attr) 633 l = d.items() 634 l.sort() # class, module, instance 635 return l 636 637 # Initialisation methods. 638 639 def init_descendants(self): 640 641 "Identify descendants of each class." 642 643 for name in self.importer.classes.keys(): 644 self.get_descendants_for_class(name) 645 646 def get_descendants_for_class(self, name): 647 648 """ 649 Use subclass information to deduce the descendants for the class of the 650 given 'name'. 651 """ 652 653 if not self.descendants.has_key(name): 654 descendants = set() 655 656 for subclass in self.importer.subclasses[name]: 657 descendants.update(self.get_descendants_for_class(subclass)) 658 descendants.add(subclass) 659 660 self.descendants[name] = descendants 661 662 return self.descendants[name] 663 664 def init_special_attributes(self): 665 666 "Add special attributes to the classes for inheritance-related tests." 667 668 all_class_attrs = self.importer.all_class_attrs 669 670 for name, descendants in self.descendants.items(): 671 for descendant in descendants: 672 all_class_attrs[descendant]["#%s" % name] = name 673 674 for name in all_class_attrs.keys(): 675 all_class_attrs[name]["#%s" % name] = name 676 677 def init_usage_index(self): 678 679 """ 680 Create indexes for module and function attribute usage and for anonymous 681 accesses. 682 """ 683 684 for module in self.importer.get_modules(): 685 for path, assignments in module.attr_usage.items(): 686 self.add_usage(assignments, path) 687 688 for location, all_attrnames in self.importer.all_attr_accesses.items(): 689 for attrnames in all_attrnames: 690 attrname = get_attrnames(attrnames)[-1] 691 access_location = (location, None, attrnames, 0) 692 self.add_usage_term(access_location, [attrname]) 693 694 def add_usage(self, assignments, path): 695 696 """ 697 Collect usage from the given 'assignments', adding 'path' details to 698 each record if specified. Add the usage to an index mapping to location 699 information, as well as to an index mapping locations to usages. 700 """ 701 702 for name, versions in assignments.items(): 703 for i, usages in enumerate(versions): 704 location = (path, name, None, i) 705 706 for attrnames in usages: 707 self.add_usage_term(location, attrnames) 708 709 def add_usage_term(self, location, attrnames): 710 711 """ 712 For 'location' and using 'attrnames' as a description of usage, record 713 in the usage index a mapping from the usage to 'location', and record in 714 the location index a mapping from 'location' to the usage. 715 """ 716 717 key = make_key(attrnames) 718 719 init_item(self.location_index, location, set) 720 self.location_index[location].add(key) 721 722 def init_accessors(self): 723 724 "Create indexes for module and function accessor information." 725 726 for module in self.importer.get_modules(): 727 for path, all_accesses in module.attr_accessors.items(): 728 self.add_accessors(all_accesses, path) 729 730 def add_accessors(self, all_accesses, path): 731 732 """ 733 For attribute accesses described by the mapping of 'all_accesses' from 734 name details to accessor details, record the locations of the accessors 735 for each access. 736 """ 737 738 # Get details for each access combining the given name and attribute. 739 740 for (name, attrnames), accesses in all_accesses.items(): 741 742 # Obtain the usage details using the access information. 743 744 for access_number, versions in enumerate(accesses): 745 access_location = (path, name, attrnames, access_number) 746 locations = [] 747 748 for version in versions: 749 location = (path, name, None, version) 750 locations.append(location) 751 752 self.access_index[access_location] = locations 753 754 def get_accessors_for_access(self, access_location): 755 756 "Find a definition providing accessor details, if necessary." 757 758 try: 759 return self.access_index[access_location] 760 except KeyError: 761 return [access_location] 762 763 def init_accesses(self): 764 765 """ 766 Initialise collections for accesses involving assignments. 767 """ 768 769 # For each scope, obtain access details. 770 771 for path, all_accesses in self.importer.all_attr_access_modifiers.items(): 772 773 # For each combination of name and attribute names, obtain 774 # applicable modifiers. 775 776 for (name, attrnames), modifiers in all_accesses.items(): 777 778 # For each access, determine the name versions affected by 779 # assignments. 780 781 for access_number, assignment in enumerate(modifiers): 782 if name: 783 access_location = (path, name, attrnames, access_number) 784 else: 785 access_location = (path, None, attrnames, 0) 786 787 # Associate assignments with usage. 788 789 accessor_locations = self.get_accessors_for_access(access_location) 790 791 for location in accessor_locations: 792 for usage in self.location_index[location]: 793 key = make_key(usage) 794 795 if assignment: 796 init_item(self.assigned_attrs, key, set) 797 self.assigned_attrs[key].add((path, name, attrnames)) 798 799 def init_aliases(self): 800 801 "Expand aliases so that alias-based accesses can be resolved." 802 803 # Get aliased names with details of their accesses. 804 805 for name_path, all_aliases in self.importer.all_aliased_names.items(): 806 path, name = name_path.rsplit(".", 1) 807 808 # For each version of the name, obtain the access location. 809 810 for version, (original_name, attrnames, access_number) in all_aliases.items(): 811 accessor_location = (path, name, None, version) 812 access_location = (path, original_name, attrnames, access_number) 813 init_item(self.alias_index, accessor_location, list) 814 self.alias_index[accessor_location].append(access_location) 815 816 # Get aliases in terms of non-aliases and accesses. 817 818 for accessor_location, access_locations in self.alias_index.items(): 819 self.update_aliases(accessor_location, access_locations) 820 821 def update_aliases(self, accessor_location, access_locations, visited=None): 822 823 """ 824 Update the given 'accessor_location' defining an alias, update 825 'access_locations' to refer to non-aliases, following name references 826 via the access index. 827 828 If 'visited' is specified, it contains a set of accessor locations (and 829 thus keys to the alias index) that are currently being defined. 830 """ 831 832 if visited is None: 833 visited = set() 834 835 updated_locations = set() 836 837 for access_location in access_locations: 838 (path, original_name, attrnames, access_number) = access_location 839 840 # Where an alias refers to a name access, obtain the original name 841 # version details. 842 843 if attrnames is None: 844 845 # For each name version, attempt to determine any accesses that 846 # initialise the name. 847 848 for name_accessor_location in self.access_index[access_location]: 849 850 # Already-visited aliases do not contribute details. 851 852 if name_accessor_location in visited: 853 continue 854 855 visited.add(name_accessor_location) 856 857 name_access_locations = self.alias_index.get(name_accessor_location) 858 if name_access_locations: 859 updated_locations.update(self.update_aliases(name_accessor_location, name_access_locations, visited)) 860 else: 861 updated_locations.add(name_accessor_location) 862 863 # Otherwise, record the access details. 864 865 else: 866 updated_locations.add(access_location) 867 868 self.alias_index[accessor_location] = updated_locations 869 return updated_locations 870 871 # Attribute mutation for types. 872 873 def modify_mutated_attributes(self): 874 875 "Identify known, mutated attributes and change their state." 876 877 # Usage-based accesses. 878 879 for usage, all_attrnames in self.assigned_attrs.items(): 880 if not usage: 881 continue 882 883 for path, name, attrnames in all_attrnames: 884 class_types = self.get_class_types_for_usage(usage) 885 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 886 module_types = self.get_module_types_for_usage(usage) 887 888 # Detect self usage within methods in order to narrow the scope 889 # of the mutation. 890 891 t = name == "self" and self.constrain_self_reference(path, class_types, only_instance_types) 892 if t: 893 class_types, only_instance_types, module_types, constrained = t 894 objects = set(class_types).union(only_instance_types).union(module_types) 895 896 self.mutate_attribute(objects, attrnames) 897 898 def mutate_attribute(self, objects, attrnames): 899 900 "Mutate static 'objects' with the given 'attrnames'." 901 902 for name in objects: 903 attr = "%s.%s" % (name, attrnames) 904 value = self.importer.get_object(attr) 905 906 # If the value is None, the attribute is 907 # inherited and need not be set explicitly on 908 # the class concerned. 909 910 if value: 911 self.modified_attributes[attr] = value 912 self.importer.set_object(attr, value.as_var()) 913 914 # Simplification of types. 915 916 def get_most_general_types(self, types): 917 918 "Return the most general types for the given 'types'." 919 920 module_types = set() 921 class_types = set() 922 923 for type in types: 924 ref = self.importer.identify(type) 925 if ref.has_kind("<module>"): 926 module_types.add(type) 927 else: 928 class_types.add(type) 929 930 types = set(self.get_most_general_module_types(module_types)) 931 types.update(self.get_most_general_class_types(class_types)) 932 return types 933 934 def get_most_general_class_types(self, class_types): 935 936 "Return the most general types for the given 'class_types'." 937 938 class_types = set(class_types) 939 to_remove = set() 940 941 for class_type in class_types: 942 for base in self.importer.classes[class_type]: 943 base = base.get_origin() 944 descendants = self.descendants[base] 945 if base in class_types and descendants.issubset(class_types): 946 to_remove.update(descendants) 947 948 class_types.difference_update(to_remove) 949 return class_types 950 951 def get_most_general_module_types(self, module_types): 952 953 "Return the most general type for the given 'module_types'." 954 955 # Where all modules are provided, an object would provide the same 956 # attributes. 957 958 if len(module_types) == len(self.importer.modules): 959 return ["__builtins__.object"] 960 else: 961 return module_types 962 963 # Type deduction for usage. 964 965 def get_types_for_usage(self, attrnames, objects): 966 967 """ 968 Identify the types that can support the given 'attrnames', using the 969 given 'objects' as the catalogue of type details. 970 """ 971 972 types = [] 973 for name, _attrnames in objects.items(): 974 if set(attrnames).issubset(_attrnames): 975 types.append(name) 976 return types 977 978 # More efficient usage-to-type indexing and retrieval. 979 980 def init_attr_type_indexes(self): 981 982 "Identify the types that can support each attribute name." 983 984 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 985 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs) 986 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 987 988 def _init_attr_type_index(self, attr_types, attrs): 989 990 """ 991 Initialise the 'attr_types' attribute-to-types mapping using the given 992 'attrs' type-to-attributes mapping. 993 """ 994 995 for name, attrnames in attrs.items(): 996 for attrname in attrnames: 997 init_item(attr_types, attrname, set) 998 attr_types[attrname].add(name) 999 1000 def get_class_types_for_usage(self, attrnames): 1001 1002 "Return names of classes supporting the given 'attrnames'." 1003 1004 return self._get_types_for_usage(attrnames, self.attr_class_types, self.importer.all_class_attrs) 1005 1006 def get_instance_types_for_usage(self, attrnames): 1007 1008 """ 1009 Return names of classes whose instances support the given 'attrnames' 1010 (as either class or instance attributes). 1011 """ 1012 1013 return self._get_types_for_usage(attrnames, self.attr_instance_types, self.importer.all_combined_attrs) 1014 1015 def get_module_types_for_usage(self, attrnames): 1016 1017 "Return names of modules supporting the given 'attrnames'." 1018 1019 return self._get_types_for_usage(attrnames, self.attr_module_types, self.importer.all_module_attrs) 1020 1021 def _get_types_for_usage(self, attrnames, attr_types, attrs): 1022 1023 """ 1024 For the given 'attrnames' representing attribute usage, return types 1025 recorded in the 'attr_types' attribute-to-types mapping that support 1026 such usage, with the given 'attrs' type-to-attributes mapping used to 1027 quickly assess whether a type supports all of the stated attributes. 1028 """ 1029 1030 # Where no attributes are used, any type would be acceptable. 1031 1032 if not attrnames: 1033 return attrs.keys() 1034 1035 types = [] 1036 1037 # Obtain types supporting the first attribute name... 1038 1039 for name in attr_types.get(attrnames[0]) or []: 1040 1041 # Record types that support all of the other attributes as well. 1042 1043 _attrnames = attrs[name] 1044 if set(attrnames).issubset(_attrnames): 1045 types.append(name) 1046 1047 return types 1048 1049 # Reference identification. 1050 1051 def identify_references(self): 1052 1053 "Identify references using usage and name reference information." 1054 1055 # Names with associated attribute usage. 1056 1057 for location, usages in self.location_index.items(): 1058 1059 # Obtain attribute usage associated with a name, deducing the nature 1060 # of the name. Obtain types only for branches involving attribute 1061 # usage. (In the absence of usage, any type could be involved, but 1062 # then no accesses exist to require knowledge of the type.) 1063 1064 have_usage = False 1065 have_no_usage_branch = False 1066 1067 for usage in usages: 1068 if not usage: 1069 have_no_usage_branch = True 1070 continue 1071 elif not have_usage: 1072 self.init_definition_details(location) 1073 have_usage = True 1074 self.record_types_for_usage(location, usage) 1075 1076 # Where some usage occurs, but where branches without usage also 1077 # occur, record the types for those branches anyway. 1078 1079 if have_usage and have_no_usage_branch: 1080 self.init_definition_details(location) 1081 self.record_types_for_usage(location, None) 1082 1083 # Specific name-based attribute accesses. 1084 1085 alias_accesses = set() 1086 1087 for access_location, accessor_locations in self.access_index.items(): 1088 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1089 1090 # Anonymous references with attribute chains. 1091 1092 for location, accesses in self.importer.all_attr_accesses.items(): 1093 1094 # Get distinct attribute names. 1095 1096 all_attrnames = set() 1097 1098 for attrnames in accesses: 1099 all_attrnames.update(get_attrnames(attrnames)) 1100 1101 # Get attribute and accessor details for each attribute name. 1102 1103 for attrname in all_attrnames: 1104 access_location = (location, None, attrname, 0) 1105 self.record_types_for_attribute(access_location, attrname) 1106 1107 # References via constant/identified objects. 1108 1109 for location, name_accesses in self.importer.all_const_accesses.items(): 1110 1111 # A mapping from the original name and attributes to resolved access 1112 # details. 1113 1114 for original_access, access in name_accesses.items(): 1115 original_name, original_attrnames = original_access 1116 objpath, ref, attrnames = access 1117 1118 # Build an accessor combining the name and attribute names used. 1119 1120 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1121 1122 # Direct accesses to attributes. 1123 1124 if not attrnames: 1125 1126 # Build a descriptive location based on the original 1127 # details, exposing the final attribute name. 1128 1129 oa, attrname = original_accessor[:-1], original_accessor[-1] 1130 oa = ".".join(oa) 1131 1132 access_location = (location, oa, attrname, 0) 1133 accessor_location = (location, oa, None, 0) 1134 self.access_index[access_location] = [accessor_location] 1135 1136 self.init_access_details(access_location) 1137 self.init_definition_details(accessor_location) 1138 1139 # Obtain a reference for the accessor in order to properly 1140 # determine its type. 1141 1142 if ref.get_kind() != "<instance>": 1143 objpath = ref.get_origin() 1144 1145 objpath = objpath.rsplit(".", 1)[0] 1146 1147 # Where the object name conflicts with the module 1148 # providing it, obtain the module details. 1149 1150 if objpath in self.importer.modules: 1151 accessor = Reference("<module>", objpath) 1152 else: 1153 accessor = self.importer.get_object(objpath) 1154 1155 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1156 self.access_constrained.add(access_location) 1157 1158 class_types, instance_types, module_types = accessor.get_types() 1159 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1160 1161 else: 1162 1163 # Build a descriptive location based on the original 1164 # details, employing the first remaining attribute name. 1165 1166 l = get_attrnames(attrnames) 1167 attrname = l[0] 1168 1169 oa = original_accessor[:-len(l)] 1170 oa = ".".join(oa) 1171 1172 access_location = (location, oa, attrnames, 0) 1173 accessor_location = (location, oa, None, 0) 1174 self.access_index[access_location] = [accessor_location] 1175 1176 self.init_access_details(access_location) 1177 self.init_definition_details(accessor_location) 1178 1179 class_types, instance_types, module_types = ref.get_types() 1180 1181 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1182 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1183 1184 original_location = (location, original_name, original_attrnames, 0) 1185 1186 if original_location != access_location: 1187 self.const_accesses[original_location] = access_location 1188 self.const_accesses_rev[access_location] = original_location 1189 1190 # Aliased name definitions. All aliases with usage will have been 1191 # defined, but they may be refined according to referenced accesses. 1192 1193 for accessor_location in self.alias_index.keys(): 1194 self.record_types_for_alias(accessor_location) 1195 1196 # Update accesses employing aliases. 1197 1198 for access_location in alias_accesses: 1199 self.record_types_for_access(access_location, self.access_index[access_location]) 1200 1201 def constrain_types(self, path, class_types, instance_types, module_types): 1202 1203 """ 1204 Using the given 'path' to an object, constrain the given 'class_types', 1205 'instance_types' and 'module_types'. 1206 1207 Return the class, instance, module types plus whether the types are 1208 constrained to a specific kind of type. 1209 """ 1210 1211 ref = self.importer.identify(path) 1212 if ref: 1213 1214 # Constrain usage suggestions using the identified object. 1215 1216 if ref.has_kind("<class>"): 1217 return ( 1218 set(class_types).intersection([ref.get_origin()]), [], [], True 1219 ) 1220 elif ref.has_kind("<module>"): 1221 return ( 1222 [], [], set(module_types).intersection([ref.get_origin()]), True 1223 ) 1224 1225 return class_types, instance_types, module_types, False 1226 1227 def get_target_types(self, location, usage): 1228 1229 """ 1230 Return the class, instance and module types constrained for the name at 1231 the given 'location' exhibiting the given 'usage'. Whether the types 1232 have been constrained using contextual information is also indicated, 1233 plus whether the types have been constrained to a specific kind of type. 1234 """ 1235 1236 unit_path, name, attrnames, version = location 1237 1238 # Detect any initialised name for the location. 1239 1240 if name: 1241 ref = self.get_initialised_name(location) 1242 if ref: 1243 (class_types, only_instance_types, module_types, 1244 _function_types, _var_types) = separate_types([ref]) 1245 return class_types, only_instance_types, module_types, True, False 1246 1247 # Retrieve the recorded types for the usage. 1248 1249 class_types = self.get_class_types_for_usage(usage) 1250 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1251 module_types = self.get_module_types_for_usage(usage) 1252 1253 # Merge usage deductions with observations to obtain reference types 1254 # for names involved with attribute accesses. 1255 1256 if not name: 1257 return class_types, only_instance_types, module_types, False, False 1258 1259 # Obtain references to known objects. 1260 1261 path = self.get_name_path(unit_path, name) 1262 1263 class_types, only_instance_types, module_types, constrained_specific = \ 1264 self.constrain_types(path, class_types, only_instance_types, module_types) 1265 1266 if constrained_specific: 1267 return class_types, only_instance_types, module_types, constrained_specific, constrained_specific 1268 1269 # Constrain "self" references. 1270 1271 if name == "self": 1272 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1273 if t: 1274 class_types, only_instance_types, module_types, constrained = t 1275 return class_types, only_instance_types, module_types, constrained, False 1276 1277 return class_types, only_instance_types, module_types, False, False 1278 1279 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1280 1281 """ 1282 Where the name "self" appears in a method, attempt to constrain the 1283 classes involved. 1284 1285 Return the class, instance, module types plus whether the types are 1286 constrained. 1287 """ 1288 1289 class_name = self.in_method(unit_path) 1290 1291 if not class_name: 1292 return None 1293 1294 classes = set([class_name]) 1295 classes.update(self.get_descendants_for_class(class_name)) 1296 1297 # Note that only instances will be expected for these references but 1298 # either classes or instances may provide the attributes. 1299 1300 return ( 1301 set(class_types).intersection(classes), 1302 set(only_instance_types).intersection(classes), 1303 [], True 1304 ) 1305 1306 def in_method(self, path): 1307 1308 "Return whether 'path' refers to a method." 1309 1310 class_name, method_name = path.rsplit(".", 1) 1311 return self.importer.classes.has_key(class_name) and class_name 1312 1313 def init_reference_details(self, location): 1314 1315 "Initialise reference-related details for 'location'." 1316 1317 self.init_definition_details(location) 1318 self.init_access_details(location) 1319 1320 def init_definition_details(self, location): 1321 1322 "Initialise name definition details for 'location'." 1323 1324 self.accessor_class_types[location] = set() 1325 self.accessor_instance_types[location] = set() 1326 self.accessor_module_types[location] = set() 1327 self.provider_class_types[location] = set() 1328 self.provider_instance_types[location] = set() 1329 self.provider_module_types[location] = set() 1330 1331 def init_access_details(self, location): 1332 1333 "Initialise access details at 'location'." 1334 1335 self.referenced_attrs[location] = {} 1336 1337 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1338 1339 """ 1340 Define types for the 'access_location' associated with the given 1341 'accessor_locations'. 1342 """ 1343 1344 path, name, attrnames, version = access_location 1345 if not attrnames: 1346 return 1347 1348 attrname = get_attrnames(attrnames)[0] 1349 1350 # Collect all suggested types for the accessors. Accesses may 1351 # require accessors from of a subset of the complete set of types. 1352 1353 class_types = set() 1354 module_types = set() 1355 instance_types = set() 1356 1357 constrained = True 1358 1359 for location in accessor_locations: 1360 1361 # Remember accesses employing aliases. 1362 1363 if alias_accesses is not None and self.alias_index.has_key(location): 1364 alias_accesses.add(access_location) 1365 1366 # Use the type information deduced for names from above. 1367 1368 if self.accessor_class_types.has_key(location): 1369 class_types.update(self.accessor_class_types[location]) 1370 module_types.update(self.accessor_module_types[location]) 1371 instance_types.update(self.accessor_instance_types[location]) 1372 1373 # Where accesses are associated with assignments but where no 1374 # attribute usage observations have caused such an association, 1375 # the attribute name is considered by itself. 1376 1377 else: 1378 self.init_definition_details(location) 1379 self.record_types_for_usage(location, [attrname]) 1380 1381 constrained = location in self.accessor_constrained and constrained 1382 1383 self.init_access_details(access_location) 1384 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1385 1386 def record_types_for_usage(self, accessor_location, usage): 1387 1388 """ 1389 Record types for the given 'accessor_location' according to the given 1390 'usage' observations which may be None to indicate an absence of usage. 1391 """ 1392 1393 (class_types, 1394 instance_types, 1395 module_types, 1396 constrained, 1397 constrained_specific) = self.get_target_types(accessor_location, usage) 1398 1399 self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific) 1400 1401 def record_types_for_attribute(self, access_location, attrname): 1402 1403 """ 1404 Record types for the 'access_location' employing only the given 1405 'attrname' for type deduction. 1406 """ 1407 1408 usage = [attrname] 1409 1410 class_types = self.get_class_types_for_usage(usage) 1411 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1412 module_types = self.get_module_types_for_usage(usage) 1413 1414 self.init_reference_details(access_location) 1415 1416 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1417 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1418 1419 def record_types_for_alias(self, accessor_location): 1420 1421 """ 1422 Define types for the 'accessor_location' not having associated usage. 1423 """ 1424 1425 have_access = self.provider_class_types.has_key(accessor_location) 1426 1427 # With an access, attempt to narrow the existing selection of provider 1428 # types. 1429 1430 if have_access: 1431 provider_class_types = self.provider_class_types[accessor_location] 1432 provider_instance_types = self.provider_instance_types[accessor_location] 1433 provider_module_types = self.provider_module_types[accessor_location] 1434 1435 # Find details for any corresponding access. 1436 1437 all_class_types = set() 1438 all_instance_types = set() 1439 all_module_types = set() 1440 1441 for access_location in self.alias_index[accessor_location]: 1442 location, name, attrnames, access_number = access_location 1443 1444 # Alias references an attribute access. 1445 1446 if attrnames: 1447 1448 # Obtain attribute references for the access. 1449 1450 attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]] 1451 1452 # Separate the different attribute types. 1453 1454 (class_types, instance_types, module_types, 1455 function_types, var_types) = separate_types(attrs) 1456 1457 # Where non-accessor types are found, do not attempt to refine 1458 # the defined accessor types. 1459 1460 if function_types or var_types: 1461 return 1462 1463 class_types = set(provider_class_types).intersection(class_types) 1464 instance_types = set(provider_instance_types).intersection(instance_types) 1465 module_types = set(provider_module_types).intersection(module_types) 1466 1467 # Alias references a name, not an access. 1468 1469 else: 1470 # Attempt to refine the types using initialised names. 1471 1472 attr = self.get_initialised_name(access_location) 1473 if attr: 1474 (class_types, instance_types, module_types, 1475 _function_types, _var_types) = separate_types([attr]) 1476 1477 # Where no further information is found, do not attempt to 1478 # refine the defined accessor types. 1479 1480 else: 1481 return 1482 1483 all_class_types.update(class_types) 1484 all_instance_types.update(instance_types) 1485 all_module_types.update(module_types) 1486 1487 # Record refined type details for the alias as an accessor. 1488 1489 self.init_definition_details(accessor_location) 1490 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1491 1492 # Without an access, attempt to identify references for the alias. 1493 1494 else: 1495 refs = set() 1496 1497 for access_location in self.alias_index[accessor_location]: 1498 1499 # Obtain any redefined constant access location. 1500 1501 if self.const_accesses.has_key(access_location): 1502 access_location = self.const_accesses[access_location] 1503 1504 location, name, attrnames, access_number = access_location 1505 1506 # Alias references an attribute access. 1507 1508 if attrnames: 1509 attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]] 1510 refs.update(attrs) 1511 1512 # Alias references a name, not an access. 1513 1514 else: 1515 attr = self.get_initialised_name(access_location) 1516 attrs = attr and [attr] or [] 1517 if not attrs and self.provider_class_types.has_key(access_location): 1518 class_types = self.provider_class_types[access_location] 1519 instance_types = self.provider_instance_types[access_location] 1520 module_types = self.provider_module_types[access_location] 1521 attrs = combine_types(class_types, instance_types, module_types) 1522 if attrs: 1523 refs.update(attrs) 1524 1525 # Record reference details for the alias separately from accessors. 1526 1527 self.referenced_objects[accessor_location] = refs 1528 1529 def get_initialised_name(self, access_location): 1530 1531 """ 1532 Return references for any initialised names at 'access_location', or 1533 None if no such references exist. 1534 """ 1535 1536 location, name, attrnames, version = access_location 1537 path = self.get_name_path(location, name) 1538 1539 # Use initialiser information, if available. 1540 1541 refs = self.importer.all_initialised_names.get(path) 1542 if refs and refs.has_key(version): 1543 return refs[version] 1544 else: 1545 return None 1546 1547 def get_name_path(self, path, name): 1548 1549 "Return a suitable qualified name from the given 'path' and 'name'." 1550 1551 if "." in name: 1552 return name 1553 else: 1554 return "%s.%s" % (path, name) 1555 1556 def record_reference_types(self, location, class_types, instance_types, 1557 module_types, constrained, constrained_specific=False): 1558 1559 """ 1560 Associate attribute provider types with the given 'location', consisting 1561 of the given 'class_types', 'instance_types' and 'module_types'. 1562 1563 If 'constrained' is indicated, the constrained nature of the accessor is 1564 recorded for the location. 1565 1566 If 'constrained_specific' is indicated using a true value, instance types 1567 will not be added to class types to permit access via instances at the 1568 given location. This is only useful where a specific accessor is known 1569 to be a class. 1570 1571 Note that the specified types only indicate the provider types for 1572 attributes, whereas the recorded accessor types indicate the possible 1573 types of the actual objects used to access attributes. 1574 """ 1575 1576 # Update the type details for the location. 1577 1578 self.provider_class_types[location].update(class_types) 1579 self.provider_instance_types[location].update(instance_types) 1580 self.provider_module_types[location].update(module_types) 1581 1582 # Class types support classes and instances as accessors. 1583 # Instance-only and module types support only their own kinds as 1584 # accessors. 1585 1586 # However, the nature of accessors can be further determined. 1587 # Any self variable may only refer to an instance. 1588 1589 path, name, version, attrnames = location 1590 if name != "self" or not self.in_method(path): 1591 self.accessor_class_types[location].update(class_types) 1592 1593 if not constrained_specific: 1594 self.accessor_instance_types[location].update(class_types) 1595 1596 self.accessor_instance_types[location].update(instance_types) 1597 1598 if name != "self" or not self.in_method(path): 1599 self.accessor_module_types[location].update(module_types) 1600 1601 if constrained: 1602 self.accessor_constrained.add(location) 1603 1604 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1605 1606 """ 1607 Identify reference attributes, associating them with the given 1608 'location', identifying the given 'attrname', employing the given 1609 'class_types', 'instance_types' and 'module_types'. 1610 1611 If 'constrained' is indicated, the constrained nature of the access is 1612 recorded for the location. 1613 """ 1614 1615 # Record the referenced objects. 1616 1617 self.referenced_attrs[location] = \ 1618 self._identify_reference_attribute(attrname, class_types, instance_types, module_types) 1619 1620 if constrained: 1621 self.access_constrained.add(location) 1622 1623 def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types): 1624 1625 """ 1626 Identify the reference attribute with the given 'attrname', employing 1627 the given 'class_types', 'instance_types' and 'module_types'. 1628 """ 1629 1630 attrs = set() 1631 1632 # The class types expose class attributes either directly or via 1633 # instances. 1634 1635 for object_type in class_types: 1636 ref = self.importer.get_class_attribute(object_type, attrname) 1637 if ref: 1638 attrs.add(("<class>", object_type, ref)) 1639 1640 # Add any distinct instance attributes that would be provided 1641 # by instances also providing indirect class attribute access. 1642 1643 for ref in self.importer.get_instance_attributes(object_type, attrname): 1644 attrs.add(("<instance>", object_type, ref)) 1645 1646 # The instance-only types expose instance attributes, but although 1647 # classes are excluded as potential accessors (since they do not provide 1648 # the instance attributes), the class types may still provide some 1649 # attributes. 1650 1651 for object_type in instance_types: 1652 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1653 1654 if instance_attrs: 1655 for ref in instance_attrs: 1656 attrs.add(("<instance>", object_type, ref)) 1657 else: 1658 ref = self.importer.get_class_attribute(object_type, attrname) 1659 if ref: 1660 attrs.add(("<class>", object_type, ref)) 1661 1662 # Module types expose module attributes for module accessors. 1663 1664 for object_type in module_types: 1665 ref = self.importer.get_module_attribute(object_type, attrname) 1666 if ref: 1667 attrs.add(("<module>", object_type, ref)) 1668 1669 return attrs 1670 1671 constrained_specific_tests = ( 1672 "constrained-specific-instance", 1673 "constrained-specific-type", 1674 "constrained-specific-object", 1675 ) 1676 1677 constrained_common_tests = ( 1678 "constrained-common-instance", 1679 "constrained-common-type", 1680 "constrained-common-object", 1681 ) 1682 1683 guarded_specific_tests = ( 1684 "guarded-specific-instance", 1685 "guarded-specific-type", 1686 "guarded-specific-object", 1687 ) 1688 1689 guarded_common_tests = ( 1690 "guarded-common-instance", 1691 "guarded-common-type", 1692 "guarded-common-object", 1693 ) 1694 1695 specific_tests = ( 1696 "specific-instance", 1697 "specific-type", 1698 "specific-object", 1699 ) 1700 1701 common_tests = ( 1702 "common-instance", 1703 "common-type", 1704 "common-object", 1705 ) 1706 1707 class_tests = ( 1708 "guarded-specific-type", 1709 "guarded-common-type", 1710 "specific-type", 1711 "common-type", 1712 ) 1713 1714 class_or_instance_tests = ( 1715 "guarded-specific-object", 1716 "guarded-common-object", 1717 "specific-object", 1718 "common-object", 1719 ) 1720 1721 def get_access_plan(self, location): 1722 1723 "Return details of the access at the given 'location'." 1724 1725 const_access = self.const_accesses_rev.has_key(location) 1726 1727 path, name, attrname_str, version = location 1728 attrnames = attrname_str.split(".") 1729 attrname = attrnames[0] 1730 1731 # Obtain reference and accessor information, retaining also distinct 1732 # provider kind details. 1733 1734 attrs = [] 1735 objtypes = [] 1736 provider_kinds = set() 1737 1738 for attrtype, objtype, attr in self.referenced_attrs[location]: 1739 attrs.append(attr) 1740 objtypes.append(objtype) 1741 provider_kinds.add(attrtype) 1742 1743 # Obtain accessor type and kind information. 1744 1745 accessor_types = self.reference_all_accessor_types[location] 1746 accessor_general_types = self.reference_all_accessor_general_types[location] 1747 accessor_kinds = get_kinds(accessor_general_types) 1748 1749 # Determine any guard or test requirements. 1750 1751 constrained = location in self.access_constrained 1752 test = self.reference_test_types[location] 1753 1754 # Determine the accessor and provider properties. 1755 1756 class_accessor = "<class>" in accessor_kinds 1757 module_accessor = "<module>" in accessor_kinds 1758 instance_accessor = "<instance>" in accessor_kinds 1759 provided_by_class = "<class>" in provider_kinds 1760 provided_by_instance = "<instance>" in provider_kinds 1761 1762 # Identify the last static attribute for context acquisition. 1763 1764 base = None 1765 dynamic_base = None 1766 1767 # Constant accesses have static accessors. 1768 1769 if const_access: 1770 base = len(objtypes) == 1 and first(objtypes) 1771 1772 # Constant accessors are static. 1773 1774 else: 1775 ref = self.importer.identify("%s.%s" % (path, name)) 1776 if ref: 1777 base = ref.get_origin() 1778 1779 # Usage of previously-generated guard and test details. 1780 1781 elif test in self.constrained_specific_tests: 1782 ref = first(accessor_types) 1783 1784 elif test in self.constrained_common_tests: 1785 ref = first(accessor_general_types) 1786 1787 elif test in self.guarded_specific_tests: 1788 ref = first(accessor_types) 1789 1790 elif test in self.guarded_common_tests: 1791 ref = first(accessor_general_types) 1792 1793 # For attribute-based tests, tentatively identify a dynamic base. 1794 # Such tests allow single or multiple kinds of a type. 1795 1796 elif test in self.common_tests or test in self.specific_tests: 1797 dynamic_base = self.reference_test_accessor_types[location] 1798 1799 # Static accessors. 1800 1801 if not base and test in self.class_tests: 1802 base = ref and ref.get_origin() or dynamic_base 1803 1804 # Accessors that are not static but whose nature is determined. 1805 1806 elif not base and ref: 1807 dynamic_base = ref.get_origin() 1808 1809 traversed = [] 1810 1811 # Traverse remaining attributes. 1812 1813 while len(attrs) == 1: 1814 attr = first(attrs) 1815 1816 traversed.append(attrname) 1817 del attrnames[0] 1818 1819 if not attrnames: 1820 break 1821 1822 # Update the last static attribute. 1823 1824 if attr.static(): 1825 base = attr.get_origin() 1826 traversed = [] 1827 1828 # Get the next attribute. 1829 1830 attrname = attrnames[0] 1831 attrs = self.importer.get_attributes(attr, attrname) 1832 1833 # Where many attributes are suggested, no single attribute identity can 1834 # be loaded. 1835 1836 else: 1837 attr = None 1838 1839 # Determine the method of access. 1840 1841 # Static, identified attribute. 1842 1843 if attr and attr.static(): 1844 method = "static"; origin = attr.final() 1845 1846 # Identified attribute that must be accessed via its parent. 1847 1848 elif attr and attr.get_name(): 1849 method = "direct"; origin = attr.get_name() 1850 1851 # Attribute accessed at a known position via its parent. 1852 1853 elif base or dynamic_base: 1854 method = "relative"; origin = None 1855 1856 # The fallback case is always run-time testing and access. 1857 1858 else: 1859 l = [] 1860 if class_accessor or module_accessor or provided_by_instance: 1861 l.append("checkobject") 1862 if instance_accessor and provided_by_class: 1863 l.append("checkclass") 1864 method = "+".join(l) 1865 origin = None 1866 1867 return base or name, traversed, attrnames, method, test, origin 1868 1869 # vim: tabstop=4 expandtab shiftwidth=4