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