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