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