1 #!/usr/bin/env python 2 3 """ 4 Deduce types for usage observations. 5 6 Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from common import first, get_attrname_from_location, get_attrnames, \ 23 get_invoked_attributes, get_name_path, init_item, \ 24 sorted_output, CommonOutput 25 from encoders import encode_attrnames, encode_access_location, \ 26 encode_constrained, encode_location, encode_usage, \ 27 get_kinds, test_for_kinds, test_for_type 28 from errors import DeduceError 29 from os.path import join 30 from referencing import combine_types, is_single_class_type, separate_types, \ 31 Reference 32 33 class Deducer(CommonOutput): 34 35 "Deduce types in a program." 36 37 def __init__(self, importer, output): 38 39 """ 40 Initialise an instance using the given 'importer' that will perform 41 deductions on the program information, writing the results to the given 42 'output' directory. 43 """ 44 45 self.importer = importer 46 self.output = output 47 48 # Descendants of classes. 49 50 self.descendants = {} 51 self.init_descendants() 52 self.init_special_attributes() 53 54 # Map locations to usage in order to determine specific types. 55 56 self.location_index = {} 57 58 # Map access locations to definition locations. 59 60 self.access_index = {} 61 62 # Map aliases to accesses that define them. 63 64 self.alias_index = {} 65 66 # Map constant accesses to redefined accesses. 67 68 self.const_accesses = {} 69 self.const_accesses_rev = {} 70 71 # Map usage observations to assigned attributes. 72 73 self.assigned_attrs = {} 74 75 # Map usage observations to objects. 76 77 self.attr_class_types = {} 78 self.attr_instance_types = {} 79 self.attr_module_types = {} 80 81 # Modified attributes from usage observations. 82 83 self.modified_attributes = {} 84 85 # Accesses that are assignments. 86 87 self.reference_assignments = set() 88 89 # Map locations to types, constrained indicators and attributes. 90 91 self.accessor_class_types = {} 92 self.accessor_instance_types = {} 93 self.accessor_module_types = {} 94 self.provider_class_types = {} 95 self.provider_instance_types = {} 96 self.provider_module_types = {} 97 self.accessor_constrained = set() 98 self.access_constrained = set() 99 self.referenced_attrs = {} 100 self.referenced_objects = {} 101 102 # Details of access operations. 103 104 self.access_plans = {} 105 106 # Accumulated information about accessors and providers. 107 108 self.accessor_general_class_types = {} 109 self.accessor_general_instance_types = {} 110 self.accessor_general_module_types = {} 111 self.accessor_all_types = {} 112 self.accessor_all_general_types = {} 113 self.provider_all_types = {} 114 self.accessor_guard_tests = {} 115 116 # Accumulated information about accessed attributes and 117 # access/attribute-specific accessor tests. 118 119 self.reference_all_attrs = {} 120 self.reference_all_attrtypes = {} 121 self.reference_all_accessor_types = {} 122 self.reference_all_accessor_general_types = {} 123 self.reference_test_types = {} 124 self.reference_test_accessor_type = {} 125 126 # The processing workflow itself. 127 128 self.init_usage_index() 129 self.init_accessors() 130 self.init_accesses() 131 self.init_aliases() 132 self.init_attr_type_indexes() 133 self.modify_mutated_attributes() 134 self.identify_references() 135 self.classify_accessors() 136 self.classify_accesses() 137 self.initialise_access_plans() 138 139 def to_output(self): 140 141 "Write the output files using deduction information." 142 143 self.check_output() 144 145 self.write_mutations() 146 self.write_accessors() 147 self.write_accesses() 148 self.write_access_plans() 149 150 def write_mutations(self): 151 152 """ 153 Write mutation-related output in the following format: 154 155 qualified name " " original object type 156 157 Object type can be "<class>", "<function>" or "<var>". 158 """ 159 160 f = open(join(self.output, "mutations"), "w") 161 try: 162 attrs = self.modified_attributes.items() 163 attrs.sort() 164 165 for attr, value in attrs: 166 print >>f, attr, value 167 finally: 168 f.close() 169 170 def write_accessors(self): 171 172 """ 173 Write reference-related output in the following format for types: 174 175 location " " ( "constrained" | "deduced" ) " " attribute type " " most general type names " " number of specific types 176 177 Note that multiple lines can be given for each location, one for each 178 attribute type. 179 180 Locations have the following format: 181 182 qualified name of scope "." local name ":" name version 183 184 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 185 where the latter indicates an absence of suitable references. 186 187 Type names indicate the type providing the attributes, being either a 188 class or module qualified name. 189 190 ---- 191 192 A summary of accessor types is formatted as follows: 193 194 location " " ( "constrained" | "deduced" ) " " ( "specific" | "common" | "unguarded" ) " " most general type names " " number of specific types 195 196 This summary groups all attribute types (class, instance, module) into a 197 single line in order to determine the complexity of identifying an 198 accessor. 199 200 ---- 201 202 References that cannot be supported by any types are written to a 203 warnings file in the following format: 204 205 location 206 207 ---- 208 209 For each location where a guard would be asserted to guarantee the 210 nature of an object, the following format is employed: 211 212 location " " ( "specific" | "common" ) " " object kind " " object types 213 214 Object kind can be "<class>", "<instance>" or "<module>". 215 """ 216 217 f_type_summary = open(join(self.output, "type_summary"), "w") 218 f_types = open(join(self.output, "types"), "w") 219 f_warnings = open(join(self.output, "type_warnings"), "w") 220 f_guards = open(join(self.output, "guards"), "w") 221 222 try: 223 locations = self.accessor_class_types.keys() 224 locations.sort() 225 226 for location in locations: 227 constrained = location in self.accessor_constrained 228 229 # Accessor information. 230 231 class_types = self.accessor_class_types[location] 232 instance_types = self.accessor_instance_types[location] 233 module_types = self.accessor_module_types[location] 234 235 general_class_types = self.accessor_general_class_types[location] 236 general_instance_types = self.accessor_general_instance_types[location] 237 general_module_types = self.accessor_general_module_types[location] 238 239 all_types = self.accessor_all_types[location] 240 all_general_types = self.accessor_all_general_types[location] 241 242 if class_types: 243 print >>f_types, encode_location(location), encode_constrained(constrained), "<class>", \ 244 sorted_output(general_class_types), len(class_types) 245 246 if instance_types: 247 print >>f_types, encode_location(location), encode_constrained(constrained), "<instance>", \ 248 sorted_output(general_instance_types), len(instance_types) 249 250 if module_types: 251 print >>f_types, encode_location(location), encode_constrained(constrained), "<module>", \ 252 sorted_output(general_module_types), len(module_types) 253 254 if not all_types: 255 print >>f_types, encode_location(location), "deduced", "<>", 0 256 attrnames = list(self.location_index[location]) 257 attrnames.sort() 258 print >>f_warnings, encode_location(location), "; ".join(map(encode_usage, attrnames)) 259 260 guard_test = self.accessor_guard_tests.get(location) 261 262 # Write specific type guard details. 263 264 if guard_test and guard_test.startswith("specific"): 265 print >>f_guards, encode_location(location), guard_test, \ 266 get_kinds(all_types)[0], \ 267 sorted_output(all_types) 268 269 # Write common type guard details. 270 271 elif guard_test and guard_test.startswith("common"): 272 print >>f_guards, encode_location(location), guard_test, \ 273 get_kinds(all_general_types)[0], \ 274 sorted_output(all_general_types) 275 276 print >>f_type_summary, encode_location(location), encode_constrained(constrained), \ 277 guard_test or "unguarded", sorted_output(all_general_types), len(all_types) 278 279 finally: 280 f_type_summary.close() 281 f_types.close() 282 f_warnings.close() 283 f_guards.close() 284 285 def write_accesses(self): 286 287 """ 288 Specific attribute output is produced in the following format: 289 290 location " " ( "constrained" | "deduced" ) " " attribute type " " attribute references 291 292 Note that multiple lines can be given for each location and attribute 293 name, one for each attribute type. 294 295 Locations have the following format: 296 297 qualified name of scope "." local name " " attribute name ":" access number 298 299 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 300 where the latter indicates an absence of suitable references. 301 302 Attribute references have the following format: 303 304 object type ":" qualified name 305 306 Object type can be "<class>", "<function>" or "<var>". 307 308 ---- 309 310 A summary of attributes is formatted as follows: 311 312 location " " attribute name " " ( "constrained" | "deduced" ) " " test " " attribute references 313 314 This summary groups all attribute types (class, instance, module) into a 315 single line in order to determine the complexity of each access. 316 317 Tests can be "validate", "specific", "untested", "guarded-validate" or "guarded-specific". 318 319 ---- 320 321 For each access where a test would be asserted to guarantee the 322 nature of an attribute, the following formats are employed: 323 324 location " " attribute name " " "validate" 325 location " " attribute name " " "specific" " " attribute type " " object type 326 327 ---- 328 329 References that cannot be supported by any types are written to a 330 warnings file in the following format: 331 332 location 333 """ 334 335 f_attr_summary = open(join(self.output, "attribute_summary"), "w") 336 f_attrs = open(join(self.output, "attributes"), "w") 337 f_tests = open(join(self.output, "tests"), "w") 338 f_warnings = open(join(self.output, "attribute_warnings"), "w") 339 340 try: 341 locations = self.referenced_attrs.keys() 342 locations.sort() 343 344 for location in locations: 345 constrained = location in self.access_constrained 346 347 # Attribute information, both name-based and anonymous. 348 349 referenced_attrs = self.referenced_attrs[location] 350 351 if referenced_attrs: 352 attrname = get_attrname_from_location(location) 353 354 all_accessed_attrs = self.reference_all_attrs[location] 355 356 for attrtype, attrs in self.get_referenced_attrs(location): 357 print >>f_attrs, encode_access_location(location), encode_constrained(constrained), attrtype, sorted_output(attrs) 358 359 test_type = self.reference_test_types.get(location) 360 361 # Write the need to test at run time. 362 363 if test_type == "validate": 364 print >>f_tests, encode_access_location(location), test_type 365 366 # Write any type checks for anonymous accesses. 367 368 elif test_type and self.reference_test_accessor_type.get(location): 369 print >>f_tests, encode_access_location(location), test_type, \ 370 sorted_output(all_accessed_attrs), \ 371 self.reference_test_accessor_type[location] 372 373 print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \ 374 test_type or "untested", sorted_output(all_accessed_attrs) 375 376 else: 377 print >>f_warnings, encode_access_location(location) 378 379 finally: 380 f_attr_summary.close() 381 f_attrs.close() 382 f_tests.close() 383 f_warnings.close() 384 385 def write_access_plans(self): 386 387 """ 388 Each attribute access is written out as a plan of the following form: 389 390 location " " name " " test " " test type " " base " " traversed attributes 391 " " attributes to traverse " " context " " access method 392 " " static attribute 393 """ 394 395 f_attrs = open(join(self.output, "attribute_plans"), "w") 396 397 try: 398 locations = self.access_plans.keys() 399 locations.sort() 400 401 for location in locations: 402 name, test, test_type, base, traversed, traversal_modes, attrnames, context, \ 403 first_method, final_method, attr = self.access_plans[location] 404 405 print >>f_attrs, encode_access_location(location), \ 406 name, test, test_type or "{}", \ 407 base or "{}", \ 408 ".".join(traversed) or "{}", \ 409 ".".join(traversal_modes) or "{}", \ 410 ".".join(attrnames) or "{}", \ 411 context, first_method, final_method, attr or "{}" 412 413 finally: 414 f_attrs.close() 415 416 def classify_accessors(self): 417 418 "For each program location, classify accessors." 419 420 # Where instance and module types are defined, class types are also 421 # defined. See: init_definition_details 422 423 locations = self.accessor_class_types.keys() 424 425 for location in locations: 426 constrained = location in self.accessor_constrained 427 428 # Provider information. 429 430 class_types = self.provider_class_types[location] 431 instance_types = self.provider_instance_types[location] 432 module_types = self.provider_module_types[location] 433 434 # Collect specific and general type information. 435 436 self.provider_all_types[location] = \ 437 combine_types(class_types, instance_types, module_types) 438 439 # Accessor information. 440 441 class_types = self.accessor_class_types[location] 442 self.accessor_general_class_types[location] = \ 443 general_class_types = self.get_most_general_class_types(class_types) 444 445 instance_types = self.accessor_instance_types[location] 446 self.accessor_general_instance_types[location] = \ 447 general_instance_types = self.get_most_general_class_types(instance_types) 448 449 module_types = self.accessor_module_types[location] 450 self.accessor_general_module_types[location] = \ 451 general_module_types = self.get_most_general_module_types(module_types) 452 453 # Collect specific and general type information. 454 455 self.accessor_all_types[location] = all_types = \ 456 combine_types(class_types, instance_types, module_types) 457 458 self.accessor_all_general_types[location] = all_general_types = \ 459 combine_types(general_class_types, general_instance_types, general_module_types) 460 461 # Record guard information. 462 463 if not constrained: 464 465 # Record specific type guard details. 466 467 if len(all_types) == 1: 468 self.accessor_guard_tests[location] = test_for_type("specific", first(all_types)) 469 elif is_single_class_type(all_types): 470 self.accessor_guard_tests[location] = "specific-object" 471 472 # Record common type guard details. 473 474 elif len(all_general_types) == 1: 475 self.accessor_guard_tests[location] = test_for_type("common", first(all_types)) 476 elif is_single_class_type(all_general_types): 477 self.accessor_guard_tests[location] = "common-object" 478 479 # Otherwise, no convenient guard can be defined. 480 481 def classify_accesses(self): 482 483 "For each program location, classify accesses." 484 485 # Attribute accesses use potentially different locations to those of 486 # accessors. 487 488 locations = self.referenced_attrs.keys() 489 490 for location in locations: 491 constrained = location in self.access_constrained 492 493 # Combine type information from all accessors supplying the access. 494 495 accessor_locations = self.get_accessors_for_access(location) 496 497 all_provider_types = set() 498 all_accessor_types = set() 499 all_accessor_general_types = set() 500 501 for accessor_location in accessor_locations: 502 503 # Obtain the provider types for guard-related attribute access 504 # checks. 505 506 all_provider_types.update(self.provider_all_types.get(accessor_location)) 507 508 # Obtain the accessor guard types (specific and general). 509 510 all_accessor_types.update(self.accessor_all_types.get(accessor_location)) 511 all_accessor_general_types.update(self.accessor_all_general_types.get(accessor_location)) 512 513 # Obtain basic properties of the types involved in the access. 514 515 single_accessor_type = len(all_accessor_types) == 1 516 single_accessor_class_type = is_single_class_type(all_accessor_types) 517 single_accessor_general_type = len(all_accessor_general_types) == 1 518 single_accessor_general_class_type = is_single_class_type(all_accessor_general_types) 519 520 # Determine whether the attribute access is guarded or not. 521 522 guarded = ( 523 single_accessor_type or single_accessor_class_type or 524 single_accessor_general_type or single_accessor_general_class_type 525 ) 526 527 if guarded: 528 (guard_class_types, guard_instance_types, guard_module_types, 529 _function_types, _var_types) = separate_types(all_provider_types) 530 531 self.reference_all_accessor_types[location] = all_accessor_types 532 self.reference_all_accessor_general_types[location] = all_accessor_general_types 533 534 # Attribute information, both name-based and anonymous. 535 536 referenced_attrs = self.referenced_attrs[location] 537 538 if not referenced_attrs: 539 raise DeduceError, repr(location) 540 541 # Record attribute information for each name used on the 542 # accessor. 543 544 attrname = get_attrname_from_location(location) 545 546 all_accessed_attrs = set() 547 all_providers = set() 548 549 # Obtain provider and attribute details for this kind of 550 # object. 551 552 for attrtype, object_type, attr in referenced_attrs: 553 all_accessed_attrs.add(attr) 554 all_providers.add(object_type) 555 556 all_general_providers = self.get_most_general_types(all_providers) 557 558 # Determine which attributes would be provided by the 559 # accessor types upheld by a guard. 560 561 if guarded: 562 guard_attrs = set() 563 for _attrtype, object_type, attr in \ 564 self._identify_reference_attribute(attrname, guard_class_types, guard_instance_types, guard_module_types): 565 guard_attrs.add(attr) 566 else: 567 guard_attrs = None 568 569 self.reference_all_attrs[location] = all_accessed_attrs 570 571 # Constrained accesses guarantee the nature of the accessor. 572 # However, there may still be many types involved. 573 574 if constrained: 575 if single_accessor_type: 576 self.reference_test_types[location] = test_for_type("constrained-specific", first(all_accessor_types)) 577 elif single_accessor_class_type: 578 self.reference_test_types[location] = "constrained-specific-object" 579 elif single_accessor_general_type: 580 self.reference_test_types[location] = test_for_type("constrained-common", first(all_accessor_general_types)) 581 elif single_accessor_general_class_type: 582 self.reference_test_types[location] = "constrained-common-object" 583 else: 584 self.reference_test_types[location] = "constrained-many" 585 586 # Suitably guarded accesses, where the nature of the 587 # accessor can be guaranteed, do not require the attribute 588 # involved to be validated. Otherwise, for unguarded 589 # accesses, access-level tests are required. 590 591 elif guarded and all_accessed_attrs.issubset(guard_attrs): 592 if single_accessor_type: 593 self.reference_test_types[location] = test_for_type("guarded-specific", first(all_accessor_types)) 594 elif single_accessor_class_type: 595 self.reference_test_types[location] = "guarded-specific-object" 596 elif single_accessor_general_type: 597 self.reference_test_types[location] = test_for_type("guarded-common", first(all_accessor_general_types)) 598 elif single_accessor_general_class_type: 599 self.reference_test_types[location] = "guarded-common-object" 600 601 # Record the need to test the type of anonymous and 602 # unconstrained accessors. 603 604 elif len(all_providers) == 1: 605 provider = first(all_providers) 606 if provider != '__builtins__.object': 607 all_accessor_kinds = set(get_kinds(all_accessor_types)) 608 if len(all_accessor_kinds) == 1: 609 test_type = test_for_kinds("specific", all_accessor_kinds) 610 else: 611 test_type = "specific-object" 612 self.reference_test_types[location] = test_type 613 self.reference_test_accessor_type[location] = provider 614 615 elif len(all_general_providers) == 1: 616 provider = first(all_general_providers) 617 if provider != '__builtins__.object': 618 all_accessor_kinds = set(get_kinds(all_accessor_general_types)) 619 if len(all_accessor_kinds) == 1: 620 test_type = test_for_kinds("common", all_accessor_kinds) 621 else: 622 test_type = "common-object" 623 self.reference_test_types[location] = test_type 624 self.reference_test_accessor_type[location] = provider 625 626 # Record the need to test the identity of the attribute. 627 628 else: 629 self.reference_test_types[location] = "validate" 630 631 def initialise_access_plans(self): 632 633 "Define attribute access plans." 634 635 for location in self.referenced_attrs.keys(): 636 original_location = self.const_accesses_rev.get(location) 637 self.access_plans[original_location or location] = self.get_access_plan(location) 638 639 def get_referenced_attrs(self, location): 640 641 """ 642 Return attributes referenced at the given access 'location' by the given 643 'attrname' as a list of (attribute type, attribute set) tuples. 644 """ 645 646 d = {} 647 for attrtype, objtype, attr in self.referenced_attrs[location]: 648 init_item(d, attrtype, set) 649 d[attrtype].add(attr) 650 l = d.items() 651 l.sort() # class, module, instance 652 return l 653 654 # Initialisation methods. 655 656 def init_descendants(self): 657 658 "Identify descendants of each class." 659 660 for name in self.importer.classes.keys(): 661 self.get_descendants_for_class(name) 662 663 def get_descendants_for_class(self, name): 664 665 """ 666 Use subclass information to deduce the descendants for the class of the 667 given 'name'. 668 """ 669 670 if not self.descendants.has_key(name): 671 descendants = set() 672 673 for subclass in self.importer.subclasses[name]: 674 descendants.update(self.get_descendants_for_class(subclass)) 675 descendants.add(subclass) 676 677 self.descendants[name] = descendants 678 679 return self.descendants[name] 680 681 def init_special_attributes(self): 682 683 "Add special attributes to the classes for inheritance-related tests." 684 685 all_class_attrs = self.importer.all_class_attrs 686 687 for name, descendants in self.descendants.items(): 688 for descendant in descendants: 689 all_class_attrs[descendant]["#%s" % name] = name 690 691 for name in all_class_attrs.keys(): 692 all_class_attrs[name]["#%s" % name] = name 693 694 def init_usage_index(self): 695 696 """ 697 Create indexes for module and function attribute usage and for anonymous 698 accesses. 699 """ 700 701 for module in self.importer.get_modules(): 702 for path, assignments in module.attr_usage.items(): 703 self.add_usage(assignments, path) 704 705 for location, all_attrnames in self.importer.all_attr_accesses.items(): 706 for attrnames in all_attrnames: 707 attrname = get_attrnames(attrnames)[-1] 708 access_location = (location, None, attrnames, 0) 709 self.add_usage_term(access_location, ((attrname, False),)) 710 711 def add_usage(self, assignments, path): 712 713 """ 714 Collect usage from the given 'assignments', adding 'path' details to 715 each record if specified. Add the usage to an index mapping to location 716 information, as well as to an index mapping locations to usages. 717 """ 718 719 for name, versions in assignments.items(): 720 for i, usages in enumerate(versions): 721 location = (path, name, None, i) 722 723 for usage in usages: 724 self.add_usage_term(location, usage) 725 726 def add_usage_term(self, location, usage): 727 728 """ 729 For 'location' and using 'usage' as a description of usage, record 730 in the usage index a mapping from the usage to 'location', and record in 731 the location index a mapping from 'location' to the usage. 732 """ 733 734 init_item(self.location_index, location, set) 735 self.location_index[location].add(usage) 736 737 def init_accessors(self): 738 739 "Create indexes for module and function accessor information." 740 741 for module in self.importer.get_modules(): 742 for path, all_accesses in module.attr_accessors.items(): 743 self.add_accessors(all_accesses, path) 744 745 def add_accessors(self, all_accesses, path): 746 747 """ 748 For attribute accesses described by the mapping of 'all_accesses' from 749 name details to accessor details, record the locations of the accessors 750 for each access. 751 """ 752 753 # Get details for each access combining the given name and attribute. 754 755 for (name, attrnames), accesses in all_accesses.items(): 756 757 # Obtain the usage details using the access information. 758 759 for access_number, versions in enumerate(accesses): 760 access_location = (path, name, attrnames, access_number) 761 locations = [] 762 763 for version in versions: 764 location = (path, name, None, version) 765 locations.append(location) 766 767 self.access_index[access_location] = locations 768 769 def get_accessors_for_access(self, access_location): 770 771 "Find a definition providing accessor details, if necessary." 772 773 try: 774 return self.access_index[access_location] 775 except KeyError: 776 return [access_location] 777 778 def init_accesses(self): 779 780 """ 781 Initialise collections for accesses involving assignments. 782 """ 783 784 # For each scope, obtain access details. 785 786 for path, all_accesses in self.importer.all_attr_access_modifiers.items(): 787 788 # For each combination of name and attribute names, obtain 789 # applicable modifiers. 790 791 for (name, attrnames), modifiers in all_accesses.items(): 792 793 # For each access, determine the name versions affected by 794 # assignments. 795 796 for access_number, assignment in enumerate(modifiers): 797 if name: 798 access_location = (path, name, attrnames, access_number) 799 else: 800 access_location = (path, None, attrnames, 0) 801 802 if assignment: 803 self.reference_assignments.add(access_location) 804 805 # Associate assignments with usage. 806 807 accessor_locations = self.get_accessors_for_access(access_location) 808 809 for location in accessor_locations: 810 for usage in self.location_index[location]: 811 if assignment: 812 init_item(self.assigned_attrs, usage, set) 813 self.assigned_attrs[usage].add((path, name, attrnames)) 814 815 def init_aliases(self): 816 817 "Expand aliases so that alias-based accesses can be resolved." 818 819 # Get aliased names with details of their accesses. 820 821 for name_path, all_aliases in self.importer.all_aliased_names.items(): 822 path, name = name_path.rsplit(".", 1) 823 824 # For each version of the name, obtain the access location. 825 826 for version, (original_name, attrnames, access_number) in all_aliases.items(): 827 accessor_location = (path, name, None, version) 828 access_location = (path, original_name, attrnames, access_number) 829 init_item(self.alias_index, accessor_location, list) 830 self.alias_index[accessor_location].append(access_location) 831 832 # Get aliases in terms of non-aliases and accesses. 833 834 for accessor_location, access_locations in self.alias_index.items(): 835 self.update_aliases(accessor_location, access_locations) 836 837 def update_aliases(self, accessor_location, access_locations, visited=None): 838 839 """ 840 Update the given 'accessor_location' defining an alias, update 841 'access_locations' to refer to non-aliases, following name references 842 via the access index. 843 844 If 'visited' is specified, it contains a set of accessor locations (and 845 thus keys to the alias index) that are currently being defined. 846 """ 847 848 if visited is None: 849 visited = set() 850 851 updated_locations = set() 852 853 for access_location in access_locations: 854 (path, original_name, attrnames, access_number) = access_location 855 856 # Where an alias refers to a name access, obtain the original name 857 # version details. 858 859 if attrnames is None: 860 861 # For each name version, attempt to determine any accesses that 862 # initialise the name. 863 864 for name_accessor_location in self.access_index[access_location]: 865 866 # Already-visited aliases do not contribute details. 867 868 if name_accessor_location in visited: 869 continue 870 871 visited.add(name_accessor_location) 872 873 name_access_locations = self.alias_index.get(name_accessor_location) 874 if name_access_locations: 875 updated_locations.update(self.update_aliases(name_accessor_location, name_access_locations, visited)) 876 else: 877 updated_locations.add(name_accessor_location) 878 879 # Otherwise, record the access details. 880 881 else: 882 updated_locations.add(access_location) 883 884 self.alias_index[accessor_location] = updated_locations 885 return updated_locations 886 887 # Attribute mutation for types. 888 889 def modify_mutated_attributes(self): 890 891 "Identify known, mutated attributes and change their state." 892 893 # Usage-based accesses. 894 895 for usage, all_attrnames in self.assigned_attrs.items(): 896 if not usage: 897 continue 898 899 for path, name, attrnames in all_attrnames: 900 class_types = self.get_class_types_for_usage(usage) 901 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 902 module_types = self.get_module_types_for_usage(usage) 903 904 # Detect self usage within methods in order to narrow the scope 905 # of the mutation. 906 907 t = name == "self" and self.constrain_self_reference(path, class_types, only_instance_types) 908 if t: 909 class_types, only_instance_types, module_types, constrained = t 910 objects = set(class_types).union(only_instance_types).union(module_types) 911 912 self.mutate_attribute(objects, attrnames) 913 914 def mutate_attribute(self, objects, attrnames): 915 916 "Mutate static 'objects' with the given 'attrnames'." 917 918 for name in objects: 919 attr = "%s.%s" % (name, attrnames) 920 value = self.importer.get_object(attr) 921 922 # If the value is None, the attribute is 923 # inherited and need not be set explicitly on 924 # the class concerned. 925 926 if value: 927 self.modified_attributes[attr] = value 928 self.importer.set_object(attr, value.as_var()) 929 930 # Simplification of types. 931 932 def get_most_general_types(self, types): 933 934 "Return the most general types for the given 'types'." 935 936 module_types = set() 937 class_types = set() 938 939 for type in types: 940 ref = self.importer.identify(type) 941 if ref.has_kind("<module>"): 942 module_types.add(type) 943 else: 944 class_types.add(type) 945 946 types = set(self.get_most_general_module_types(module_types)) 947 types.update(self.get_most_general_class_types(class_types)) 948 return types 949 950 def get_most_general_class_types(self, class_types): 951 952 "Return the most general types for the given 'class_types'." 953 954 class_types = set(class_types) 955 to_remove = set() 956 957 for class_type in class_types: 958 for base in self.importer.classes[class_type]: 959 base = base.get_origin() 960 descendants = self.descendants[base] 961 if base in class_types and descendants.issubset(class_types): 962 to_remove.update(descendants) 963 964 class_types.difference_update(to_remove) 965 return class_types 966 967 def get_most_general_module_types(self, module_types): 968 969 "Return the most general type for the given 'module_types'." 970 971 # Where all modules are provided, an object would provide the same 972 # attributes. 973 974 if len(module_types) == len(self.importer.modules): 975 return ["__builtins__.object"] 976 else: 977 return module_types 978 979 # More efficient usage-to-type indexing and retrieval. 980 981 def init_attr_type_indexes(self): 982 983 "Identify the types that can support each attribute name." 984 985 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 986 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs) 987 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 988 989 def _init_attr_type_index(self, attr_types, attrs): 990 991 """ 992 Initialise the 'attr_types' attribute-to-types mapping using the given 993 'attrs' type-to-attributes mapping. 994 """ 995 996 for name, attrnames in attrs.items(): 997 for attrname in attrnames: 998 init_item(attr_types, attrname, set) 999 attr_types[attrname].add(name) 1000 1001 def get_class_types_for_usage(self, usage): 1002 1003 "Return names of classes supporting the given 'usage'." 1004 1005 return self._get_types_for_usage(usage, self.attr_class_types, self.importer.all_class_attrs) 1006 1007 def get_instance_types_for_usage(self, usage): 1008 1009 """ 1010 Return names of classes whose instances support the given 'usage' 1011 (as either class or instance attributes). 1012 """ 1013 1014 return self._get_types_for_usage(usage, self.attr_instance_types, self.importer.all_combined_attrs) 1015 1016 def get_module_types_for_usage(self, usage): 1017 1018 "Return names of modules supporting the given 'usage'." 1019 1020 return self._get_types_for_usage(usage, self.attr_module_types, self.importer.all_module_attrs) 1021 1022 def _get_types_for_usage(self, usage, attr_types, attrs): 1023 1024 """ 1025 For the given 'usage' representing attribute usage, return types 1026 recorded in the 'attr_types' attribute-to-types mapping that support 1027 such usage, with the given 'attrs' type-to-attributes mapping used to 1028 quickly assess whether a type supports all of the stated attributes. 1029 """ 1030 1031 # Where no attributes are used, any type would be acceptable. 1032 1033 if not usage: 1034 return attrs.keys() 1035 1036 attrnames = [] 1037 for attrname, invocation in usage: 1038 attrnames.append(attrname) 1039 1040 types = [] 1041 1042 # Obtain types supporting the first attribute name... 1043 1044 for name in attr_types.get(attrnames[0]) or []: 1045 1046 # Record types that support all of the other attributes as well. 1047 1048 _attrnames = attrs[name] 1049 if set(attrnames).issubset(_attrnames): 1050 types.append(name) 1051 1052 return types 1053 1054 # Reference identification. 1055 1056 def identify_references(self): 1057 1058 "Identify references using usage and name reference information." 1059 1060 # Names with associated attribute usage. 1061 1062 for location, usages in self.location_index.items(): 1063 1064 # Obtain attribute usage associated with a name, deducing the nature 1065 # of the name. Obtain types only for branches involving attribute 1066 # usage. (In the absence of usage, any type could be involved, but 1067 # then no accesses exist to require knowledge of the type.) 1068 1069 have_usage = False 1070 have_no_usage_branch = False 1071 1072 for usage in usages: 1073 if not usage: 1074 have_no_usage_branch = True 1075 continue 1076 elif not have_usage: 1077 self.init_definition_details(location) 1078 have_usage = True 1079 self.record_types_for_usage(location, usage) 1080 1081 # Where some usage occurs, but where branches without usage also 1082 # occur, record the types for those branches anyway. 1083 1084 if have_usage and have_no_usage_branch: 1085 self.init_definition_details(location) 1086 self.record_types_for_usage(location, None) 1087 1088 # Specific name-based attribute accesses. 1089 1090 alias_accesses = set() 1091 1092 for access_location, accessor_locations in self.access_index.items(): 1093 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1094 1095 # Anonymous references with attribute chains. 1096 1097 for location, accesses in self.importer.all_attr_accesses.items(): 1098 1099 # Get distinct attribute names. 1100 1101 all_attrnames = set() 1102 1103 for attrnames in accesses: 1104 all_attrnames.update(get_attrnames(attrnames)) 1105 1106 # Get attribute and accessor details for each attribute name. 1107 1108 for attrname in all_attrnames: 1109 access_location = (location, None, attrname, 0) 1110 self.record_types_for_attribute(access_location, attrname) 1111 1112 # References via constant/identified objects. 1113 1114 for location, name_accesses in self.importer.all_const_accesses.items(): 1115 1116 # A mapping from the original name and attributes to resolved access 1117 # details. 1118 1119 for original_access, access in name_accesses.items(): 1120 original_name, original_attrnames = original_access 1121 objpath, ref, attrnames = access 1122 1123 # Build an accessor combining the name and attribute names used. 1124 1125 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1126 1127 # Direct accesses to attributes. 1128 1129 if not attrnames: 1130 1131 # Build a descriptive location based on the original 1132 # details, exposing the final attribute name. 1133 1134 oa, attrname = original_accessor[:-1], original_accessor[-1] 1135 oa = ".".join(oa) 1136 1137 access_location = (location, oa, attrname, 0) 1138 accessor_location = (location, oa, None, 0) 1139 self.access_index[access_location] = [accessor_location] 1140 1141 self.init_access_details(access_location) 1142 self.init_definition_details(accessor_location) 1143 1144 # Obtain a reference for the accessor in order to properly 1145 # determine its type. 1146 1147 if ref.get_kind() != "<instance>": 1148 objpath = ref.get_origin() 1149 1150 objpath = objpath.rsplit(".", 1)[0] 1151 1152 # Where the object name conflicts with the module 1153 # providing it, obtain the module details. 1154 1155 if objpath in self.importer.modules: 1156 accessor = Reference("<module>", objpath) 1157 else: 1158 accessor = self.importer.get_object(objpath) 1159 1160 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1161 self.access_constrained.add(access_location) 1162 1163 class_types, instance_types, module_types = accessor.get_types() 1164 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1165 1166 else: 1167 1168 # Build a descriptive location based on the original 1169 # details, employing the first remaining attribute name. 1170 1171 l = get_attrnames(attrnames) 1172 attrname = l[0] 1173 1174 oa = original_accessor[:-len(l)] 1175 oa = ".".join(oa) 1176 1177 access_location = (location, oa, attrnames, 0) 1178 accessor_location = (location, oa, None, 0) 1179 self.access_index[access_location] = [accessor_location] 1180 1181 self.init_access_details(access_location) 1182 self.init_definition_details(accessor_location) 1183 1184 class_types, instance_types, module_types = ref.get_types() 1185 1186 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1187 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1188 1189 original_location = (location, original_name, original_attrnames, 0) 1190 1191 if original_location != access_location: 1192 self.const_accesses[original_location] = access_location 1193 self.const_accesses_rev[access_location] = original_location 1194 1195 # Aliased name definitions. All aliases with usage will have been 1196 # defined, but they may be refined according to referenced accesses. 1197 1198 for accessor_location in self.alias_index.keys(): 1199 self.record_types_for_alias(accessor_location) 1200 1201 # Update accesses employing aliases. 1202 1203 for access_location in alias_accesses: 1204 self.record_types_for_access(access_location, self.access_index[access_location]) 1205 1206 def constrain_types(self, path, class_types, instance_types, module_types): 1207 1208 """ 1209 Using the given 'path' to an object, constrain the given 'class_types', 1210 'instance_types' and 'module_types'. 1211 1212 Return the class, instance, module types plus whether the types are 1213 constrained to a specific kind of type. 1214 """ 1215 1216 ref = self.importer.identify(path) 1217 if ref: 1218 1219 # Constrain usage suggestions using the identified object. 1220 1221 if ref.has_kind("<class>"): 1222 return ( 1223 set(class_types).intersection([ref.get_origin()]), [], [], True 1224 ) 1225 elif ref.has_kind("<module>"): 1226 return ( 1227 [], [], set(module_types).intersection([ref.get_origin()]), True 1228 ) 1229 1230 return class_types, instance_types, module_types, False 1231 1232 def get_target_types(self, location, usage): 1233 1234 """ 1235 Return the class, instance and module types constrained for the name at 1236 the given 'location' exhibiting the given 'usage'. Whether the types 1237 have been constrained using contextual information is also indicated, 1238 plus whether the types have been constrained to a specific kind of type. 1239 """ 1240 1241 unit_path, name, attrnames, version = location 1242 1243 # Detect any initialised name for the location. 1244 1245 if name: 1246 ref = self.get_initialised_name(location) 1247 if ref: 1248 (class_types, only_instance_types, module_types, 1249 _function_types, _var_types) = separate_types([ref]) 1250 return class_types, only_instance_types, module_types, True, False 1251 1252 # Retrieve the recorded types for the usage. 1253 1254 class_types = self.get_class_types_for_usage(usage) 1255 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1256 module_types = self.get_module_types_for_usage(usage) 1257 1258 # Merge usage deductions with observations to obtain reference types 1259 # for names involved with attribute accesses. 1260 1261 if not name: 1262 return class_types, only_instance_types, module_types, False, False 1263 1264 # Obtain references to known objects. 1265 1266 path = get_name_path(unit_path, name) 1267 1268 class_types, only_instance_types, module_types, constrained_specific = \ 1269 self.constrain_types(path, class_types, only_instance_types, module_types) 1270 1271 if constrained_specific: 1272 return class_types, only_instance_types, module_types, constrained_specific, constrained_specific 1273 1274 # Constrain "self" references. 1275 1276 if name == "self": 1277 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1278 if t: 1279 class_types, only_instance_types, module_types, constrained = t 1280 return class_types, only_instance_types, module_types, constrained, False 1281 1282 return class_types, only_instance_types, module_types, False, False 1283 1284 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1285 1286 """ 1287 Where the name "self" appears in a method, attempt to constrain the 1288 classes involved. 1289 1290 Return the class, instance, module types plus whether the types are 1291 constrained. 1292 """ 1293 1294 class_name = self.in_method(unit_path) 1295 1296 if not class_name: 1297 return None 1298 1299 classes = set([class_name]) 1300 classes.update(self.get_descendants_for_class(class_name)) 1301 1302 # Note that only instances will be expected for these references but 1303 # either classes or instances may provide the attributes. 1304 1305 return ( 1306 set(class_types).intersection(classes), 1307 set(only_instance_types).intersection(classes), 1308 [], True 1309 ) 1310 1311 def in_method(self, path): 1312 1313 "Return whether 'path' refers to a method." 1314 1315 class_name, method_name = path.rsplit(".", 1) 1316 return self.importer.classes.has_key(class_name) and class_name 1317 1318 def init_reference_details(self, location): 1319 1320 "Initialise reference-related details for 'location'." 1321 1322 self.init_definition_details(location) 1323 self.init_access_details(location) 1324 1325 def init_definition_details(self, location): 1326 1327 "Initialise name definition details for 'location'." 1328 1329 self.accessor_class_types[location] = set() 1330 self.accessor_instance_types[location] = set() 1331 self.accessor_module_types[location] = set() 1332 self.provider_class_types[location] = set() 1333 self.provider_instance_types[location] = set() 1334 self.provider_module_types[location] = set() 1335 1336 def init_access_details(self, location): 1337 1338 "Initialise access details at 'location'." 1339 1340 self.referenced_attrs[location] = {} 1341 1342 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1343 1344 """ 1345 Define types for the 'access_location' associated with the given 1346 'accessor_locations'. 1347 """ 1348 1349 attrname = get_attrname_from_location(access_location) 1350 if not attrname: 1351 return 1352 1353 # Collect all suggested types for the accessors. Accesses may 1354 # require accessors from of a subset of the complete set of types. 1355 1356 class_types = set() 1357 module_types = set() 1358 instance_types = set() 1359 1360 constrained = True 1361 1362 for location in accessor_locations: 1363 1364 # Remember accesses employing aliases. 1365 1366 if alias_accesses is not None and self.alias_index.has_key(location): 1367 alias_accesses.add(access_location) 1368 1369 # Use the type information deduced for names from above. 1370 1371 if self.accessor_class_types.has_key(location): 1372 class_types.update(self.accessor_class_types[location]) 1373 module_types.update(self.accessor_module_types[location]) 1374 instance_types.update(self.accessor_instance_types[location]) 1375 1376 # Where accesses are associated with assignments but where no 1377 # attribute usage observations have caused such an association, 1378 # the attribute name is considered by itself. 1379 1380 else: 1381 self.init_definition_details(location) 1382 self.record_types_for_usage(location, [(attrname, False)]) 1383 1384 constrained = location in self.accessor_constrained and constrained 1385 1386 self.init_access_details(access_location) 1387 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1388 1389 def record_types_for_usage(self, accessor_location, usage): 1390 1391 """ 1392 Record types for the given 'accessor_location' according to the given 1393 'usage' observations which may be None to indicate an absence of usage. 1394 """ 1395 1396 (class_types, 1397 instance_types, 1398 module_types, 1399 constrained, 1400 constrained_specific) = self.get_target_types(accessor_location, usage) 1401 1402 invocations = get_invoked_attributes(usage) 1403 1404 self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific, invocations) 1405 1406 def record_types_for_attribute(self, access_location, attrname): 1407 1408 """ 1409 Record types for the 'access_location' employing only the given 1410 'attrname' for type deduction. 1411 """ 1412 1413 usage = ((attrname, False),) 1414 1415 class_types = self.get_class_types_for_usage(usage) 1416 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1417 module_types = self.get_module_types_for_usage(usage) 1418 1419 self.init_reference_details(access_location) 1420 1421 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1422 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1423 1424 def record_types_for_alias(self, accessor_location): 1425 1426 """ 1427 Define types for the 'accessor_location' not having associated usage. 1428 """ 1429 1430 have_access = self.provider_class_types.has_key(accessor_location) 1431 1432 # With an access, attempt to narrow the existing selection of provider 1433 # types. 1434 1435 if have_access: 1436 provider_class_types = self.provider_class_types[accessor_location] 1437 provider_instance_types = self.provider_instance_types[accessor_location] 1438 provider_module_types = self.provider_module_types[accessor_location] 1439 1440 # Find details for any corresponding access. 1441 1442 all_class_types = set() 1443 all_instance_types = set() 1444 all_module_types = set() 1445 1446 for access_location in self.alias_index[accessor_location]: 1447 location, name, attrnames, access_number = access_location 1448 1449 # Alias references an attribute access. 1450 1451 if attrnames: 1452 1453 # Obtain attribute references for the access. 1454 1455 attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]] 1456 1457 # Separate the different attribute types. 1458 1459 (class_types, instance_types, module_types, 1460 function_types, var_types) = separate_types(attrs) 1461 1462 # Where non-accessor types are found, do not attempt to refine 1463 # the defined accessor types. 1464 1465 if function_types or var_types: 1466 return 1467 1468 class_types = set(provider_class_types).intersection(class_types) 1469 instance_types = set(provider_instance_types).intersection(instance_types) 1470 module_types = set(provider_module_types).intersection(module_types) 1471 1472 # Alias references a name, not an access. 1473 1474 else: 1475 # Attempt to refine the types using initialised names. 1476 1477 attr = self.get_initialised_name(access_location) 1478 if attr: 1479 (class_types, instance_types, module_types, 1480 _function_types, _var_types) = separate_types([attr]) 1481 1482 # Where no further information is found, do not attempt to 1483 # refine the defined accessor types. 1484 1485 else: 1486 return 1487 1488 all_class_types.update(class_types) 1489 all_instance_types.update(instance_types) 1490 all_module_types.update(module_types) 1491 1492 # Record refined type details for the alias as an accessor. 1493 1494 self.init_definition_details(accessor_location) 1495 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1496 1497 # Without an access, attempt to identify references for the alias. 1498 1499 else: 1500 refs = set() 1501 1502 for access_location in self.alias_index[accessor_location]: 1503 1504 # Obtain any redefined constant access location. 1505 1506 if self.const_accesses.has_key(access_location): 1507 access_location = self.const_accesses[access_location] 1508 1509 location, name, attrnames, access_number = access_location 1510 1511 # Alias references an attribute access. 1512 1513 if attrnames: 1514 attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]] 1515 refs.update(attrs) 1516 1517 # Alias references a name, not an access. 1518 1519 else: 1520 attr = self.get_initialised_name(access_location) 1521 attrs = attr and [attr] or [] 1522 if not attrs and self.provider_class_types.has_key(access_location): 1523 class_types = self.provider_class_types[access_location] 1524 instance_types = self.provider_instance_types[access_location] 1525 module_types = self.provider_module_types[access_location] 1526 attrs = combine_types(class_types, instance_types, module_types) 1527 if attrs: 1528 refs.update(attrs) 1529 1530 # Record reference details for the alias separately from accessors. 1531 1532 self.referenced_objects[accessor_location] = refs 1533 1534 def get_initialised_name(self, access_location): 1535 1536 """ 1537 Return references for any initialised names at 'access_location', or 1538 None if no such references exist. 1539 """ 1540 1541 location, name, attrnames, version = access_location 1542 path = get_name_path(location, name) 1543 1544 # Use initialiser information, if available. 1545 1546 refs = self.importer.all_initialised_names.get(path) 1547 if refs and refs.has_key(version): 1548 return refs[version] 1549 else: 1550 return None 1551 1552 def record_reference_types(self, location, class_types, instance_types, 1553 module_types, constrained, constrained_specific=False, invocations=None): 1554 1555 """ 1556 Associate attribute provider types with the given 'location', consisting 1557 of the given 'class_types', 'instance_types' and 'module_types'. 1558 1559 If 'constrained' is indicated, the constrained nature of the accessor is 1560 recorded for the location. 1561 1562 If 'constrained_specific' is indicated using a true value, instance types 1563 will not be added to class types to permit access via instances at the 1564 given location. This is only useful where a specific accessor is known 1565 to be a class. 1566 1567 Note that the specified types only indicate the provider types for 1568 attributes, whereas the recorded accessor types indicate the possible 1569 types of the actual objects used to access attributes. 1570 """ 1571 1572 # Update the type details for the location. 1573 1574 self.provider_class_types[location].update(class_types) 1575 self.provider_instance_types[location].update(instance_types) 1576 self.provider_module_types[location].update(module_types) 1577 1578 # Class types support classes and instances as accessors. 1579 # Instance-only and module types support only their own kinds as 1580 # accessors. 1581 1582 path, name, version, attrnames = location 1583 1584 if invocations: 1585 class_only_types = self.filter_for_invocations(class_types, invocations) 1586 else: 1587 class_only_types = class_types 1588 1589 # However, the nature of accessors can be further determined. 1590 # Any self variable may only refer to an instance. 1591 1592 if name != "self" or not self.in_method(path): 1593 self.accessor_class_types[location].update(class_only_types) 1594 1595 if not constrained_specific: 1596 self.accessor_instance_types[location].update(class_types) 1597 1598 self.accessor_instance_types[location].update(instance_types) 1599 1600 if name != "self" or not self.in_method(path): 1601 self.accessor_module_types[location].update(module_types) 1602 1603 if constrained: 1604 self.accessor_constrained.add(location) 1605 1606 def filter_for_invocations(self, class_types, attrnames): 1607 1608 """ 1609 From the given 'class_types', identify methods for the given 1610 'attrnames' that are being invoked, returning a filtered collection of 1611 class types. 1612 """ 1613 1614 to_filter = set() 1615 1616 for class_type in class_types: 1617 for attrname in attrnames: 1618 ref = self.importer.get_class_attribute(class_type, attrname) 1619 parent_class = ref and ref.parent() 1620 1621 if ref and ref.has_kind("<function>") and ( 1622 parent_class == class_type or 1623 class_type in self.descendants[parent_class]): 1624 1625 to_filter.add(class_type) 1626 break 1627 1628 return set(class_types).difference(to_filter) 1629 1630 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1631 1632 """ 1633 Identify reference attributes, associating them with the given 1634 'location', identifying the given 'attrname', employing the given 1635 'class_types', 'instance_types' and 'module_types'. 1636 1637 If 'constrained' is indicated, the constrained nature of the access is 1638 recorded for the location. 1639 """ 1640 1641 # Record the referenced objects. 1642 1643 self.referenced_attrs[location] = \ 1644 self._identify_reference_attribute(attrname, class_types, instance_types, module_types) 1645 1646 if constrained: 1647 self.access_constrained.add(location) 1648 1649 def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types): 1650 1651 """ 1652 Identify the reference attribute with the given 'attrname', employing 1653 the given 'class_types', 'instance_types' and 'module_types'. 1654 """ 1655 1656 attrs = set() 1657 1658 # The class types expose class attributes either directly or via 1659 # instances. 1660 1661 for object_type in class_types: 1662 ref = self.importer.get_class_attribute(object_type, attrname) 1663 if ref: 1664 attrs.add(("<class>", object_type, ref)) 1665 1666 # Add any distinct instance attributes that would be provided 1667 # by instances also providing indirect class attribute access. 1668 1669 for ref in self.importer.get_instance_attributes(object_type, attrname): 1670 attrs.add(("<instance>", object_type, ref)) 1671 1672 # The instance-only types expose instance attributes, but although 1673 # classes are excluded as potential accessors (since they do not provide 1674 # the instance attributes), the class types may still provide some 1675 # attributes. 1676 1677 for object_type in instance_types: 1678 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1679 1680 if instance_attrs: 1681 for ref in instance_attrs: 1682 attrs.add(("<instance>", object_type, ref)) 1683 else: 1684 ref = self.importer.get_class_attribute(object_type, attrname) 1685 if ref: 1686 attrs.add(("<class>", object_type, ref)) 1687 1688 # Module types expose module attributes for module accessors. 1689 1690 for object_type in module_types: 1691 ref = self.importer.get_module_attribute(object_type, attrname) 1692 if ref: 1693 attrs.add(("<module>", object_type, ref)) 1694 1695 return attrs 1696 1697 constrained_specific_tests = ( 1698 "constrained-specific-instance", 1699 "constrained-specific-type", 1700 "constrained-specific-object", 1701 ) 1702 1703 constrained_common_tests = ( 1704 "constrained-common-instance", 1705 "constrained-common-type", 1706 "constrained-common-object", 1707 ) 1708 1709 guarded_specific_tests = ( 1710 "guarded-specific-instance", 1711 "guarded-specific-type", 1712 "guarded-specific-object", 1713 ) 1714 1715 guarded_common_tests = ( 1716 "guarded-common-instance", 1717 "guarded-common-type", 1718 "guarded-common-object", 1719 ) 1720 1721 specific_tests = ( 1722 "specific-instance", 1723 "specific-type", 1724 "specific-object", 1725 ) 1726 1727 common_tests = ( 1728 "common-instance", 1729 "common-type", 1730 "common-object", 1731 ) 1732 1733 class_tests = ( 1734 "guarded-specific-type", 1735 "guarded-common-type", 1736 "specific-type", 1737 "common-type", 1738 ) 1739 1740 class_or_instance_tests = ( 1741 "guarded-specific-object", 1742 "guarded-common-object", 1743 "specific-object", 1744 "common-object", 1745 ) 1746 1747 def get_access_plan(self, location): 1748 1749 """ 1750 Return details of the access at the given 'location'. The details are as 1751 follows: 1752 1753 * the initial accessor (from which accesses will be performed if no 1754 computed static accessor is found) 1755 * details of any test required on the initial accessor 1756 * details of any type employed by the test 1757 * any static accessor (from which accesses will be performed in 1758 preference to the initial accessor) 1759 * attributes needing to be traversed from the base that yield 1760 unambiguous objects 1761 * remaining attributes needing to be tested and traversed 1762 * details of the context 1763 * the method of obtaining the final attribute 1764 * any static final attribute 1765 """ 1766 1767 const_access = self.const_accesses_rev.has_key(location) 1768 1769 path, name, attrnames, version = location 1770 remaining = attrnames.split(".") 1771 attrname = remaining[0] 1772 1773 # Obtain reference and accessor information, retaining also distinct 1774 # provider kind details. 1775 1776 attrs = [] 1777 objtypes = [] 1778 provider_kinds = set() 1779 1780 for attrtype, objtype, attr in self.referenced_attrs[location]: 1781 attrs.append(attr) 1782 objtypes.append(objtype) 1783 provider_kinds.add(attrtype) 1784 1785 # Obtain accessor type and kind information. 1786 1787 accessor_types = self.reference_all_accessor_types[location] 1788 accessor_general_types = self.reference_all_accessor_general_types[location] 1789 accessor_kinds = get_kinds(accessor_general_types) 1790 1791 # Determine any guard or test requirements. 1792 1793 constrained = location in self.access_constrained 1794 test = self.reference_test_types[location] 1795 test_type = self.reference_test_accessor_type.get(location) 1796 1797 # Determine the accessor and provider properties. 1798 1799 class_accessor = "<class>" in accessor_kinds 1800 module_accessor = "<module>" in accessor_kinds 1801 instance_accessor = "<instance>" in accessor_kinds 1802 provided_by_class = "<class>" in provider_kinds 1803 provided_by_instance = "<instance>" in provider_kinds 1804 1805 # Determine how attributes may be accessed relative to the accessor. 1806 1807 object_relative = class_accessor or module_accessor or provided_by_instance 1808 class_relative = instance_accessor and provided_by_class 1809 1810 # Identify the last static attribute for context acquisition. 1811 1812 base = None 1813 dynamic_base = None 1814 1815 # Constant accesses have static accessors. 1816 1817 if const_access: 1818 base = len(objtypes) == 1 and first(objtypes) 1819 1820 # Constant accessors are static. 1821 1822 else: 1823 ref = self.importer.identify("%s.%s" % (path, name)) 1824 if ref: 1825 base = ref.get_origin() 1826 1827 # Usage of previously-generated guard and test details. 1828 1829 elif test in self.constrained_specific_tests: 1830 ref = first(accessor_types) 1831 1832 elif test in self.constrained_common_tests: 1833 ref = first(accessor_general_types) 1834 1835 elif test in self.guarded_specific_tests: 1836 ref = first(accessor_types) 1837 1838 elif test in self.guarded_common_tests: 1839 ref = first(accessor_general_types) 1840 1841 # For attribute-based tests, tentatively identify a dynamic base. 1842 # Such tests allow single or multiple kinds of a type. 1843 1844 elif test in self.common_tests or test in self.specific_tests: 1845 dynamic_base = test_type 1846 1847 # Static accessors. 1848 1849 if not base and test in self.class_tests: 1850 base = ref and ref.get_origin() or dynamic_base 1851 1852 # Accessors that are not static but whose nature is determined. 1853 1854 elif not base and ref: 1855 dynamic_base = ref.get_origin() 1856 1857 traversed = [] 1858 traversal_modes = [] 1859 provider_kind = first(provider_kinds) 1860 1861 # Traverse remaining attributes. 1862 1863 while len(attrs) == 1: 1864 attr = first(attrs) 1865 accessor_kind = attr.get_kind() 1866 1867 traversed.append(attrname) 1868 traversal_modes.append(accessor_kind == provider_kind and "object" or "class") 1869 1870 del remaining[0] 1871 1872 if not remaining: 1873 break 1874 1875 # Update the last static attribute. 1876 1877 if attr.static(): 1878 base = attr.get_origin() 1879 traversed = [] 1880 traversal_modes = [] 1881 1882 # Get the next attribute. 1883 1884 attrname = remaining[0] 1885 attrs = self.importer.get_attributes(attr, attrname) 1886 provider_kind = self.importer.get_attribute_provider(attr, attrname) 1887 1888 # Where many attributes are suggested, no single attribute identity can 1889 # be loaded. 1890 1891 else: 1892 attr = None 1893 1894 # Determine the method of access. 1895 1896 # Identified attribute that must be accessed via its parent. 1897 1898 if attr and attr.get_name() and location in self.reference_assignments: 1899 final_method = "assign"; origin = attr.get_name() 1900 1901 # Static, identified attribute. 1902 1903 elif attr and attr.static(): 1904 final_method = "static"; origin = attr.final() 1905 1906 # All other methods of access involve traversal. 1907 1908 else: 1909 final_method = "access"; origin = None 1910 1911 # First attribute accessed at a known position via the accessor. 1912 1913 if base or dynamic_base: 1914 first_method = "relative" + (object_relative and "-object" or "") + \ 1915 (class_relative and "-class" or "") 1916 1917 # The fallback case is always run-time testing and access. 1918 1919 else: 1920 first_method = "check" + (object_relative and "-object" or "") + \ 1921 (class_relative and "-class" or "") 1922 1923 # Determine the nature of the context. 1924 1925 context = len(traversed or remaining) == 1 and (base and "base" or "original-accessor") or "final-accessor" 1926 1927 return name, test, test_type, base, traversed, traversal_modes, remaining, context, first_method, final_method, origin 1928 1929 # vim: tabstop=4 expandtab shiftwidth=4