1 #!/usr/bin/env python 2 3 """ 4 Deduce types for usage observations. 5 6 Copyright (C) 2014, 2015, 2016, 2017 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_access_location, encode_constrained, \ 27 encode_instruction, encode_location, encode_usage, \ 28 get_kinds, test_label_for_kind, test_label_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 root_class_type = "__builtins__.object" 39 40 def __init__(self, importer, output): 41 42 """ 43 Initialise an instance using the given 'importer' that will perform 44 deductions on the program information, writing the results to the given 45 'output' directory. 46 """ 47 48 self.importer = importer 49 self.output = output 50 51 # Descendants of classes. 52 53 self.descendants = {} 54 self.init_descendants() 55 self.init_special_attributes() 56 57 # Map locations to usage in order to determine specific types. 58 59 self.location_index = {} 60 61 # Map access locations to definition locations. 62 63 self.access_index = {} 64 65 # Map aliases to accesses that define them. 66 67 self.alias_index = {} 68 69 # Map constant accesses to redefined accesses. 70 71 self.const_accesses = {} 72 self.const_accesses_rev = {} 73 74 # Map usage observations to assigned attributes. 75 76 self.assigned_attrs = {} 77 78 # Map usage observations to objects. 79 80 self.attr_class_types = {} 81 self.attr_instance_types = {} 82 self.attr_module_types = {} 83 84 # All known attribute names. 85 86 self.all_attrnames = set() 87 88 # Modified attributes from usage observations. 89 90 self.modified_attributes = {} 91 92 # Accesses that are assignments or invocations. 93 94 self.reference_assignments = set() 95 self.reference_invocations = {} 96 self.reference_invocations_unsuitable = {} 97 98 # Map locations to types, constrained indicators and attributes. 99 100 self.accessor_class_types = {} 101 self.accessor_instance_types = {} 102 self.accessor_module_types = {} 103 self.provider_class_types = {} 104 self.provider_instance_types = {} 105 self.provider_module_types = {} 106 self.accessor_constrained = set() 107 self.access_constrained = set() 108 self.referenced_attrs = {} 109 self.referenced_objects = {} 110 111 # Details of access operations. 112 113 self.access_plans = {} 114 115 # Specific attribute access information. 116 117 self.access_instructions = {} 118 self.accessor_kinds = {} 119 120 # Accumulated information about accessors and providers. 121 122 self.accessor_general_class_types = {} 123 self.accessor_general_instance_types = {} 124 self.accessor_general_module_types = {} 125 self.accessor_all_types = {} 126 self.accessor_all_general_types = {} 127 self.provider_all_types = {} 128 self.accessor_guard_tests = {} 129 130 # Accumulated information about accessed attributes and 131 # access/attribute-specific accessor tests. 132 133 self.reference_all_attrs = {} 134 self.reference_all_providers = {} 135 self.reference_all_provider_kinds = {} 136 self.reference_all_accessor_types = {} 137 self.reference_all_accessor_general_types = {} 138 self.reference_test_types = {} 139 self.reference_test_accessor_type = {} 140 141 # The processing workflow itself. 142 143 self.init_usage_index() 144 self.init_attr_type_indexes() 145 self.init_combined_attribute_index() 146 self.init_accessors() 147 self.init_accesses() 148 self.init_aliases() 149 self.modify_mutated_attributes() 150 self.identify_references() 151 self.classify_accessors() 152 self.classify_accesses() 153 self.initialise_access_plans() 154 self.initialise_access_instructions() 155 self.identify_dependencies() 156 157 def to_output(self): 158 159 "Write the output files using deduction information." 160 161 self.check_output() 162 163 self.write_mutations() 164 self.write_accessors() 165 self.write_accesses() 166 self.write_access_plans() 167 168 def write_mutations(self): 169 170 """ 171 Write mutation-related output in the following format: 172 173 qualified name " " original object type 174 175 Object type can be "<class>", "<function>" or "<var>". 176 """ 177 178 f = open(join(self.output, "mutations"), "w") 179 try: 180 attrs = self.modified_attributes.items() 181 attrs.sort() 182 183 for attr, value in attrs: 184 print >>f, attr, value 185 finally: 186 f.close() 187 188 def write_accessors(self): 189 190 """ 191 Write reference-related output in the following format for types: 192 193 location " " ( "constrained" | "deduced" ) " " attribute type " " most general type names " " number of specific types 194 195 Note that multiple lines can be given for each location, one for each 196 attribute type. 197 198 Locations have the following format: 199 200 qualified name of scope "." local name ":" name version 201 202 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 203 where the latter indicates an absence of suitable references. 204 205 Type names indicate the type providing the attributes, being either a 206 class or module qualified name. 207 208 ---- 209 210 A summary of accessor types is formatted as follows: 211 212 location " " ( "constrained" | "deduced" ) " " ( "specific" | "common" | "unguarded" ) " " most general type names " " number of specific types 213 214 This summary groups all attribute types (class, instance, module) into a 215 single line in order to determine the complexity of identifying an 216 accessor. 217 218 ---- 219 220 References that cannot be supported by any types are written to a 221 warnings file in the following format: 222 223 location 224 225 ---- 226 227 For each location where a guard would be asserted to guarantee the 228 nature of an object, the following format is employed: 229 230 location " " ( "specific" | "common" ) " " object kind " " object types 231 232 Object kind can be "<class>", "<instance>" or "<module>". 233 """ 234 235 f_type_summary = open(join(self.output, "type_summary"), "w") 236 f_types = open(join(self.output, "types"), "w") 237 f_warnings = open(join(self.output, "type_warnings"), "w") 238 f_guards = open(join(self.output, "guards"), "w") 239 240 try: 241 locations = self.accessor_class_types.keys() 242 locations.sort() 243 244 for location in locations: 245 constrained = location in self.accessor_constrained 246 247 # Accessor information. 248 249 class_types = self.accessor_class_types[location] 250 instance_types = self.accessor_instance_types[location] 251 module_types = self.accessor_module_types[location] 252 253 general_class_types = self.accessor_general_class_types[location] 254 general_instance_types = self.accessor_general_instance_types[location] 255 general_module_types = self.accessor_general_module_types[location] 256 257 all_types = self.accessor_all_types[location] 258 all_general_types = self.accessor_all_general_types[location] 259 260 if class_types: 261 print >>f_types, encode_location(location), encode_constrained(constrained), "<class>", \ 262 sorted_output(general_class_types), len(class_types) 263 264 if instance_types: 265 print >>f_types, encode_location(location), encode_constrained(constrained), "<instance>", \ 266 sorted_output(general_instance_types), len(instance_types) 267 268 if module_types: 269 print >>f_types, encode_location(location), encode_constrained(constrained), "<module>", \ 270 sorted_output(general_module_types), len(module_types) 271 272 if not all_types: 273 print >>f_types, encode_location(location), "deduced", "<>", 0 274 attrnames = list(self.location_index[location]) 275 attrnames.sort() 276 print >>f_warnings, encode_location(location), "; ".join(map(encode_usage, attrnames)) 277 278 guard_test = self.accessor_guard_tests.get(location) 279 if guard_test: 280 guard_test_type, guard_test_arg = guard_test 281 282 # Write specific type guard details. 283 284 if guard_test and guard_test_type == "specific": 285 print >>f_guards, encode_location(location), "-".join(guard_test), \ 286 first(get_kinds(all_types)), \ 287 sorted_output(all_types) 288 289 # Write common type guard details. 290 291 elif guard_test and guard_test_type == "common": 292 print >>f_guards, encode_location(location), "-".join(guard_test), \ 293 first(get_kinds(all_general_types)), \ 294 sorted_output(all_general_types) 295 296 print >>f_type_summary, encode_location(location), encode_constrained(constrained), \ 297 guard_test and "-".join(guard_test) or "unguarded", sorted_output(all_general_types), len(all_types) 298 299 finally: 300 f_type_summary.close() 301 f_types.close() 302 f_warnings.close() 303 f_guards.close() 304 305 def write_accesses(self): 306 307 """ 308 Specific attribute output is produced in the following format: 309 310 location " " ( "constrained" | "deduced" ) " " attribute type " " attribute references 311 312 Note that multiple lines can be given for each location and attribute 313 name, one for each attribute type. 314 315 Locations have the following format: 316 317 qualified name of scope "." local name " " attribute name ":" access number 318 319 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 320 where the latter indicates an absence of suitable references. 321 322 Attribute references have the following format: 323 324 object type ":" qualified name 325 326 Object type can be "<class>", "<function>" or "<var>". 327 328 ---- 329 330 A summary of attributes is formatted as follows: 331 332 location " " attribute name " " ( "constrained" | "deduced" ) " " test " " attribute references 333 334 This summary groups all attribute types (class, instance, module) into a 335 single line in order to determine the complexity of each access. 336 337 Tests can be "validate", "specific", "untested", "guarded-validate" or "guarded-specific". 338 339 ---- 340 341 For each access where a test would be asserted to guarantee the 342 nature of an attribute, the following formats are employed: 343 344 location " " attribute name " " "validate" 345 location " " attribute name " " "specific" " " attribute type " " object type 346 347 ---- 348 349 References that cannot be supported by any types are written to a 350 warnings file in the following format: 351 352 location 353 """ 354 355 f_attr_summary = open(join(self.output, "attribute_summary"), "w") 356 f_attrs = open(join(self.output, "attributes"), "w") 357 f_tests = open(join(self.output, "tests"), "w") 358 f_warnings = open(join(self.output, "attribute_warnings"), "w") 359 f_unsuitable = open(join(self.output, "invocation_warnings"), "w") 360 361 try: 362 locations = self.referenced_attrs.keys() 363 locations.sort() 364 365 for location in locations: 366 constrained = location in self.access_constrained 367 368 # Attribute information, both name-based and anonymous. 369 370 referenced_attrs = self.referenced_attrs[location] 371 372 if referenced_attrs: 373 attrname = get_attrname_from_location(location) 374 375 all_accessed_attrs = self.reference_all_attrs[location] 376 377 for attrtype, attrs in self.get_referenced_attrs(location): 378 print >>f_attrs, encode_access_location(location), encode_constrained(constrained), attrtype, sorted_output(attrs) 379 380 test_type = self.reference_test_types.get(location) 381 382 # Write the need to test at run time. 383 384 if test_type[0] == "validate": 385 print >>f_tests, encode_access_location(location), "-".join(test_type) 386 387 # Write any type checks for anonymous accesses. 388 389 elif test_type and self.reference_test_accessor_type.get(location): 390 print >>f_tests, encode_access_location(location), "-".join(test_type), \ 391 sorted_output(all_accessed_attrs), \ 392 self.reference_test_accessor_type[location] 393 394 print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \ 395 test_type and "-".join(test_type) or "untested", sorted_output(all_accessed_attrs) 396 397 # Write details of potentially unsuitable invocation 398 # occurrences. 399 400 unsuitable = self.reference_invocations_unsuitable.get(location) 401 if unsuitable: 402 unsuitable = map(str, unsuitable) 403 unsuitable.sort() 404 print >>f_unsuitable, encode_access_location(location), ", ".join(unsuitable) 405 406 else: 407 print >>f_warnings, encode_access_location(location) 408 409 finally: 410 f_attr_summary.close() 411 f_attrs.close() 412 f_tests.close() 413 f_warnings.close() 414 f_unsuitable.close() 415 416 def write_access_plans(self): 417 418 """ 419 Write access and instruction plans. 420 421 Each attribute access is written out as a plan of the following form: 422 423 location " " name " " test " " test type " " base " " traversed attributes 424 " " traversal access modes " " attributes to traverse 425 " " context " " context test " " first access method 426 " " final access method " " static attribute " " accessor kinds 427 428 Locations have the following format: 429 430 qualified name of scope "." local name ":" name version 431 432 Traversal access modes are either "class" (obtain accessor class to 433 access attribute) or "object" (obtain attribute directly from accessor). 434 """ 435 436 f_attrs = open(join(self.output, "attribute_plans"), "w") 437 438 try: 439 locations = self.access_plans.keys() 440 locations.sort() 441 442 for location in locations: 443 name, test, test_type, base, \ 444 traversed, traversal_modes, attrnames, \ 445 context, context_test, \ 446 first_method, final_method, \ 447 attr, accessor_kinds = self.access_plans[location] 448 449 print >>f_attrs, encode_access_location(location), \ 450 name or "{}", \ 451 test and "-".join(test) or "{}", \ 452 test_type or "{}", \ 453 base or "{}", \ 454 ".".join(traversed) or "{}", \ 455 ".".join(traversal_modes) or "{}", \ 456 ".".join(attrnames) or "{}", \ 457 context, context_test, \ 458 first_method, final_method, attr or "{}", \ 459 ",".join(accessor_kinds) 460 461 finally: 462 f_attrs.close() 463 464 f = open(join(self.output, "instruction_plans"), "w") 465 try: 466 access_instructions = self.access_instructions.items() 467 access_instructions.sort() 468 469 for location, instructions in access_instructions: 470 print >>f, encode_access_location(location), "..." 471 for instruction in instructions: 472 print >>f, encode_instruction(instruction) 473 print >>f 474 475 finally: 476 f.close() 477 478 def classify_accessors(self): 479 480 "For each program location, classify accessors." 481 482 # Where instance and module types are defined, class types are also 483 # defined. See: init_definition_details 484 485 locations = self.accessor_class_types.keys() 486 487 for location in locations: 488 constrained = location in self.accessor_constrained 489 490 # Provider information. 491 492 class_types = self.provider_class_types[location] 493 instance_types = self.provider_instance_types[location] 494 module_types = self.provider_module_types[location] 495 496 # Collect specific and general type information. 497 498 self.provider_all_types[location] = \ 499 combine_types(class_types, instance_types, module_types) 500 501 # Accessor information. 502 503 class_types = self.accessor_class_types[location] 504 self.accessor_general_class_types[location] = \ 505 general_class_types = self.get_most_general_class_types(class_types) 506 507 instance_types = self.accessor_instance_types[location] 508 self.accessor_general_instance_types[location] = \ 509 general_instance_types = self.get_most_general_class_types(instance_types) 510 511 module_types = self.accessor_module_types[location] 512 self.accessor_general_module_types[location] = \ 513 general_module_types = self.get_most_general_module_types(module_types) 514 515 # Collect specific and general type information. 516 517 self.accessor_all_types[location] = all_types = \ 518 combine_types(class_types, instance_types, module_types) 519 520 self.accessor_all_general_types[location] = all_general_types = \ 521 combine_types(general_class_types, general_instance_types, general_module_types) 522 523 # Record guard information. 524 525 if not constrained: 526 527 # Record specific type guard details. 528 529 if len(all_types) == 1: 530 self.accessor_guard_tests[location] = ("specific", test_label_for_type(first(all_types))) 531 elif is_single_class_type(all_types): 532 self.accessor_guard_tests[location] = ("specific", "object") 533 534 # Record common type guard details. 535 536 elif len(all_general_types) == 1: 537 self.accessor_guard_tests[location] = ("common", test_label_for_type(first(all_types))) 538 elif is_single_class_type(all_general_types): 539 self.accessor_guard_tests[location] = ("common", "object") 540 541 # Otherwise, no convenient guard can be defined. 542 543 def classify_accesses(self): 544 545 "For each program location, classify accesses." 546 547 # Attribute accesses use potentially different locations to those of 548 # accessors. 549 550 locations = self.referenced_attrs.keys() 551 552 for location in locations: 553 constrained = location in self.access_constrained 554 555 # Combine type information from all accessors supplying the access. 556 557 accessor_locations = self.get_accessors_for_access(location) 558 559 all_provider_types = set() 560 all_accessor_types = set() 561 all_accessor_general_types = set() 562 563 for accessor_location in accessor_locations: 564 565 # Obtain the provider types for guard-related attribute access 566 # checks. 567 568 all_provider_types.update(self.provider_all_types.get(accessor_location)) 569 570 # Obtain the accessor guard types (specific and general). 571 572 all_accessor_types.update(self.accessor_all_types.get(accessor_location)) 573 all_accessor_general_types.update(self.accessor_all_general_types.get(accessor_location)) 574 575 # Obtain basic properties of the types involved in the access. 576 577 single_accessor_type = len(all_accessor_types) == 1 578 single_accessor_class_type = is_single_class_type(all_accessor_types) 579 single_accessor_general_type = len(all_accessor_general_types) == 1 580 single_accessor_general_class_type = is_single_class_type(all_accessor_general_types) 581 582 # Determine whether the attribute access is guarded or not. 583 584 guarded = ( 585 single_accessor_type or single_accessor_class_type or 586 single_accessor_general_type or single_accessor_general_class_type 587 ) 588 589 if guarded: 590 (guard_class_types, guard_instance_types, guard_module_types, 591 _function_types, _var_types) = separate_types(all_provider_types) 592 593 self.reference_all_accessor_types[location] = all_accessor_types 594 self.reference_all_accessor_general_types[location] = all_accessor_general_types 595 596 # Attribute information, both name-based and anonymous. 597 598 referenced_attrs = self.referenced_attrs[location] 599 600 if not referenced_attrs: 601 raise DeduceError("In %s, access via %s to attribute %s (occurrence %d) cannot be identified." % location) 602 603 # Record attribute information for each name used on the 604 # accessor. 605 606 attrname = get_attrname_from_location(location) 607 608 self.reference_all_attrs[location] = all_accessed_attrs = [] 609 self.reference_all_providers[location] = all_providers = [] 610 self.reference_all_provider_kinds[location] = all_provider_kinds = set() 611 612 # Obtain provider and attribute details for this kind of 613 # object. 614 615 for attrtype, object_type, attr in referenced_attrs: 616 all_accessed_attrs.append(attr) 617 all_providers.append(object_type) 618 all_provider_kinds.add(attrtype) 619 620 # Obtain reference and provider information as sets for the 621 # operations below, retaining the list forms for use with 622 # instruction plan preparation. 623 624 all_accessed_attrs = set(all_accessed_attrs) 625 all_providers = set(all_providers) 626 all_general_providers = self.get_most_general_types(all_providers) 627 628 # Determine which attributes would be provided by the 629 # accessor types upheld by a guard. 630 631 if guarded: 632 guard_attrs = set() 633 634 for _attrtype, object_type, attr in \ 635 self._identify_reference_attribute(location, attrname, guard_class_types, guard_instance_types, guard_module_types): 636 637 guard_attrs.add(attr) 638 else: 639 guard_attrs = None 640 641 # Constrained accesses guarantee the nature of the accessor. 642 # However, there may still be many types involved. 643 644 if constrained: 645 if single_accessor_type: 646 self.reference_test_types[location] = ("constrained", "specific", test_label_for_type(first(all_accessor_types))) 647 elif single_accessor_class_type: 648 self.reference_test_types[location] = ("constrained", "specific", "object") 649 elif single_accessor_general_type: 650 self.reference_test_types[location] = ("constrained", "common", test_label_for_type(first(all_accessor_general_types))) 651 elif single_accessor_general_class_type: 652 self.reference_test_types[location] = ("constrained", "common", "object") 653 else: 654 self.reference_test_types[location] = ("constrained", "many") 655 656 # Suitably guarded accesses, where the nature of the 657 # accessor can be guaranteed, do not require the attribute 658 # involved to be validated. Otherwise, for unguarded 659 # accesses, access-level tests are required. 660 661 elif guarded and all_accessed_attrs.issubset(guard_attrs): 662 if single_accessor_type: 663 self.reference_test_types[location] = ("guarded", "specific", test_label_for_type(first(all_accessor_types))) 664 elif single_accessor_class_type: 665 self.reference_test_types[location] = ("guarded", "specific", "object") 666 elif single_accessor_general_type: 667 self.reference_test_types[location] = ("guarded", "common", test_label_for_type(first(all_accessor_general_types))) 668 elif single_accessor_general_class_type: 669 self.reference_test_types[location] = ("guarded", "common", "object") 670 671 # Record the need to test the type of anonymous and 672 # unconstrained accessors. 673 674 elif len(all_providers) == 1: 675 provider = first(all_providers) 676 if provider != self.root_class_type: 677 all_accessor_kinds = set(get_kinds(all_accessor_types)) 678 if len(all_accessor_kinds) == 1: 679 test_type = ("test", "specific", test_label_for_kind(first(all_accessor_kinds))) 680 else: 681 test_type = ("test", "specific", "object") 682 self.reference_test_types[location] = test_type 683 self.reference_test_accessor_type[location] = provider 684 685 elif len(all_general_providers) == 1: 686 provider = first(all_general_providers) 687 if provider != self.root_class_type: 688 all_accessor_kinds = set(get_kinds(all_accessor_general_types)) 689 if len(all_accessor_kinds) == 1: 690 test_type = ("test", "common", test_label_for_kind(first(all_accessor_kinds))) 691 else: 692 test_type = ("test", "common", "object") 693 self.reference_test_types[location] = test_type 694 self.reference_test_accessor_type[location] = provider 695 696 # Record the need to test the identity of the attribute. 697 698 else: 699 self.reference_test_types[location] = ("validate",) 700 701 def initialise_access_plans(self): 702 703 "Define attribute access plans." 704 705 for location in self.referenced_attrs.keys(): 706 original_location = self.const_accesses_rev.get(location) 707 self.access_plans[original_location or location] = self.get_access_plan(location) 708 709 def identify_dependencies(self): 710 711 "Introduce more module dependencies to the importer." 712 713 for location, referenced_attrs in self.referenced_attrs.items(): 714 path, name, attrnames, version = location 715 716 # Identify module-level paths. 717 718 if self.importer.modules.has_key(path): 719 module_name = path 720 721 # Identify the module containing other paths. 722 723 else: 724 ref = self.importer.identify(path) 725 for objpath in ref.ancestors(): 726 if self.importer.modules.has_key(objpath): 727 module_name = objpath 728 break 729 else: 730 raise DeduceError("Cannot find module for path %s." % path) 731 732 # Identify references providing dependencies. 733 734 for attrtype, objtype, attr in referenced_attrs: 735 self.importer.add_dependency(path, attr.get_origin()) 736 737 def get_referenced_attrs(self, location): 738 739 """ 740 Return attributes referenced at the given access 'location' by the given 741 'attrname' as a list of (attribute type, attribute set) tuples. 742 """ 743 744 d = {} 745 for attrtype, objtype, attr in self.referenced_attrs[location]: 746 init_item(d, attrtype, set) 747 d[attrtype].add(attr.unaliased()) 748 l = d.items() 749 l.sort() # class, module, instance 750 return l 751 752 # Initialisation methods. 753 754 def init_descendants(self): 755 756 "Identify descendants of each class." 757 758 for name in self.importer.classes.keys(): 759 self.get_descendants_for_class(name) 760 761 def get_descendants_for_class(self, name): 762 763 """ 764 Use subclass information to deduce the descendants for the class of the 765 given 'name'. 766 """ 767 768 if not self.descendants.has_key(name): 769 descendants = set() 770 771 for subclass in self.importer.subclasses[name]: 772 descendants.update(self.get_descendants_for_class(subclass)) 773 descendants.add(subclass) 774 775 self.descendants[name] = descendants 776 777 return self.descendants[name] 778 779 def init_special_attributes(self): 780 781 "Add special attributes to the classes for inheritance-related tests." 782 783 all_class_attrs = self.importer.all_class_attrs 784 785 for name, descendants in self.descendants.items(): 786 for descendant in descendants: 787 all_class_attrs[descendant]["#%s" % name] = name 788 789 for name in all_class_attrs.keys(): 790 all_class_attrs[name]["#%s" % name] = name 791 792 def init_usage_index(self): 793 794 """ 795 Create indexes for module and function attribute usage and for anonymous 796 accesses. 797 """ 798 799 for module in self.importer.get_modules(): 800 for path, assignments in module.attr_usage.items(): 801 self.add_usage(assignments, path) 802 803 for location, all_attrnames in self.importer.all_attr_accesses.items(): 804 for attrnames in all_attrnames: 805 attrname = get_attrnames(attrnames)[-1] 806 access_location = (location, None, attrnames, 0) 807 self.add_usage_term(access_location, ((attrname, False, False),)) 808 809 def add_usage(self, assignments, path): 810 811 """ 812 Collect usage from the given 'assignments', adding 'path' details to 813 each record if specified. Add the usage to an index mapping to location 814 information, as well as to an index mapping locations to usages. 815 """ 816 817 for name, versions in assignments.items(): 818 for i, usages in enumerate(versions): 819 location = (path, name, None, i) 820 821 for usage in usages: 822 self.add_usage_term(location, usage) 823 824 def add_usage_term(self, location, usage): 825 826 """ 827 For 'location' and using 'usage' as a description of usage, record 828 in the usage index a mapping from the usage to 'location', and record in 829 the location index a mapping from 'location' to the usage. 830 """ 831 832 init_item(self.location_index, location, set) 833 self.location_index[location].add(usage) 834 835 def init_accessors(self): 836 837 "Create indexes for module and function accessor information." 838 839 for module in self.importer.get_modules(): 840 for path, all_accesses in module.attr_accessors.items(): 841 self.add_accessors(all_accesses, path) 842 843 def add_accessors(self, all_accesses, path): 844 845 """ 846 For attribute accesses described by the mapping of 'all_accesses' from 847 name details to accessor details, record the locations of the accessors 848 for each access. 849 """ 850 851 # Get details for each access combining the given name and attribute. 852 853 for (name, attrnames), accesses in all_accesses.items(): 854 855 # Obtain the usage details using the access information. 856 857 for access_number, versions in enumerate(accesses): 858 access_location = (path, name, attrnames, access_number) 859 locations = [] 860 861 for version in versions: 862 location = (path, name, None, version) 863 locations.append(location) 864 865 self.access_index[access_location] = locations 866 867 def get_accessors_for_access(self, access_location): 868 869 "Find a definition providing accessor details, if necessary." 870 871 try: 872 return self.access_index[access_location] 873 except KeyError: 874 return [access_location] 875 876 def init_accesses(self): 877 878 """ 879 Check that attributes used in accesses are actually defined on some 880 object. This can be overlooked if unknown attributes are employed in 881 attribute chains. 882 883 Initialise collections for accesses involving assignments. 884 """ 885 886 # For each scope, obtain access details. 887 888 for path, all_accesses in self.importer.all_attr_access_modifiers.items(): 889 890 # For each combination of name and attribute names, obtain 891 # applicable modifiers. 892 893 for (name, attrname_str), modifiers in all_accesses.items(): 894 895 # For each access, determine the name versions affected by 896 # assignments. 897 898 for access_number, (assignment, invocation) in enumerate(modifiers): 899 900 if name: 901 access_location = (path, name, attrname_str, access_number) 902 else: 903 access_location = (path, None, attrname_str, 0) 904 905 # Plain name accesses do not employ attributes and are 906 # ignored. 907 908 if not attrname_str: 909 continue 910 911 attrnames = get_attrnames(attrname_str) 912 913 # Check the attribute names. 914 915 for attrname in attrnames: 916 if not attrname in self.all_attrnames: 917 raise DeduceError("In %s, attribute %s is not defined in the program." % (path, attrname)) 918 919 # Now only process assignments and invocations. 920 921 if invocation: 922 self.reference_invocations[access_location] = invocation 923 continue 924 elif not assignment: 925 continue 926 927 # Associate assignments with usage. 928 929 self.reference_assignments.add(access_location) 930 931 # Assignment occurs for the only attribute. 932 933 if len(attrnames) == 1: 934 accessor_locations = self.get_accessors_for_access(access_location) 935 936 for location in accessor_locations: 937 for usage in self.location_index[location]: 938 init_item(self.assigned_attrs, usage, set) 939 self.assigned_attrs[usage].add((path, name, attrnames[0])) 940 941 # Assignment occurs for the final attribute. 942 943 else: 944 usage = ((attrnames[-1], False, False),) 945 init_item(self.assigned_attrs, usage, set) 946 self.assigned_attrs[usage].add((path, name, attrnames[-1])) 947 948 def init_aliases(self): 949 950 "Expand aliases so that alias-based accesses can be resolved." 951 952 # Get aliased names with details of their accesses. 953 954 for (path, name), all_aliases in self.importer.all_aliased_names.items(): 955 956 # For each version of the name, obtain the access location. 957 958 for version, (original_path, original_name, attrnames, access_number) in all_aliases.items(): 959 accessor_location = (path, name, None, version) 960 access_location = (original_path, original_name, attrnames, access_number) 961 init_item(self.alias_index, accessor_location, list) 962 self.alias_index[accessor_location].append(access_location) 963 964 # Get aliases in terms of non-aliases and accesses. 965 966 for accessor_location, access_locations in self.alias_index.items(): 967 self.update_aliases(accessor_location, access_locations) 968 969 def update_aliases(self, accessor_location, access_locations, visited=None): 970 971 """ 972 Update the given 'accessor_location' defining an alias, update 973 'access_locations' to refer to non-aliases, following name references 974 via the access index. 975 976 If 'visited' is specified, it contains a set of accessor locations (and 977 thus keys to the alias index) that are currently being defined. 978 """ 979 980 if visited is None: 981 visited = set() 982 983 updated_locations = set() 984 985 for access_location in access_locations: 986 (path, original_name, attrnames, access_number) = access_location 987 988 # Where an alias refers to a name access, obtain the original name 989 # version details. 990 991 if attrnames is None: 992 993 # For each name version, attempt to determine any accesses that 994 # initialise the name. 995 996 for name_accessor_location in self.access_index[access_location]: 997 998 # Already-visited aliases do not contribute details. 999 1000 if name_accessor_location in visited: 1001 continue 1002 1003 visited.add(name_accessor_location) 1004 1005 name_access_locations = self.alias_index.get(name_accessor_location) 1006 if name_access_locations: 1007 updated_locations.update(self.update_aliases(name_accessor_location, name_access_locations, visited)) 1008 else: 1009 updated_locations.add(name_accessor_location) 1010 1011 # Otherwise, record the access details. 1012 1013 else: 1014 updated_locations.add(access_location) 1015 1016 self.alias_index[accessor_location] = updated_locations 1017 return updated_locations 1018 1019 # Attribute mutation for types. 1020 1021 def modify_mutated_attributes(self): 1022 1023 "Identify known, mutated attributes and change their state." 1024 1025 # Usage-based accesses. 1026 1027 for usage, all_attrnames in self.assigned_attrs.items(): 1028 if not usage: 1029 continue 1030 1031 for path, name, attrname in all_attrnames: 1032 class_types = self.get_class_types_for_usage(usage) 1033 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1034 module_types = self.get_module_types_for_usage(usage) 1035 1036 # Detect self usage within methods in order to narrow the scope 1037 # of the mutation. 1038 1039 t = name == "self" and self.constrain_self_reference(path, class_types, only_instance_types) 1040 if t: 1041 class_types, only_instance_types, module_types, constrained = t 1042 objects = set(class_types).union(only_instance_types).union(module_types) 1043 1044 self.mutate_attribute(objects, attrname) 1045 1046 def mutate_attribute(self, objects, attrname): 1047 1048 "Mutate static 'objects' with the given 'attrname'." 1049 1050 for name in objects: 1051 attr = "%s.%s" % (name, attrname) 1052 value = self.importer.get_object(attr) 1053 1054 # If the value is None, the attribute is 1055 # inherited and need not be set explicitly on 1056 # the class concerned. 1057 1058 if value: 1059 self.modified_attributes[attr] = value 1060 self.importer.set_object(attr, value.as_var()) 1061 1062 # Simplification of types. 1063 1064 def get_most_general_types(self, types): 1065 1066 "Return the most general types for the given 'types'." 1067 1068 module_types = set() 1069 class_types = set() 1070 1071 for type in types: 1072 ref = self.importer.identify(type) 1073 if ref.has_kind("<module>"): 1074 module_types.add(type) 1075 else: 1076 class_types.add(type) 1077 1078 types = set(self.get_most_general_module_types(module_types)) 1079 types.update(self.get_most_general_class_types(class_types)) 1080 return types 1081 1082 def get_most_general_class_types(self, class_types): 1083 1084 "Return the most general types for the given 'class_types'." 1085 1086 class_types = set(class_types) 1087 to_remove = set() 1088 1089 for class_type in class_types: 1090 for base in self.importer.classes[class_type]: 1091 base = base.get_origin() 1092 descendants = self.descendants[base] 1093 if base in class_types and descendants.issubset(class_types): 1094 to_remove.update(descendants) 1095 1096 class_types.difference_update(to_remove) 1097 return class_types 1098 1099 def get_most_general_module_types(self, module_types): 1100 1101 "Return the most general type for the given 'module_types'." 1102 1103 # Where all modules are provided, an object would provide the same 1104 # attributes. 1105 1106 if len(module_types) == len(self.importer.modules): 1107 return [self.root_class_type] 1108 else: 1109 return module_types 1110 1111 # More efficient usage-to-type indexing and retrieval. 1112 1113 def init_attr_type_indexes(self): 1114 1115 "Identify the types that can support each attribute name." 1116 1117 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 1118 self._init_attr_type_index(self.attr_instance_types, self.importer.all_instance_attrs, True) 1119 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs, False) 1120 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 1121 1122 def _init_attr_type_index(self, attr_types, attrs, assignment=None): 1123 1124 """ 1125 Initialise the 'attr_types' attribute-to-types mapping using the given 1126 'attrs' type-to-attributes mapping. 1127 """ 1128 1129 for name, attrnames in attrs.items(): 1130 for attrname in attrnames: 1131 1132 # Permit general access for certain kinds of object. 1133 1134 if assignment is None: 1135 init_item(attr_types, (attrname, False), set) 1136 init_item(attr_types, (attrname, True), set) 1137 attr_types[(attrname, False)].add(name) 1138 attr_types[(attrname, True)].add(name) 1139 1140 # Restrict attribute assignment for instances. 1141 1142 else: 1143 init_item(attr_types, (attrname, assignment), set) 1144 attr_types[(attrname, assignment)].add(name) 1145 1146 def get_class_types_for_usage(self, usage): 1147 1148 "Return names of classes supporting the given 'usage'." 1149 1150 return self._get_types_for_usage(usage, self.attr_class_types, self.importer.all_class_attrs) 1151 1152 def get_instance_types_for_usage(self, usage): 1153 1154 """ 1155 Return names of classes whose instances support the given 'usage' 1156 (as either class or instance attributes). 1157 """ 1158 1159 return self._get_types_for_usage(usage, self.attr_instance_types, self.importer.all_combined_attrs) 1160 1161 def get_module_types_for_usage(self, usage): 1162 1163 "Return names of modules supporting the given 'usage'." 1164 1165 return self._get_types_for_usage(usage, self.attr_module_types, self.importer.all_module_attrs) 1166 1167 def _get_types_for_usage(self, usage, attr_types, attrs): 1168 1169 """ 1170 For the given 'usage' representing attribute usage, return types 1171 recorded in the 'attr_types' attribute-to-types mapping that support 1172 such usage, with the given 'attrs' type-to-attributes mapping used to 1173 quickly assess whether a type supports all of the stated attributes. 1174 """ 1175 1176 # Where no attributes are used, any type would be acceptable. 1177 1178 if not usage: 1179 return attrs.keys() 1180 1181 keys = [] 1182 for attrname, invocation, assignment in usage: 1183 keys.append((attrname, assignment)) 1184 1185 # Obtain types supporting the first (attribute name, assignment) key... 1186 1187 types = set(attr_types.get(keys[0]) or []) 1188 1189 for key in keys[1:]: 1190 1191 # Record types that support all of the other attributes as well. 1192 1193 types.intersection_update(attr_types.get(key) or []) 1194 1195 return types 1196 1197 def init_combined_attribute_index(self): 1198 1199 "Initialise a combined index for the detection of invalid attributes." 1200 1201 self.all_attrnames = set() 1202 for attrs in (self.importer.all_combined_attrs, self.importer.all_module_attrs): 1203 for name, attrnames in attrs.items(): 1204 self.all_attrnames.update(attrnames) 1205 1206 # Reference identification. 1207 1208 def identify_references(self): 1209 1210 "Identify references using usage and name reference information." 1211 1212 # Names with associated attribute usage. 1213 1214 for location, usages in self.location_index.items(): 1215 1216 # Obtain attribute usage associated with a name, deducing the nature 1217 # of the name. Obtain types only for branches involving attribute 1218 # usage. (In the absence of usage, any type could be involved, but 1219 # then no accesses exist to require knowledge of the type.) 1220 1221 have_usage = False 1222 have_no_usage_branch = False 1223 1224 for usage in usages: 1225 if not usage: 1226 have_no_usage_branch = True 1227 continue 1228 elif not have_usage: 1229 self.init_definition_details(location) 1230 have_usage = True 1231 self.record_types_for_usage(location, usage) 1232 1233 # Where some usage occurs, but where branches without usage also 1234 # occur, record the types for those branches anyway. 1235 1236 if have_usage and have_no_usage_branch: 1237 self.init_definition_details(location) 1238 self.record_types_for_usage(location, None) 1239 1240 # Specific name-based attribute accesses. 1241 1242 alias_accesses = set() 1243 1244 for access_location, accessor_locations in self.access_index.items(): 1245 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1246 1247 # Anonymous references with attribute chains. 1248 1249 for location, accesses in self.importer.all_attr_accesses.items(): 1250 1251 # Get distinct attribute names. 1252 1253 all_attrnames = set() 1254 1255 for attrnames in accesses: 1256 all_attrnames.update(get_attrnames(attrnames)) 1257 1258 # Get attribute and accessor details for each attribute name. 1259 1260 for attrname in all_attrnames: 1261 access_location = (location, None, attrname, 0) 1262 self.record_types_for_attribute(access_location, attrname) 1263 1264 # References via constant/identified objects. 1265 1266 for location, name_accesses in self.importer.all_const_accesses.items(): 1267 1268 # A mapping from the original name and attributes to resolved access 1269 # details. 1270 1271 for original_access, access in name_accesses.items(): 1272 original_name, original_attrnames = original_access 1273 objpath, ref, attrnames = access 1274 1275 # Build an accessor combining the name and attribute names used. 1276 1277 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1278 1279 # Direct accesses to attributes. 1280 1281 if not attrnames: 1282 1283 # Build a descriptive location based on the original 1284 # details, exposing the final attribute name. 1285 1286 oa, attrname = original_accessor[:-1], original_accessor[-1] 1287 oa = ".".join(oa) 1288 1289 access_location = (location, oa, attrname, 0) 1290 accessor_location = (location, oa, None, 0) 1291 self.access_index[access_location] = [accessor_location] 1292 1293 self.init_access_details(access_location) 1294 self.init_definition_details(accessor_location) 1295 1296 # Obtain a reference for the accessor in order to properly 1297 # determine its type. 1298 1299 if ref.get_kind() != "<instance>": 1300 objpath = ref.get_origin() 1301 1302 objpath = objpath.rsplit(".", 1)[0] 1303 1304 # Where the object name conflicts with the module 1305 # providing it, obtain the module details. 1306 1307 if objpath in self.importer.modules: 1308 accessor = Reference("<module>", objpath) 1309 else: 1310 accessor = self.importer.get_object(objpath) 1311 1312 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1313 self.access_constrained.add(access_location) 1314 1315 class_types, instance_types, module_types = accessor.get_types() 1316 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1317 1318 else: 1319 1320 # Build a descriptive location based on the original 1321 # details, employing the first remaining attribute name. 1322 1323 l = get_attrnames(attrnames) 1324 attrname = l[0] 1325 1326 oa = original_accessor[:-len(l)] 1327 oa = ".".join(oa) 1328 1329 access_location = (location, oa, attrnames, 0) 1330 accessor_location = (location, oa, None, 0) 1331 self.access_index[access_location] = [accessor_location] 1332 1333 self.init_access_details(access_location) 1334 self.init_definition_details(accessor_location) 1335 1336 class_types, instance_types, module_types = ref.get_types() 1337 1338 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1339 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1340 1341 # Define mappings between the original and access locations 1342 # so that translation can work from the source details. 1343 1344 original_location = (location, original_name, original_attrnames, 0) 1345 1346 if original_location != access_location: 1347 self.const_accesses[original_location] = access_location 1348 self.const_accesses_rev[access_location] = original_location 1349 1350 # Aliased name definitions. All aliases with usage will have been 1351 # defined, but they may be refined according to referenced accesses. 1352 1353 for accessor_location in self.alias_index.keys(): 1354 self.record_types_for_alias(accessor_location) 1355 1356 # Update accesses employing aliases. 1357 1358 for access_location in alias_accesses: 1359 self.record_types_for_access(access_location, self.access_index[access_location]) 1360 1361 def constrain_types(self, path, class_types, instance_types, module_types): 1362 1363 """ 1364 Using the given 'path' to an object, constrain the given 'class_types', 1365 'instance_types' and 'module_types'. 1366 1367 Return the class, instance, module types plus whether the types are 1368 constrained to a specific kind of type. 1369 """ 1370 1371 ref = self.importer.identify(path) 1372 if ref: 1373 1374 # Constrain usage suggestions using the identified object. 1375 1376 if ref.has_kind("<class>"): 1377 return ( 1378 set(class_types).intersection([ref.get_origin()]), [], [], True 1379 ) 1380 elif ref.has_kind("<module>"): 1381 return ( 1382 [], [], set(module_types).intersection([ref.get_origin()]), True 1383 ) 1384 1385 return class_types, instance_types, module_types, False 1386 1387 def get_target_types(self, location, usage): 1388 1389 """ 1390 Return the class, instance and module types constrained for the name at 1391 the given 'location' exhibiting the given 'usage'. Whether the types 1392 have been constrained using contextual information is also indicated, 1393 plus whether the types have been constrained to a specific kind of type. 1394 """ 1395 1396 unit_path, name, attrnames, version = location 1397 have_assignments = get_assigned_attributes(usage) 1398 1399 # Detect any initialised name for the location. 1400 1401 if name: 1402 ref = self.get_initialised_name(location) 1403 if ref: 1404 (class_types, only_instance_types, module_types, 1405 _function_types, _var_types) = separate_types([ref]) 1406 return class_types, only_instance_types, module_types, True, have_assignments 1407 1408 # Retrieve the recorded types for the usage. 1409 1410 class_types = self.get_class_types_for_usage(usage) 1411 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1412 module_types = self.get_module_types_for_usage(usage) 1413 1414 # Merge usage deductions with observations to obtain reference types 1415 # for names involved with attribute accesses. 1416 1417 if not name: 1418 return class_types, only_instance_types, module_types, False, have_assignments 1419 1420 # Obtain references to known objects. 1421 1422 path = get_name_path(unit_path, name) 1423 1424 class_types, only_instance_types, module_types, constrained_specific = \ 1425 self.constrain_types(path, class_types, only_instance_types, module_types) 1426 1427 if constrained_specific: 1428 return class_types, only_instance_types, module_types, constrained_specific, \ 1429 constrained_specific or have_assignments 1430 1431 # Constrain "self" references. 1432 1433 if name == "self": 1434 1435 # Test for the class of the method in the deduced types. 1436 1437 class_name = self.in_method(unit_path) 1438 1439 if class_name and class_name not in class_types and class_name not in only_instance_types: 1440 raise DeduceError("In %s, usage {%s} is not directly supported by class %s or its instances." % 1441 (unit_path, encode_usage(usage), class_name)) 1442 1443 # Constrain the types to the class's hierarchy. 1444 1445 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1446 if t: 1447 class_types, only_instance_types, module_types, constrained = t 1448 return class_types, only_instance_types, module_types, constrained, have_assignments 1449 1450 return class_types, only_instance_types, module_types, False, have_assignments 1451 1452 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1453 1454 """ 1455 Where the name "self" appears in a method, attempt to constrain the 1456 classes involved. 1457 1458 Return the class, instance, module types plus whether the types are 1459 constrained. 1460 """ 1461 1462 class_name = self.in_method(unit_path) 1463 1464 if not class_name: 1465 return None 1466 1467 classes = set([class_name]) 1468 classes.update(self.get_descendants_for_class(class_name)) 1469 1470 # Note that only instances will be expected for these references but 1471 # either classes or instances may provide the attributes. 1472 1473 return ( 1474 set(class_types).intersection(classes), 1475 set(only_instance_types).intersection(classes), 1476 [], True 1477 ) 1478 1479 def in_method(self, path): 1480 1481 "Return whether 'path' refers to a method." 1482 1483 class_name, method_name = path.rsplit(".", 1) 1484 return class_name != "__builtins__.core.type" and self.importer.classes.has_key(class_name) and class_name 1485 1486 def init_reference_details(self, location): 1487 1488 "Initialise reference-related details for 'location'." 1489 1490 self.init_definition_details(location) 1491 self.init_access_details(location) 1492 1493 def init_definition_details(self, location): 1494 1495 "Initialise name definition details for 'location'." 1496 1497 self.accessor_class_types[location] = set() 1498 self.accessor_instance_types[location] = set() 1499 self.accessor_module_types[location] = set() 1500 self.provider_class_types[location] = set() 1501 self.provider_instance_types[location] = set() 1502 self.provider_module_types[location] = set() 1503 1504 def init_access_details(self, location): 1505 1506 "Initialise access details at 'location'." 1507 1508 self.referenced_attrs[location] = {} 1509 1510 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1511 1512 """ 1513 Define types for the 'access_location' associated with the given 1514 'accessor_locations'. 1515 """ 1516 1517 attrname = get_attrname_from_location(access_location) 1518 if not attrname: 1519 return 1520 1521 # Collect all suggested types for the accessors. Accesses may 1522 # require accessors from of a subset of the complete set of types. 1523 1524 class_types = set() 1525 module_types = set() 1526 instance_types = set() 1527 1528 constrained = True 1529 1530 for location in accessor_locations: 1531 1532 # Remember accesses employing aliases. 1533 1534 if alias_accesses is not None and self.alias_index.has_key(location): 1535 alias_accesses.add(access_location) 1536 1537 # Use the type information deduced for names from above. 1538 1539 if self.accessor_class_types.has_key(location): 1540 class_types.update(self.accessor_class_types[location]) 1541 module_types.update(self.accessor_module_types[location]) 1542 instance_types.update(self.accessor_instance_types[location]) 1543 1544 # Where accesses are associated with assignments but where no 1545 # attribute usage observations have caused such an association, 1546 # the attribute name is considered by itself. 1547 1548 else: 1549 self.init_definition_details(location) 1550 self.record_types_for_usage(location, [(attrname, False, False)]) 1551 1552 constrained = location in self.accessor_constrained and constrained 1553 1554 self.init_access_details(access_location) 1555 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1556 1557 def record_types_for_usage(self, accessor_location, usage): 1558 1559 """ 1560 Record types for the given 'accessor_location' according to the given 1561 'usage' observations which may be None to indicate an absence of usage. 1562 """ 1563 1564 (class_types, 1565 instance_types, 1566 module_types, 1567 constrained, 1568 constrained_specific) = self.get_target_types(accessor_location, usage) 1569 1570 invocations = get_invoked_attributes(usage) 1571 1572 self.record_reference_types(accessor_location, class_types, instance_types, 1573 module_types, constrained, constrained_specific, invocations) 1574 1575 def record_types_for_attribute(self, access_location, attrname): 1576 1577 """ 1578 Record types for the 'access_location' employing only the given 1579 'attrname' for type deduction. 1580 """ 1581 1582 (class_types, 1583 only_instance_types, 1584 module_types) = self.get_types_for_attribute(attrname) 1585 1586 self.init_reference_details(access_location) 1587 1588 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1589 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1590 1591 def get_types_for_attribute(self, attrname): 1592 1593 "Return class, instance-only and module types supporting 'attrname'." 1594 1595 usage = ((attrname, False, False),) 1596 1597 class_types = self.get_class_types_for_usage(usage) 1598 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1599 module_types = self.get_module_types_for_usage(usage) 1600 1601 return class_types, only_instance_types, module_types 1602 1603 def record_types_for_alias(self, accessor_location): 1604 1605 """ 1606 Define types for the 'accessor_location' not having associated usage. 1607 """ 1608 1609 have_access = self.provider_class_types.has_key(accessor_location) 1610 1611 # With an access, attempt to narrow the existing selection of provider 1612 # types. Invocations attempt to find return value information, with 1613 # instance return values also yielding class providers (since attributes 1614 # on instances could be provided by classes). 1615 1616 if have_access: 1617 provider_class_types = self.provider_class_types[accessor_location] 1618 provider_instance_types = self.provider_instance_types[accessor_location] 1619 provider_module_types = self.provider_module_types[accessor_location] 1620 1621 # Find details for any corresponding access. 1622 1623 all_class_types = set() 1624 all_instance_types = set() 1625 all_module_types = set() 1626 1627 for access_location in self.alias_index[accessor_location]: 1628 location, name, attrnames, access_number = access_location 1629 invocation = self.reference_invocations.get(access_location) 1630 1631 attrnames = attrnames and attrnames.split(".") 1632 remaining = attrnames and len(attrnames) > 1 1633 1634 # Alias has remaining attributes: reference details do not 1635 # correspond to the accessor; the remaining attributes would 1636 # need to be traversed first. 1637 1638 if remaining: 1639 return 1640 1641 # Alias references an attribute access. 1642 1643 if attrnames: 1644 1645 # Obtain references and attribute types for the access. 1646 1647 attrs = self.get_references_for_access(access_location) 1648 attrs = self.convert_invocation_providers(attrs, invocation) 1649 1650 (class_types, instance_types, module_types, function_types, 1651 var_types) = separate_types(attrs) 1652 1653 # Where non-accessor types are found, do not attempt to refine 1654 # the defined accessor types. 1655 1656 if function_types or var_types: 1657 return 1658 1659 # Invocations converting class accessors to instances do not 1660 # change the nature of class providers. 1661 1662 class_types = set(provider_class_types).intersection(class_types) 1663 instance_types = set(provider_instance_types).intersection(instance_types) 1664 module_types = set(provider_module_types).intersection(module_types) 1665 1666 # Alias references a name, not an access. 1667 1668 else: 1669 # Attempt to refine the types using initialised names. 1670 1671 attr = self.get_initialised_name(access_location) 1672 if attr: 1673 attrs = self.convert_invocation_providers([attr], invocation) 1674 1675 (class_types, instance_types, module_types, function_types, 1676 var_types) = separate_types(attrs) 1677 1678 # Where no further information is found, do not attempt to 1679 # refine the defined accessor types. 1680 1681 else: 1682 return 1683 1684 all_class_types.update(class_types) 1685 all_instance_types.update(instance_types) 1686 all_module_types.update(module_types) 1687 1688 # Record refined type details for the alias as an accessor. 1689 1690 self.init_definition_details(accessor_location) 1691 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1692 1693 # Without an access, attempt to identify references for the alias. 1694 # Invocations convert classes to instances and also attempt to find 1695 # return value information. 1696 1697 else: 1698 refs = set() 1699 1700 for access_location in self.alias_index[accessor_location]: 1701 1702 # Obtain any redefined constant access location. 1703 1704 if self.const_accesses.has_key(access_location): 1705 access_location = self.const_accesses[access_location] 1706 1707 location, name, attrnames, access_number = access_location 1708 invocation = self.reference_invocations.get(access_location) 1709 1710 attrnames = attrnames and attrnames.split(".") 1711 remaining = attrnames and len(attrnames) > 1 1712 1713 # Alias has remaining attributes: reference details do not 1714 # correspond to the accessor; the remaining attributes would 1715 # need to be traversed first. 1716 1717 if remaining: 1718 return 1719 1720 # Alias references an attribute access. 1721 1722 attrname = attrnames and attrnames[0] 1723 1724 if attrname: 1725 1726 # Obtain references and attribute types for the access. 1727 1728 attrs = self.get_references_for_access(access_location) 1729 attrs = self.convert_invocations(attrs, invocation) 1730 refs.update(attrs) 1731 1732 # Alias references a name, not an access. 1733 1734 else: 1735 1736 # Obtain initialiser information. 1737 1738 attr = self.get_initialised_name(access_location) 1739 if attr: 1740 refs.update(self.convert_invocations([attr], invocation)) 1741 1742 # Obtain provider information. 1743 1744 elif self.provider_class_types.has_key(access_location): 1745 class_types = self.provider_class_types[access_location] 1746 instance_types = self.provider_instance_types[access_location] 1747 module_types = self.provider_module_types[access_location] 1748 1749 refs.update(combine_types(class_types, instance_types, module_types)) 1750 1751 # Record reference details for the alias separately from accessors. 1752 1753 self.referenced_objects[accessor_location] = refs 1754 1755 def get_references_for_access(self, access_location): 1756 1757 "Return the references identified for 'access_location'." 1758 1759 attrs = [] 1760 for attrtype, object_type, attr in self.referenced_attrs[access_location]: 1761 attrs.append(attr) 1762 return attrs 1763 1764 def convert_invocation_providers(self, refs, invocation): 1765 1766 """ 1767 Convert 'refs' to providers corresponding to the results of invoking 1768 each of the given references, if 'invocation' is set to a true value. 1769 """ 1770 1771 if not invocation: 1772 return refs 1773 1774 providers = set() 1775 1776 for ref in refs: 1777 ref = self.convert_invocation_provider(ref) 1778 if ref.has_kind("<instance>"): 1779 providers.add(Reference("<class>", ref.get_origin())) 1780 providers.add(ref) 1781 1782 return providers 1783 1784 def convert_invocation_provider(self, ref): 1785 1786 "Convert 'ref' to a provider appropriate to its invocation result." 1787 1788 if ref: 1789 if ref.has_kind("<class>"): 1790 return ref 1791 elif ref.has_kind("<function>"): 1792 refs = self.importer.all_return_values.get(ref.get_origin()) 1793 if refs and len(refs) == 1: 1794 return first(refs) 1795 1796 return Reference("<var>") 1797 1798 def convert_invocations(self, refs, invocation): 1799 1800 """ 1801 Convert 'refs' to invocation results if 'invocation' is set to a true 1802 value. 1803 """ 1804 1805 return invocation and map(self.convert_invocation, refs) or refs 1806 1807 def convert_invocation(self, ref): 1808 1809 "Convert 'ref' to its invocation result." 1810 1811 if ref: 1812 if ref.has_kind("<class>"): 1813 return ref.instance_of() 1814 elif ref.has_kind("<function>"): 1815 refs = self.importer.all_return_values.get(ref.get_origin()) 1816 if refs and len(refs) == 1: 1817 return first(refs) 1818 1819 return Reference("<var>") 1820 1821 def get_initialised_name(self, access_location): 1822 1823 """ 1824 Return references for any initialised names at 'access_location', or 1825 None if no such references exist. 1826 """ 1827 1828 path, name, attrnames, version = access_location 1829 1830 # Use initialiser information, if available. 1831 1832 refs = self.importer.all_initialised_names.get((path, name)) 1833 if refs and refs.has_key(version): 1834 return refs[version] 1835 else: 1836 return None 1837 1838 def record_reference_types(self, location, class_types, instance_types, 1839 module_types, constrained, constrained_specific=False, invocations=None): 1840 1841 """ 1842 Associate attribute provider types with the given 'location', consisting 1843 of the given 'class_types', 'instance_types' and 'module_types'. 1844 1845 If 'constrained' is indicated, the constrained nature of the accessor is 1846 recorded for the location. 1847 1848 If 'constrained_specific' is indicated using a true value, instance types 1849 will not be added to class types to permit access via instances at the 1850 given location. This is only useful where a specific accessor is known 1851 to be a class. 1852 1853 If 'invocations' is given, the given attribute names indicate those 1854 which are involved in invocations. Such invocations, if involving 1855 functions, will employ those functions as bound methods and will 1856 therefore not support classes as accessors, only instances of such 1857 classes. 1858 1859 Note that the specified types only indicate the provider types for 1860 attributes, whereas the recorded accessor types indicate the possible 1861 types of the actual objects used to access attributes. 1862 """ 1863 1864 # Update the type details for the location. 1865 1866 self.provider_class_types[location].update(class_types) 1867 self.provider_instance_types[location].update(instance_types) 1868 self.provider_module_types[location].update(module_types) 1869 1870 # Class types support classes and instances as accessors. 1871 # Instance-only and module types support only their own kinds as 1872 # accessors. 1873 1874 path, name, version, attrnames = location 1875 1876 if invocations: 1877 class_only_types = self.filter_for_invocations(class_types, invocations) 1878 else: 1879 class_only_types = class_types 1880 1881 # However, the nature of accessors can be further determined. 1882 # Any self variable may only refer to an instance. 1883 1884 if name != "self" or not self.in_method(path): 1885 self.accessor_class_types[location].update(class_only_types) 1886 1887 if not constrained_specific: 1888 self.accessor_instance_types[location].update(class_types) 1889 1890 self.accessor_instance_types[location].update(instance_types) 1891 1892 if name != "self" or not self.in_method(path): 1893 self.accessor_module_types[location].update(module_types) 1894 1895 if constrained: 1896 self.accessor_constrained.add(location) 1897 1898 def filter_for_invocations(self, class_types, attrnames): 1899 1900 """ 1901 From the given 'class_types', identify methods for the given 1902 'attrnames' that are being invoked, returning a filtered collection of 1903 class types. 1904 1905 This method may be used to remove class types from consideration where 1906 their attributes are methods that are directly invoked: method 1907 invocations must involve instance accessors. 1908 """ 1909 1910 to_filter = set() 1911 1912 for class_type in class_types: 1913 for attrname in attrnames: 1914 1915 # Attempt to obtain a class attribute of the given name. This 1916 # may return an attribute provided by an ancestor class. 1917 1918 ref = self.importer.get_class_attribute(class_type, attrname) 1919 parent_class = ref and ref.parent() 1920 1921 # If such an attribute is a method and would be available on 1922 # the given class, record the class for filtering. 1923 1924 if ref and ref.has_kind("<function>") and ( 1925 parent_class == class_type or 1926 class_type in self.descendants[parent_class]): 1927 1928 to_filter.add(class_type) 1929 break 1930 1931 return set(class_types).difference(to_filter) 1932 1933 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1934 1935 """ 1936 Identify reference attributes, associating them with the given 1937 'location', identifying the given 'attrname', employing the given 1938 'class_types', 'instance_types' and 'module_types'. 1939 1940 If 'constrained' is indicated, the constrained nature of the access is 1941 recorded for the location. 1942 """ 1943 1944 # Record the referenced objects. 1945 1946 self.referenced_attrs[location] = \ 1947 self._identify_reference_attribute(location, attrname, class_types, instance_types, module_types) 1948 1949 if constrained: 1950 self.access_constrained.add(location) 1951 1952 def _identify_reference_attribute(self, location, attrname, class_types, instance_types, module_types): 1953 1954 """ 1955 Identify the reference attribute at the given access 'location', using 1956 the given 'attrname', and employing the given 'class_types', 1957 'instance_types' and 'module_types'. 1958 """ 1959 1960 attrs = set() 1961 1962 # The class types expose class attributes either directly or via 1963 # instances. 1964 1965 for object_type in class_types: 1966 ref = self.importer.get_class_attribute(object_type, attrname) 1967 if ref and self.is_compatible_callable(location, object_type, ref): 1968 attrs.add(("<class>", object_type, ref)) 1969 1970 # Add any distinct instance attributes that would be provided 1971 # by instances also providing indirect class attribute access. 1972 1973 for ref in self.importer.get_instance_attributes(object_type, attrname): 1974 if self.is_compatible_callable(location, object_type, ref): 1975 attrs.add(("<instance>", object_type, ref)) 1976 1977 # The instance-only types expose instance attributes, but although 1978 # classes are excluded as potential accessors (since they do not provide 1979 # the instance attributes), the class types may still provide some 1980 # attributes. 1981 1982 for object_type in instance_types: 1983 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1984 1985 if instance_attrs: 1986 for ref in instance_attrs: 1987 if self.is_compatible_callable(location, object_type, ref): 1988 attrs.add(("<instance>", object_type, ref)) 1989 else: 1990 ref = self.importer.get_class_attribute(object_type, attrname) 1991 if ref and self.is_compatible_callable(location, object_type, ref): 1992 attrs.add(("<class>", object_type, ref)) 1993 1994 # Module types expose module attributes for module accessors. 1995 1996 for object_type in module_types: 1997 ref = self.importer.get_module_attribute(object_type, attrname) 1998 if ref and self.is_compatible_callable(location, object_type, ref): 1999 attrs.add(("<module>", object_type, ref)) 2000 2001 return attrs 2002 2003 def is_compatible_callable(self, location, object_type, ref): 2004 2005 """ 2006 Return whether any invocation at 'location' involving an attribute of 2007 'object_type' identified by 'ref' is compatible with any arguments used. 2008 """ 2009 2010 invocation = self.reference_invocations.get(location) 2011 if invocation is None: 2012 return True 2013 2014 objpath = ref.get_origin() 2015 if not objpath: 2016 return True 2017 2018 parameters = self.importer.function_parameters.get(objpath) 2019 if not parameters: 2020 return True 2021 2022 defaults = self.importer.function_defaults.get(objpath) 2023 arguments, keywords = invocation 2024 names = set(parameters) 2025 2026 # Determine whether the specified arguments are 2027 # compatible with the callable signature. 2028 2029 if arguments >= len(parameters) - len(defaults) and \ 2030 arguments <= len(parameters) and \ 2031 names.issuperset(keywords): 2032 2033 return True 2034 else: 2035 init_item(self.reference_invocations_unsuitable, location, set) 2036 self.reference_invocations_unsuitable[location].add(ref) 2037 return False 2038 2039 # Attribute access plan formulation. 2040 2041 class_tests = ( 2042 ("guarded", "specific", "type"), 2043 ("guarded", "common", "type"), 2044 ("test", "specific", "type"), 2045 ("test", "common", "type"), 2046 ) 2047 2048 def get_access_plan(self, location): 2049 2050 """ 2051 Return details of the access at the given 'location'. The details are as 2052 follows: 2053 2054 * the initial accessor (from which accesses will be performed if no 2055 computed static accessor is found) 2056 * details of any test required on the initial accessor 2057 * details of any type employed by the test 2058 * any static accessor (from which accesses will be performed in 2059 preference to the initial accessor) 2060 * attributes needing to be traversed from the base that yield 2061 unambiguous objects 2062 * access modes for each of the unambiguously-traversed attributes 2063 * remaining attributes needing to be tested and traversed 2064 * details of the context 2065 * any test to apply to the context 2066 * the method of obtaining the first attribute 2067 * the method of obtaining the final attribute 2068 * any static final attribute 2069 * the kinds of objects providing the final attribute 2070 """ 2071 2072 const_access = self.const_accesses_rev.get(location) 2073 2074 path, name, attrnames, version = location 2075 remaining = attrnames.split(".") 2076 attrname = remaining[0] 2077 2078 # Obtain reference, provider and provider kind information. 2079 2080 attrs = self.reference_all_attrs[location] 2081 provider_types = self.reference_all_providers[location] 2082 provider_kinds = self.reference_all_provider_kinds[location] 2083 2084 # Obtain accessor type and kind information. 2085 2086 accessor_types = self.reference_all_accessor_types[location] 2087 accessor_general_types = self.reference_all_accessor_general_types[location] 2088 accessor_kinds = get_kinds(accessor_general_types) 2089 2090 # Determine any guard or test requirements. 2091 2092 constrained = location in self.access_constrained 2093 test = self.reference_test_types[location] 2094 test_type = self.reference_test_accessor_type.get(location) 2095 2096 # Determine the accessor and provider properties. 2097 2098 class_accessor = "<class>" in accessor_kinds 2099 module_accessor = "<module>" in accessor_kinds 2100 instance_accessor = "<instance>" in accessor_kinds 2101 provided_by_class = "<class>" in provider_kinds 2102 provided_by_instance = "<instance>" in provider_kinds 2103 2104 # Determine how attributes may be accessed relative to the accessor. 2105 2106 object_relative = class_accessor or module_accessor or provided_by_instance 2107 class_relative = instance_accessor and provided_by_class 2108 2109 # Identify the last static attribute for context acquisition. 2110 2111 base = None 2112 dynamic_base = None 2113 2114 # Constant accesses have static providers. 2115 2116 if const_access: 2117 base = len(provider_types) == 1 and first(provider_types) 2118 2119 # Name-based accesses. 2120 2121 elif name: 2122 ref = self.importer.identify("%s.%s" % (path, name)) 2123 2124 # Constant accessors are static. 2125 2126 if ref and ref.static(): 2127 base = ref.get_origin() 2128 2129 # Usage of previously-generated guard and test details. 2130 2131 elif test[:2] == ("constrained", "specific"): 2132 ref = first(accessor_types) 2133 2134 elif test[:2] == ("constrained", "common"): 2135 ref = first(accessor_general_types) 2136 2137 elif test[:2] == ("guarded", "specific"): 2138 ref = first(accessor_types) 2139 2140 elif test[:2] == ("guarded", "common"): 2141 ref = first(accessor_general_types) 2142 2143 # For attribute-based tests, tentatively identify a dynamic base. 2144 # Such tests allow single or multiple kinds of a type. 2145 2146 elif test[0] == "test" and test[1] in ("common", "specific"): 2147 dynamic_base = test_type 2148 2149 # Static accessors. 2150 2151 if not base and test in self.class_tests: 2152 base = ref and ref.get_origin() or dynamic_base 2153 2154 # Accessors that are not static but whose nature is determined. 2155 2156 elif not base and ref: 2157 dynamic_base = ref.get_origin() 2158 2159 # Determine initial accessor details. 2160 2161 accessor = base or dynamic_base 2162 accessor_kind = len(accessor_kinds) == 1 and first(accessor_kinds) or None 2163 provider_kind = len(provider_kinds) == 1 and first(provider_kinds) or None 2164 2165 # Traverse remaining attributes. 2166 2167 traversed = [] 2168 traversal_modes = [] 2169 2170 while len(attrs) == 1 and not first(attrs).has_kind("<var>"): 2171 attr = first(attrs) 2172 2173 traversed.append(attrname) 2174 traversal_modes.append(accessor_kind == provider_kind and "object" or "class") 2175 2176 # Consume attribute names providing unambiguous attributes. 2177 2178 del remaining[0] 2179 2180 if not remaining: 2181 break 2182 2183 # Update the last static attribute. 2184 2185 if attr.static(): 2186 base = attr.get_origin() 2187 traversed = [] 2188 traversal_modes = [] 2189 2190 # Get the access details. 2191 2192 attrname = remaining[0] 2193 accessor = attr.get_origin() 2194 accessor_kind = attr.get_kind() 2195 provider_kind = self.importer.get_attribute_provider(attr, attrname) 2196 accessor_kinds = [accessor_kind] 2197 provider_kinds = [provider_kind] 2198 2199 # Get the next attribute. 2200 2201 attrs = self.importer.get_attributes(attr, attrname) 2202 2203 # Where many attributes are suggested, no single attribute identity can 2204 # be loaded. 2205 2206 else: 2207 attr = None 2208 2209 # Determine the method of access. 2210 2211 is_assignment = location in self.reference_assignments or const_access in self.reference_assignments 2212 is_invocation = location in self.reference_invocations or const_access in self.reference_invocations 2213 2214 # Identified attribute that must be accessed via its parent. 2215 2216 if attr and attr.get_name() and is_assignment: 2217 final_method = "static-assign"; origin = attr.get_name() 2218 2219 # Static, identified attribute. 2220 2221 elif attr and attr.static(): 2222 final_method = is_assignment and "static-assign" or \ 2223 is_invocation and "static-invoke" or \ 2224 "static" 2225 origin = attr.final() 2226 2227 # All other methods of access involve traversal. 2228 2229 else: 2230 final_method = is_assignment and "assign" or \ 2231 is_invocation and "access-invoke" or \ 2232 "access" 2233 origin = None 2234 2235 # First attribute accessed at a known position via the accessor. 2236 2237 # Static bases support object-relative accesses only. 2238 2239 if base: 2240 first_method = "relative-object" 2241 2242 # Dynamic bases support either object- or class-relative accesses. 2243 2244 elif dynamic_base: 2245 first_method = "relative" + (object_relative and "-object" or "") + \ 2246 (class_relative and "-class" or "") 2247 2248 # The fallback case is always run-time testing and access. 2249 2250 else: 2251 first_method = "check" + (object_relative and "-object" or "") + \ 2252 (class_relative and "-class" or "") 2253 2254 # Determine whether an unbound method is being accessed via an instance, 2255 # requiring a context test. 2256 2257 context_test = "ignore" 2258 2259 # Assignments do not employ the context. 2260 2261 if is_assignment: 2262 pass 2263 2264 # Obtain a selection of possible attributes if no unambiguous attribute 2265 # was identified. 2266 2267 elif not attr: 2268 2269 # Use previously-deduced attributes for a simple ambiguous access. 2270 # Otherwise, use the final attribute name to obtain possible 2271 # attributes. 2272 2273 if len(remaining) > 1: 2274 attrname = remaining[-1] 2275 2276 (class_types, 2277 only_instance_types, 2278 module_types) = self.get_types_for_attribute(attrname) 2279 2280 accessor_kinds = set() 2281 provider_kinds = set() 2282 2283 if class_types: 2284 accessor_kinds.add("<class>") 2285 accessor_kinds.add("<instance>") 2286 provider_kinds.add("<class>") 2287 if only_instance_types: 2288 accessor_kinds.add("<instance>") 2289 provider_kinds.add("<instance>") 2290 if module_types: 2291 accessor_kinds.add("<module>") 2292 provider_kinds.add("<module>") 2293 2294 attrs = set() 2295 for type in combine_types(class_types, only_instance_types, module_types): 2296 attrs.update(self.importer.get_attributes(type, attrname)) 2297 2298 always_unbound = True 2299 have_function = False 2300 have_var = False 2301 2302 # Determine whether all attributes are unbound methods and whether 2303 # functions or unidentified attributes occur. 2304 2305 for attr in attrs: 2306 always_unbound = always_unbound and attr.has_kind("<function>") and attr.name_parent() == attr.parent() 2307 have_function = have_function or attr.has_kind("<function>") 2308 have_var = have_var or attr.has_kind("<var>") 2309 2310 # Test for class-via-instance accesses. 2311 2312 if accessor_kind == "<instance>" and \ 2313 provider_kind == "<class>": 2314 2315 if always_unbound: 2316 context_test = "replace" 2317 else: 2318 context_test = "test" 2319 2320 # Test for the presence of class-via-instance accesses. 2321 2322 elif "<instance>" in accessor_kinds and \ 2323 "<class>" in provider_kinds and \ 2324 (have_function or have_var): 2325 2326 context_test = "test" 2327 2328 # With an unambiguous attribute, determine whether a test is needed. 2329 2330 elif accessor_kind == "<instance>" and \ 2331 provider_kind == "<class>" and \ 2332 (attr.has_kind("<var>") or 2333 attr.has_kind("<function>") and 2334 attr.name_parent() == attr.parent()): 2335 2336 if attr.has_kind("<var>"): 2337 context_test = "test" 2338 else: 2339 context_test = "replace" 2340 2341 # With an unambiguous attribute with ambiguity in the access method, 2342 # generate a test. 2343 2344 elif "<instance>" in accessor_kinds and \ 2345 "<class>" in provider_kinds and \ 2346 (attr.has_kind("<var>") or 2347 attr.has_kind("<function>") and 2348 attr.name_parent() == attr.parent()): 2349 2350 context_test = "test" 2351 2352 # Determine the nature of the context. 2353 2354 context = context_test == "ignore" and "unset" or \ 2355 len(traversed + remaining) == 1 and \ 2356 (base and "base" or "original-accessor") or \ 2357 "final-accessor" 2358 2359 return name, test, test_type, base, \ 2360 traversed, traversal_modes, remaining, \ 2361 context, context_test, \ 2362 first_method, final_method, \ 2363 origin, accessor_kinds 2364 2365 def initialise_access_instructions(self): 2366 2367 "Expand access plans into instruction sequences." 2368 2369 for access_location, access_plan in self.access_plans.items(): 2370 2371 # Obtain the access details. 2372 2373 name, test, test_type, base, \ 2374 traversed, traversal_modes, attrnames, \ 2375 context, context_test, \ 2376 first_method, final_method, \ 2377 origin, accessor_kinds = access_plan 2378 2379 # Emit instructions by appending them to a list. 2380 2381 instructions = [] 2382 emit = instructions.append 2383 2384 # Identify any static original accessor. 2385 2386 if base: 2387 original_accessor = base 2388 2389 # Employ names as contexts unless the context needs testing and 2390 # potentially updating. In such cases, temporary context storage is 2391 # used instead. 2392 2393 elif name and not (context_test == "test" and 2394 final_method in ("access-invoke", "static-invoke")): 2395 original_accessor = "<name>" # refers to the name 2396 2397 # Use a generic placeholder representing the access expression in 2398 # the general case. 2399 2400 else: 2401 original_accessor = "<expr>" 2402 2403 # Prepare for any first attribute access. 2404 2405 if traversed: 2406 attrname = traversed[0] 2407 del traversed[0] 2408 elif attrnames: 2409 attrname = attrnames[0] 2410 del attrnames[0] 2411 2412 # Perform the first access explicitly if at least one operation 2413 # requires it. 2414 2415 access_first_attribute = final_method in ("access", "access-invoke", "assign") or traversed or attrnames 2416 2417 # Determine whether the first access involves assignment. 2418 2419 assigning = not traversed and not attrnames and final_method == "assign" 2420 set_accessor = assigning and "<set_target_accessor>" or "<set_accessor>" 2421 stored_accessor = assigning and "<target_accessor>" or "<accessor>" 2422 2423 # Set the context if already available. 2424 2425 context_var = None 2426 2427 if context == "base": 2428 accessor = context_var = (base,) 2429 elif context == "original-accessor": 2430 2431 # Prevent re-evaluation of any dynamic expression by storing it. 2432 2433 if original_accessor == "<expr>": 2434 if final_method in ("access-invoke", "static-invoke"): 2435 emit(("<set_context>", original_accessor)) 2436 accessor = context_var = ("<context>",) 2437 else: 2438 emit((set_accessor, original_accessor)) 2439 accessor = context_var = (stored_accessor,) 2440 else: 2441 accessor = context_var = (original_accessor,) 2442 2443 # Assigning does not set the context. 2444 2445 elif context in ("final-accessor", "unset") and access_first_attribute: 2446 2447 # Prevent re-evaluation of any dynamic expression by storing it. 2448 2449 if original_accessor == "<expr>": 2450 emit((set_accessor, original_accessor)) 2451 accessor = (stored_accessor,) 2452 else: 2453 accessor = (original_accessor,) 2454 2455 # Apply any test. 2456 2457 if test[0] == "test": 2458 accessor = ("__%s_%s_%s" % test, accessor, test_type) 2459 2460 # Perform the first or final access. 2461 # The access only needs performing if the resulting accessor is used. 2462 2463 remaining = len(traversed + attrnames) 2464 2465 if access_first_attribute: 2466 2467 if first_method == "relative-class": 2468 if assigning: 2469 emit(("__store_via_class", accessor, attrname, "<assexpr>")) 2470 else: 2471 accessor = ("__load_via_class", accessor, attrname) 2472 2473 elif first_method == "relative-object": 2474 if assigning: 2475 emit(("__store_via_object", accessor, attrname, "<assexpr>")) 2476 else: 2477 accessor = ("__load_via_object", accessor, attrname) 2478 2479 elif first_method == "relative-object-class": 2480 if assigning: 2481 emit(("__get_class_and_store", accessor, attrname, "<assexpr>")) 2482 else: 2483 accessor = ("__get_class_and_load", accessor, attrname) 2484 2485 elif first_method == "check-class": 2486 if assigning: 2487 emit(("__check_and_store_via_class", accessor, attrname, "<assexpr>")) 2488 else: 2489 accessor = ("__check_and_load_via_class", accessor, attrname) 2490 2491 elif first_method == "check-object": 2492 if assigning: 2493 emit(("__check_and_store_via_object", accessor, attrname, "<assexpr>")) 2494 else: 2495 accessor = ("__check_and_load_via_object", accessor, attrname) 2496 2497 elif first_method == "check-object-class": 2498 if assigning: 2499 emit(("__check_and_store_via_any", accessor, attrname, "<assexpr>")) 2500 else: 2501 accessor = ("__check_and_load_via_any", accessor, attrname) 2502 2503 # Traverse attributes using the accessor. 2504 2505 if traversed: 2506 for attrname, traversal_mode in zip(traversed, traversal_modes): 2507 assigning = remaining == 1 and final_method == "assign" 2508 2509 # Set the context, if appropriate. 2510 2511 if remaining == 1 and final_method != "assign" and context == "final-accessor": 2512 2513 # Invoked attributes employ a separate context accessed 2514 # during invocation. 2515 2516 if final_method in ("access-invoke", "static-invoke"): 2517 emit(("<set_context>", accessor)) 2518 accessor = context_var = "<context>" 2519 2520 # A private context within the access is otherwise 2521 # retained. 2522 2523 else: 2524 emit(("<set_private_context>", accessor)) 2525 accessor = context_var = "<private_context>" 2526 2527 # Perform the access only if not achieved directly. 2528 2529 if remaining > 1 or final_method in ("access", "access-invoke", "assign"): 2530 2531 if traversal_mode == "class": 2532 if assigning: 2533 emit(("__store_via_class", accessor, attrname, "<assexpr>")) 2534 else: 2535 accessor = ("__load_via_class", accessor, attrname) 2536 else: 2537 if assigning: 2538 emit(("__store_via_object", accessor, attrname, "<assexpr>")) 2539 else: 2540 accessor = ("__load_via_object", accessor, attrname) 2541 2542 remaining -= 1 2543 2544 if attrnames: 2545 for attrname in attrnames: 2546 assigning = remaining == 1 and final_method == "assign" 2547 2548 # Set the context, if appropriate. 2549 2550 if remaining == 1 and final_method != "assign" and context == "final-accessor": 2551 2552 # Invoked attributes employ a separate context accessed 2553 # during invocation. 2554 2555 if final_method in ("access-invoke", "static-invoke"): 2556 emit(("<set_context>", accessor)) 2557 accessor = context_var = "<context>" 2558 2559 # A private context within the access is otherwise 2560 # retained. 2561 2562 else: 2563 emit(("<set_private_context>", accessor)) 2564 accessor = context_var = "<private_context>" 2565 2566 # Perform the access only if not achieved directly. 2567 2568 if remaining > 1 or final_method in ("access", "access-invoke", "assign"): 2569 2570 # Constrain instructions involving certain special 2571 # attribute names. 2572 2573 to_search = attrname == "__data__" and "object" or "any" 2574 2575 if assigning: 2576 emit(("__check_and_store_via_%s" % to_search, accessor, attrname, "<assexpr>")) 2577 else: 2578 accessor = ("__check_and_load_via_%s" % to_search, accessor, attrname) 2579 2580 remaining -= 1 2581 2582 # Define or emit the means of accessing the actual target. 2583 2584 # Assignments to known attributes. 2585 2586 if final_method == "static-assign": 2587 parent, attrname = origin.rsplit(".", 1) 2588 emit(("__store_via_object", parent, attrname, "<assexpr>")) 2589 2590 # Invoked attributes employ a separate context. 2591 2592 elif final_method in ("static", "static-invoke"): 2593 accessor = ("__load_static_ignore", origin) 2594 2595 # Wrap accesses in context operations. 2596 2597 if context_test == "test": 2598 2599 # Test and combine the context with static attribute details. 2600 2601 if final_method == "static": 2602 emit(("__load_static_test", context_var, origin)) 2603 2604 # Test the context, storing it separately if required for the 2605 # immediately invoked static attribute. 2606 2607 elif final_method == "static-invoke": 2608 emit(("<test_context_static>", context_var, origin)) 2609 2610 # Test the context, storing it separately if required for an 2611 # immediately invoked attribute. 2612 2613 elif final_method == "access-invoke": 2614 emit(("<test_context_revert>", context_var, accessor)) 2615 2616 # Test the context and update the attribute details if 2617 # appropriate. 2618 2619 else: 2620 emit(("__test_context", context_var, accessor)) 2621 2622 elif context_test == "replace": 2623 2624 # Produce an object with updated context. 2625 2626 if final_method == "static": 2627 emit(("__load_static_replace", context_var, origin)) 2628 2629 # Omit the context update operation where the target is static 2630 # and the context is recorded separately. 2631 2632 elif final_method == "static-invoke": 2633 pass 2634 2635 # If a separate context is used for an immediate invocation, 2636 # produce the attribute details unchanged. 2637 2638 elif final_method == "access-invoke": 2639 emit(accessor) 2640 2641 # Update the context in the attribute details. 2642 2643 else: 2644 emit(("__update_context", context_var, accessor)) 2645 2646 # Omit the accessor for assignments and for invocations of static 2647 # targets. 2648 2649 elif final_method not in ("assign", "static-assign", "static-invoke"): 2650 emit(accessor) 2651 2652 # Produce an advisory instruction regarding the context. 2653 2654 if context_var: 2655 emit(("<context_identity>", context_var)) 2656 2657 self.access_instructions[access_location] = instructions 2658 self.accessor_kinds[access_location] = accessor_kinds 2659 2660 # vim: tabstop=4 expandtab shiftwidth=4