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