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