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