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, 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(attrnames) or "{}", \ 410 context, first_method, final_method, attr or "{}" 411 412 finally: 413 f_attrs.close() 414 415 def classify_accessors(self): 416 417 "For each program location, classify accessors." 418 419 # Where instance and module types are defined, class types are also 420 # defined. See: init_definition_details 421 422 locations = self.accessor_class_types.keys() 423 424 for location in locations: 425 constrained = location in self.accessor_constrained 426 427 # Provider information. 428 429 class_types = self.provider_class_types[location] 430 instance_types = self.provider_instance_types[location] 431 module_types = self.provider_module_types[location] 432 433 # Collect specific and general type information. 434 435 self.provider_all_types[location] = \ 436 combine_types(class_types, instance_types, module_types) 437 438 # Accessor information. 439 440 class_types = self.accessor_class_types[location] 441 self.accessor_general_class_types[location] = \ 442 general_class_types = self.get_most_general_class_types(class_types) 443 444 instance_types = self.accessor_instance_types[location] 445 self.accessor_general_instance_types[location] = \ 446 general_instance_types = self.get_most_general_class_types(instance_types) 447 448 module_types = self.accessor_module_types[location] 449 self.accessor_general_module_types[location] = \ 450 general_module_types = self.get_most_general_module_types(module_types) 451 452 # Collect specific and general type information. 453 454 self.accessor_all_types[location] = all_types = \ 455 combine_types(class_types, instance_types, module_types) 456 457 self.accessor_all_general_types[location] = all_general_types = \ 458 combine_types(general_class_types, general_instance_types, general_module_types) 459 460 # Record guard information. 461 462 if not constrained: 463 464 # Record specific type guard details. 465 466 if len(all_types) == 1: 467 self.accessor_guard_tests[location] = test_for_type("specific", first(all_types)) 468 elif is_single_class_type(all_types): 469 self.accessor_guard_tests[location] = "specific-object" 470 471 # Record common type guard details. 472 473 elif len(all_general_types) == 1: 474 self.accessor_guard_tests[location] = test_for_type("common", first(all_types)) 475 elif is_single_class_type(all_general_types): 476 self.accessor_guard_tests[location] = "common-object" 477 478 # Otherwise, no convenient guard can be defined. 479 480 def classify_accesses(self): 481 482 "For each program location, classify accesses." 483 484 # Attribute accesses use potentially different locations to those of 485 # accessors. 486 487 locations = self.referenced_attrs.keys() 488 489 for location in locations: 490 constrained = location in self.access_constrained 491 492 # Combine type information from all accessors supplying the access. 493 494 accessor_locations = self.get_accessors_for_access(location) 495 496 all_provider_types = set() 497 all_accessor_types = set() 498 all_accessor_general_types = set() 499 500 for accessor_location in accessor_locations: 501 502 # Obtain the provider types for guard-related attribute access 503 # checks. 504 505 all_provider_types.update(self.provider_all_types.get(accessor_location)) 506 507 # Obtain the accessor guard types (specific and general). 508 509 all_accessor_types.update(self.accessor_all_types.get(accessor_location)) 510 all_accessor_general_types.update(self.accessor_all_general_types.get(accessor_location)) 511 512 # Obtain basic properties of the types involved in the access. 513 514 single_accessor_type = len(all_accessor_types) == 1 515 single_accessor_class_type = is_single_class_type(all_accessor_types) 516 single_accessor_general_type = len(all_accessor_general_types) == 1 517 single_accessor_general_class_type = is_single_class_type(all_accessor_general_types) 518 519 # Determine whether the attribute access is guarded or not. 520 521 guarded = ( 522 single_accessor_type or single_accessor_class_type or 523 single_accessor_general_type or single_accessor_general_class_type 524 ) 525 526 if guarded: 527 (guard_class_types, guard_instance_types, guard_module_types, 528 _function_types, _var_types) = separate_types(all_provider_types) 529 530 self.reference_all_accessor_types[location] = all_accessor_types 531 self.reference_all_accessor_general_types[location] = all_accessor_general_types 532 533 # Attribute information, both name-based and anonymous. 534 535 referenced_attrs = self.referenced_attrs[location] 536 537 if not referenced_attrs: 538 raise DeduceError, repr(location) 539 540 # Record attribute information for each name used on the 541 # accessor. 542 543 attrname = get_attrname_from_location(location) 544 545 all_accessed_attrs = set() 546 all_providers = set() 547 548 # Obtain provider and attribute details for this kind of 549 # object. 550 551 for attrtype, object_type, attr in referenced_attrs: 552 all_accessed_attrs.add(attr) 553 all_providers.add(object_type) 554 555 all_general_providers = self.get_most_general_types(all_providers) 556 557 # Determine which attributes would be provided by the 558 # accessor types upheld by a guard. 559 560 if guarded: 561 guard_attrs = set() 562 for _attrtype, object_type, attr in \ 563 self._identify_reference_attribute(attrname, guard_class_types, guard_instance_types, guard_module_types): 564 guard_attrs.add(attr) 565 else: 566 guard_attrs = None 567 568 self.reference_all_attrs[location] = all_accessed_attrs 569 570 # Constrained accesses guarantee the nature of the accessor. 571 # However, there may still be many types involved. 572 573 if constrained: 574 if single_accessor_type: 575 self.reference_test_types[location] = test_for_type("constrained-specific", first(all_accessor_types)) 576 elif single_accessor_class_type: 577 self.reference_test_types[location] = "constrained-specific-object" 578 elif single_accessor_general_type: 579 self.reference_test_types[location] = test_for_type("constrained-common", first(all_accessor_general_types)) 580 elif single_accessor_general_class_type: 581 self.reference_test_types[location] = "constrained-common-object" 582 else: 583 self.reference_test_types[location] = "constrained-many" 584 585 # Suitably guarded accesses, where the nature of the 586 # accessor can be guaranteed, do not require the attribute 587 # involved to be validated. Otherwise, for unguarded 588 # accesses, access-level tests are required. 589 590 elif guarded and all_accessed_attrs.issubset(guard_attrs): 591 if single_accessor_type: 592 self.reference_test_types[location] = test_for_type("guarded-specific", first(all_accessor_types)) 593 elif single_accessor_class_type: 594 self.reference_test_types[location] = "guarded-specific-object" 595 elif single_accessor_general_type: 596 self.reference_test_types[location] = test_for_type("guarded-common", first(all_accessor_general_types)) 597 elif single_accessor_general_class_type: 598 self.reference_test_types[location] = "guarded-common-object" 599 600 # Record the need to test the type of anonymous and 601 # unconstrained accessors. 602 603 elif len(all_providers) == 1: 604 provider = first(all_providers) 605 if provider != '__builtins__.object': 606 all_accessor_kinds = set(get_kinds(all_accessor_types)) 607 if len(all_accessor_kinds) == 1: 608 test_type = test_for_kinds("specific", all_accessor_kinds) 609 else: 610 test_type = "specific-object" 611 self.reference_test_types[location] = test_type 612 self.reference_test_accessor_type[location] = provider 613 614 elif len(all_general_providers) == 1: 615 provider = first(all_general_providers) 616 if provider != '__builtins__.object': 617 all_accessor_kinds = set(get_kinds(all_accessor_general_types)) 618 if len(all_accessor_kinds) == 1: 619 test_type = test_for_kinds("common", all_accessor_kinds) 620 else: 621 test_type = "common-object" 622 self.reference_test_types[location] = test_type 623 self.reference_test_accessor_type[location] = provider 624 625 # Record the need to test the identity of the attribute. 626 627 else: 628 self.reference_test_types[location] = "validate" 629 630 def initialise_access_plans(self): 631 632 "Define attribute access plans." 633 634 for location in self.referenced_attrs.keys(): 635 original_location = self.const_accesses_rev.get(location) 636 self.access_plans[original_location or location] = self.get_access_plan(location) 637 638 def get_referenced_attrs(self, location): 639 640 """ 641 Return attributes referenced at the given access 'location' by the given 642 'attrname' as a list of (attribute type, attribute set) tuples. 643 """ 644 645 d = {} 646 for attrtype, objtype, attr in self.referenced_attrs[location]: 647 init_item(d, attrtype, set) 648 d[attrtype].add(attr) 649 l = d.items() 650 l.sort() # class, module, instance 651 return l 652 653 # Initialisation methods. 654 655 def init_descendants(self): 656 657 "Identify descendants of each class." 658 659 for name in self.importer.classes.keys(): 660 self.get_descendants_for_class(name) 661 662 def get_descendants_for_class(self, name): 663 664 """ 665 Use subclass information to deduce the descendants for the class of the 666 given 'name'. 667 """ 668 669 if not self.descendants.has_key(name): 670 descendants = set() 671 672 for subclass in self.importer.subclasses[name]: 673 descendants.update(self.get_descendants_for_class(subclass)) 674 descendants.add(subclass) 675 676 self.descendants[name] = descendants 677 678 return self.descendants[name] 679 680 def init_special_attributes(self): 681 682 "Add special attributes to the classes for inheritance-related tests." 683 684 all_class_attrs = self.importer.all_class_attrs 685 686 for name, descendants in self.descendants.items(): 687 for descendant in descendants: 688 all_class_attrs[descendant]["#%s" % name] = name 689 690 for name in all_class_attrs.keys(): 691 all_class_attrs[name]["#%s" % name] = name 692 693 def init_usage_index(self): 694 695 """ 696 Create indexes for module and function attribute usage and for anonymous 697 accesses. 698 """ 699 700 for module in self.importer.get_modules(): 701 for path, assignments in module.attr_usage.items(): 702 self.add_usage(assignments, path) 703 704 for location, all_attrnames in self.importer.all_attr_accesses.items(): 705 for attrnames in all_attrnames: 706 attrname = get_attrnames(attrnames)[-1] 707 access_location = (location, None, attrnames, 0) 708 self.add_usage_term(access_location, ((attrname, False),)) 709 710 def add_usage(self, assignments, path): 711 712 """ 713 Collect usage from the given 'assignments', adding 'path' details to 714 each record if specified. Add the usage to an index mapping to location 715 information, as well as to an index mapping locations to usages. 716 """ 717 718 for name, versions in assignments.items(): 719 for i, usages in enumerate(versions): 720 location = (path, name, None, i) 721 722 for usage in usages: 723 self.add_usage_term(location, usage) 724 725 def add_usage_term(self, location, usage): 726 727 """ 728 For 'location' and using 'usage' as a description of usage, record 729 in the usage index a mapping from the usage to 'location', and record in 730 the location index a mapping from 'location' to the usage. 731 """ 732 733 init_item(self.location_index, location, set) 734 self.location_index[location].add(usage) 735 736 def init_accessors(self): 737 738 "Create indexes for module and function accessor information." 739 740 for module in self.importer.get_modules(): 741 for path, all_accesses in module.attr_accessors.items(): 742 self.add_accessors(all_accesses, path) 743 744 def add_accessors(self, all_accesses, path): 745 746 """ 747 For attribute accesses described by the mapping of 'all_accesses' from 748 name details to accessor details, record the locations of the accessors 749 for each access. 750 """ 751 752 # Get details for each access combining the given name and attribute. 753 754 for (name, attrnames), accesses in all_accesses.items(): 755 756 # Obtain the usage details using the access information. 757 758 for access_number, versions in enumerate(accesses): 759 access_location = (path, name, attrnames, access_number) 760 locations = [] 761 762 for version in versions: 763 location = (path, name, None, version) 764 locations.append(location) 765 766 self.access_index[access_location] = locations 767 768 def get_accessors_for_access(self, access_location): 769 770 "Find a definition providing accessor details, if necessary." 771 772 try: 773 return self.access_index[access_location] 774 except KeyError: 775 return [access_location] 776 777 def init_accesses(self): 778 779 """ 780 Initialise collections for accesses involving assignments. 781 """ 782 783 # For each scope, obtain access details. 784 785 for path, all_accesses in self.importer.all_attr_access_modifiers.items(): 786 787 # For each combination of name and attribute names, obtain 788 # applicable modifiers. 789 790 for (name, attrnames), modifiers in all_accesses.items(): 791 792 # For each access, determine the name versions affected by 793 # assignments. 794 795 for access_number, assignment in enumerate(modifiers): 796 if name: 797 access_location = (path, name, attrnames, access_number) 798 else: 799 access_location = (path, None, attrnames, 0) 800 801 if assignment: 802 self.reference_assignments.add(access_location) 803 804 # Associate assignments with usage. 805 806 accessor_locations = self.get_accessors_for_access(access_location) 807 808 for location in accessor_locations: 809 for usage in self.location_index[location]: 810 if assignment: 811 init_item(self.assigned_attrs, usage, set) 812 self.assigned_attrs[usage].add((path, name, attrnames)) 813 814 def init_aliases(self): 815 816 "Expand aliases so that alias-based accesses can be resolved." 817 818 # Get aliased names with details of their accesses. 819 820 for name_path, all_aliases in self.importer.all_aliased_names.items(): 821 path, name = name_path.rsplit(".", 1) 822 823 # For each version of the name, obtain the access location. 824 825 for version, (original_name, attrnames, access_number) in all_aliases.items(): 826 accessor_location = (path, name, None, version) 827 access_location = (path, original_name, attrnames, access_number) 828 init_item(self.alias_index, accessor_location, list) 829 self.alias_index[accessor_location].append(access_location) 830 831 # Get aliases in terms of non-aliases and accesses. 832 833 for accessor_location, access_locations in self.alias_index.items(): 834 self.update_aliases(accessor_location, access_locations) 835 836 def update_aliases(self, accessor_location, access_locations, visited=None): 837 838 """ 839 Update the given 'accessor_location' defining an alias, update 840 'access_locations' to refer to non-aliases, following name references 841 via the access index. 842 843 If 'visited' is specified, it contains a set of accessor locations (and 844 thus keys to the alias index) that are currently being defined. 845 """ 846 847 if visited is None: 848 visited = set() 849 850 updated_locations = set() 851 852 for access_location in access_locations: 853 (path, original_name, attrnames, access_number) = access_location 854 855 # Where an alias refers to a name access, obtain the original name 856 # version details. 857 858 if attrnames is None: 859 860 # For each name version, attempt to determine any accesses that 861 # initialise the name. 862 863 for name_accessor_location in self.access_index[access_location]: 864 865 # Already-visited aliases do not contribute details. 866 867 if name_accessor_location in visited: 868 continue 869 870 visited.add(name_accessor_location) 871 872 name_access_locations = self.alias_index.get(name_accessor_location) 873 if name_access_locations: 874 updated_locations.update(self.update_aliases(name_accessor_location, name_access_locations, visited)) 875 else: 876 updated_locations.add(name_accessor_location) 877 878 # Otherwise, record the access details. 879 880 else: 881 updated_locations.add(access_location) 882 883 self.alias_index[accessor_location] = updated_locations 884 return updated_locations 885 886 # Attribute mutation for types. 887 888 def modify_mutated_attributes(self): 889 890 "Identify known, mutated attributes and change their state." 891 892 # Usage-based accesses. 893 894 for usage, all_attrnames in self.assigned_attrs.items(): 895 if not usage: 896 continue 897 898 for path, name, attrnames in all_attrnames: 899 class_types = self.get_class_types_for_usage(usage) 900 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 901 module_types = self.get_module_types_for_usage(usage) 902 903 # Detect self usage within methods in order to narrow the scope 904 # of the mutation. 905 906 t = name == "self" and self.constrain_self_reference(path, class_types, only_instance_types) 907 if t: 908 class_types, only_instance_types, module_types, constrained = t 909 objects = set(class_types).union(only_instance_types).union(module_types) 910 911 self.mutate_attribute(objects, attrnames) 912 913 def mutate_attribute(self, objects, attrnames): 914 915 "Mutate static 'objects' with the given 'attrnames'." 916 917 for name in objects: 918 attr = "%s.%s" % (name, attrnames) 919 value = self.importer.get_object(attr) 920 921 # If the value is None, the attribute is 922 # inherited and need not be set explicitly on 923 # the class concerned. 924 925 if value: 926 self.modified_attributes[attr] = value 927 self.importer.set_object(attr, value.as_var()) 928 929 # Simplification of types. 930 931 def get_most_general_types(self, types): 932 933 "Return the most general types for the given 'types'." 934 935 module_types = set() 936 class_types = set() 937 938 for type in types: 939 ref = self.importer.identify(type) 940 if ref.has_kind("<module>"): 941 module_types.add(type) 942 else: 943 class_types.add(type) 944 945 types = set(self.get_most_general_module_types(module_types)) 946 types.update(self.get_most_general_class_types(class_types)) 947 return types 948 949 def get_most_general_class_types(self, class_types): 950 951 "Return the most general types for the given 'class_types'." 952 953 class_types = set(class_types) 954 to_remove = set() 955 956 for class_type in class_types: 957 for base in self.importer.classes[class_type]: 958 base = base.get_origin() 959 descendants = self.descendants[base] 960 if base in class_types and descendants.issubset(class_types): 961 to_remove.update(descendants) 962 963 class_types.difference_update(to_remove) 964 return class_types 965 966 def get_most_general_module_types(self, module_types): 967 968 "Return the most general type for the given 'module_types'." 969 970 # Where all modules are provided, an object would provide the same 971 # attributes. 972 973 if len(module_types) == len(self.importer.modules): 974 return ["__builtins__.object"] 975 else: 976 return module_types 977 978 # More efficient usage-to-type indexing and retrieval. 979 980 def init_attr_type_indexes(self): 981 982 "Identify the types that can support each attribute name." 983 984 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 985 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs) 986 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 987 988 def _init_attr_type_index(self, attr_types, attrs): 989 990 """ 991 Initialise the 'attr_types' attribute-to-types mapping using the given 992 'attrs' type-to-attributes mapping. 993 """ 994 995 for name, attrnames in attrs.items(): 996 for attrname in attrnames: 997 init_item(attr_types, attrname, set) 998 attr_types[attrname].add(name) 999 1000 def get_class_types_for_usage(self, usage): 1001 1002 "Return names of classes supporting the given 'usage'." 1003 1004 return self._get_types_for_usage(usage, self.attr_class_types, self.importer.all_class_attrs) 1005 1006 def get_instance_types_for_usage(self, usage): 1007 1008 """ 1009 Return names of classes whose instances support the given 'usage' 1010 (as either class or instance attributes). 1011 """ 1012 1013 return self._get_types_for_usage(usage, self.attr_instance_types, self.importer.all_combined_attrs) 1014 1015 def get_module_types_for_usage(self, usage): 1016 1017 "Return names of modules supporting the given 'usage'." 1018 1019 return self._get_types_for_usage(usage, self.attr_module_types, self.importer.all_module_attrs) 1020 1021 def _get_types_for_usage(self, usage, attr_types, attrs): 1022 1023 """ 1024 For the given 'usage' representing attribute usage, return types 1025 recorded in the 'attr_types' attribute-to-types mapping that support 1026 such usage, with the given 'attrs' type-to-attributes mapping used to 1027 quickly assess whether a type supports all of the stated attributes. 1028 """ 1029 1030 # Where no attributes are used, any type would be acceptable. 1031 1032 if not usage: 1033 return attrs.keys() 1034 1035 attrnames = [] 1036 for attrname, invocation in usage: 1037 attrnames.append(attrname) 1038 1039 types = [] 1040 1041 # Obtain types supporting the first attribute name... 1042 1043 for name in attr_types.get(attrnames[0]) or []: 1044 1045 # Record types that support all of the other attributes as well. 1046 1047 _attrnames = attrs[name] 1048 if set(attrnames).issubset(_attrnames): 1049 types.append(name) 1050 1051 return types 1052 1053 # Reference identification. 1054 1055 def identify_references(self): 1056 1057 "Identify references using usage and name reference information." 1058 1059 # Names with associated attribute usage. 1060 1061 for location, usages in self.location_index.items(): 1062 1063 # Obtain attribute usage associated with a name, deducing the nature 1064 # of the name. Obtain types only for branches involving attribute 1065 # usage. (In the absence of usage, any type could be involved, but 1066 # then no accesses exist to require knowledge of the type.) 1067 1068 have_usage = False 1069 have_no_usage_branch = False 1070 1071 for usage in usages: 1072 if not usage: 1073 have_no_usage_branch = True 1074 continue 1075 elif not have_usage: 1076 self.init_definition_details(location) 1077 have_usage = True 1078 self.record_types_for_usage(location, usage) 1079 1080 # Where some usage occurs, but where branches without usage also 1081 # occur, record the types for those branches anyway. 1082 1083 if have_usage and have_no_usage_branch: 1084 self.init_definition_details(location) 1085 self.record_types_for_usage(location, None) 1086 1087 # Specific name-based attribute accesses. 1088 1089 alias_accesses = set() 1090 1091 for access_location, accessor_locations in self.access_index.items(): 1092 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1093 1094 # Anonymous references with attribute chains. 1095 1096 for location, accesses in self.importer.all_attr_accesses.items(): 1097 1098 # Get distinct attribute names. 1099 1100 all_attrnames = set() 1101 1102 for attrnames in accesses: 1103 all_attrnames.update(get_attrnames(attrnames)) 1104 1105 # Get attribute and accessor details for each attribute name. 1106 1107 for attrname in all_attrnames: 1108 access_location = (location, None, attrname, 0) 1109 self.record_types_for_attribute(access_location, attrname) 1110 1111 # References via constant/identified objects. 1112 1113 for location, name_accesses in self.importer.all_const_accesses.items(): 1114 1115 # A mapping from the original name and attributes to resolved access 1116 # details. 1117 1118 for original_access, access in name_accesses.items(): 1119 original_name, original_attrnames = original_access 1120 objpath, ref, attrnames = access 1121 1122 # Build an accessor combining the name and attribute names used. 1123 1124 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1125 1126 # Direct accesses to attributes. 1127 1128 if not attrnames: 1129 1130 # Build a descriptive location based on the original 1131 # details, exposing the final attribute name. 1132 1133 oa, attrname = original_accessor[:-1], original_accessor[-1] 1134 oa = ".".join(oa) 1135 1136 access_location = (location, oa, attrname, 0) 1137 accessor_location = (location, oa, None, 0) 1138 self.access_index[access_location] = [accessor_location] 1139 1140 self.init_access_details(access_location) 1141 self.init_definition_details(accessor_location) 1142 1143 # Obtain a reference for the accessor in order to properly 1144 # determine its type. 1145 1146 if ref.get_kind() != "<instance>": 1147 objpath = ref.get_origin() 1148 1149 objpath = objpath.rsplit(".", 1)[0] 1150 1151 # Where the object name conflicts with the module 1152 # providing it, obtain the module details. 1153 1154 if objpath in self.importer.modules: 1155 accessor = Reference("<module>", objpath) 1156 else: 1157 accessor = self.importer.get_object(objpath) 1158 1159 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1160 self.access_constrained.add(access_location) 1161 1162 class_types, instance_types, module_types = accessor.get_types() 1163 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1164 1165 else: 1166 1167 # Build a descriptive location based on the original 1168 # details, employing the first remaining attribute name. 1169 1170 l = get_attrnames(attrnames) 1171 attrname = l[0] 1172 1173 oa = original_accessor[:-len(l)] 1174 oa = ".".join(oa) 1175 1176 access_location = (location, oa, attrnames, 0) 1177 accessor_location = (location, oa, None, 0) 1178 self.access_index[access_location] = [accessor_location] 1179 1180 self.init_access_details(access_location) 1181 self.init_definition_details(accessor_location) 1182 1183 class_types, instance_types, module_types = ref.get_types() 1184 1185 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1186 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1187 1188 original_location = (location, original_name, original_attrnames, 0) 1189 1190 if original_location != access_location: 1191 self.const_accesses[original_location] = access_location 1192 self.const_accesses_rev[access_location] = original_location 1193 1194 # Aliased name definitions. All aliases with usage will have been 1195 # defined, but they may be refined according to referenced accesses. 1196 1197 for accessor_location in self.alias_index.keys(): 1198 self.record_types_for_alias(accessor_location) 1199 1200 # Update accesses employing aliases. 1201 1202 for access_location in alias_accesses: 1203 self.record_types_for_access(access_location, self.access_index[access_location]) 1204 1205 def constrain_types(self, path, class_types, instance_types, module_types): 1206 1207 """ 1208 Using the given 'path' to an object, constrain the given 'class_types', 1209 'instance_types' and 'module_types'. 1210 1211 Return the class, instance, module types plus whether the types are 1212 constrained to a specific kind of type. 1213 """ 1214 1215 ref = self.importer.identify(path) 1216 if ref: 1217 1218 # Constrain usage suggestions using the identified object. 1219 1220 if ref.has_kind("<class>"): 1221 return ( 1222 set(class_types).intersection([ref.get_origin()]), [], [], True 1223 ) 1224 elif ref.has_kind("<module>"): 1225 return ( 1226 [], [], set(module_types).intersection([ref.get_origin()]), True 1227 ) 1228 1229 return class_types, instance_types, module_types, False 1230 1231 def get_target_types(self, location, usage): 1232 1233 """ 1234 Return the class, instance and module types constrained for the name at 1235 the given 'location' exhibiting the given 'usage'. Whether the types 1236 have been constrained using contextual information is also indicated, 1237 plus whether the types have been constrained to a specific kind of type. 1238 """ 1239 1240 unit_path, name, attrnames, version = location 1241 1242 # Detect any initialised name for the location. 1243 1244 if name: 1245 ref = self.get_initialised_name(location) 1246 if ref: 1247 (class_types, only_instance_types, module_types, 1248 _function_types, _var_types) = separate_types([ref]) 1249 return class_types, only_instance_types, module_types, True, False 1250 1251 # Retrieve the recorded types for the usage. 1252 1253 class_types = self.get_class_types_for_usage(usage) 1254 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1255 module_types = self.get_module_types_for_usage(usage) 1256 1257 # Merge usage deductions with observations to obtain reference types 1258 # for names involved with attribute accesses. 1259 1260 if not name: 1261 return class_types, only_instance_types, module_types, False, False 1262 1263 # Obtain references to known objects. 1264 1265 path = get_name_path(unit_path, name) 1266 1267 class_types, only_instance_types, module_types, constrained_specific = \ 1268 self.constrain_types(path, class_types, only_instance_types, module_types) 1269 1270 if constrained_specific: 1271 return class_types, only_instance_types, module_types, constrained_specific, constrained_specific 1272 1273 # Constrain "self" references. 1274 1275 if name == "self": 1276 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1277 if t: 1278 class_types, only_instance_types, module_types, constrained = t 1279 return class_types, only_instance_types, module_types, constrained, False 1280 1281 return class_types, only_instance_types, module_types, False, False 1282 1283 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1284 1285 """ 1286 Where the name "self" appears in a method, attempt to constrain the 1287 classes involved. 1288 1289 Return the class, instance, module types plus whether the types are 1290 constrained. 1291 """ 1292 1293 class_name = self.in_method(unit_path) 1294 1295 if not class_name: 1296 return None 1297 1298 classes = set([class_name]) 1299 classes.update(self.get_descendants_for_class(class_name)) 1300 1301 # Note that only instances will be expected for these references but 1302 # either classes or instances may provide the attributes. 1303 1304 return ( 1305 set(class_types).intersection(classes), 1306 set(only_instance_types).intersection(classes), 1307 [], True 1308 ) 1309 1310 def in_method(self, path): 1311 1312 "Return whether 'path' refers to a method." 1313 1314 class_name, method_name = path.rsplit(".", 1) 1315 return self.importer.classes.has_key(class_name) and class_name 1316 1317 def init_reference_details(self, location): 1318 1319 "Initialise reference-related details for 'location'." 1320 1321 self.init_definition_details(location) 1322 self.init_access_details(location) 1323 1324 def init_definition_details(self, location): 1325 1326 "Initialise name definition details for 'location'." 1327 1328 self.accessor_class_types[location] = set() 1329 self.accessor_instance_types[location] = set() 1330 self.accessor_module_types[location] = set() 1331 self.provider_class_types[location] = set() 1332 self.provider_instance_types[location] = set() 1333 self.provider_module_types[location] = set() 1334 1335 def init_access_details(self, location): 1336 1337 "Initialise access details at 'location'." 1338 1339 self.referenced_attrs[location] = {} 1340 1341 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1342 1343 """ 1344 Define types for the 'access_location' associated with the given 1345 'accessor_locations'. 1346 """ 1347 1348 attrname = get_attrname_from_location(access_location) 1349 if not attrname: 1350 return 1351 1352 # Collect all suggested types for the accessors. Accesses may 1353 # require accessors from of a subset of the complete set of types. 1354 1355 class_types = set() 1356 module_types = set() 1357 instance_types = set() 1358 1359 constrained = True 1360 1361 for location in accessor_locations: 1362 1363 # Remember accesses employing aliases. 1364 1365 if alias_accesses is not None and self.alias_index.has_key(location): 1366 alias_accesses.add(access_location) 1367 1368 # Use the type information deduced for names from above. 1369 1370 if self.accessor_class_types.has_key(location): 1371 class_types.update(self.accessor_class_types[location]) 1372 module_types.update(self.accessor_module_types[location]) 1373 instance_types.update(self.accessor_instance_types[location]) 1374 1375 # Where accesses are associated with assignments but where no 1376 # attribute usage observations have caused such an association, 1377 # the attribute name is considered by itself. 1378 1379 else: 1380 self.init_definition_details(location) 1381 self.record_types_for_usage(location, [(attrname, False)]) 1382 1383 constrained = location in self.accessor_constrained and constrained 1384 1385 self.init_access_details(access_location) 1386 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1387 1388 def record_types_for_usage(self, accessor_location, usage): 1389 1390 """ 1391 Record types for the given 'accessor_location' according to the given 1392 'usage' observations which may be None to indicate an absence of usage. 1393 """ 1394 1395 (class_types, 1396 instance_types, 1397 module_types, 1398 constrained, 1399 constrained_specific) = self.get_target_types(accessor_location, usage) 1400 1401 invocations = get_invoked_attributes(usage) 1402 1403 self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific, invocations) 1404 1405 def record_types_for_attribute(self, access_location, attrname): 1406 1407 """ 1408 Record types for the 'access_location' employing only the given 1409 'attrname' for type deduction. 1410 """ 1411 1412 usage = ((attrname, False),) 1413 1414 class_types = self.get_class_types_for_usage(usage) 1415 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1416 module_types = self.get_module_types_for_usage(usage) 1417 1418 self.init_reference_details(access_location) 1419 1420 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1421 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1422 1423 def record_types_for_alias(self, accessor_location): 1424 1425 """ 1426 Define types for the 'accessor_location' not having associated usage. 1427 """ 1428 1429 have_access = self.provider_class_types.has_key(accessor_location) 1430 1431 # With an access, attempt to narrow the existing selection of provider 1432 # types. 1433 1434 if have_access: 1435 provider_class_types = self.provider_class_types[accessor_location] 1436 provider_instance_types = self.provider_instance_types[accessor_location] 1437 provider_module_types = self.provider_module_types[accessor_location] 1438 1439 # Find details for any corresponding access. 1440 1441 all_class_types = set() 1442 all_instance_types = set() 1443 all_module_types = set() 1444 1445 for access_location in self.alias_index[accessor_location]: 1446 location, name, attrnames, access_number = access_location 1447 1448 # Alias references an attribute access. 1449 1450 if attrnames: 1451 1452 # Obtain attribute references for the access. 1453 1454 attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]] 1455 1456 # Separate the different attribute types. 1457 1458 (class_types, instance_types, module_types, 1459 function_types, var_types) = separate_types(attrs) 1460 1461 # Where non-accessor types are found, do not attempt to refine 1462 # the defined accessor types. 1463 1464 if function_types or var_types: 1465 return 1466 1467 class_types = set(provider_class_types).intersection(class_types) 1468 instance_types = set(provider_instance_types).intersection(instance_types) 1469 module_types = set(provider_module_types).intersection(module_types) 1470 1471 # Alias references a name, not an access. 1472 1473 else: 1474 # Attempt to refine the types using initialised names. 1475 1476 attr = self.get_initialised_name(access_location) 1477 if attr: 1478 (class_types, instance_types, module_types, 1479 _function_types, _var_types) = separate_types([attr]) 1480 1481 # Where no further information is found, do not attempt to 1482 # refine the defined accessor types. 1483 1484 else: 1485 return 1486 1487 all_class_types.update(class_types) 1488 all_instance_types.update(instance_types) 1489 all_module_types.update(module_types) 1490 1491 # Record refined type details for the alias as an accessor. 1492 1493 self.init_definition_details(accessor_location) 1494 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1495 1496 # Without an access, attempt to identify references for the alias. 1497 1498 else: 1499 refs = set() 1500 1501 for access_location in self.alias_index[accessor_location]: 1502 1503 # Obtain any redefined constant access location. 1504 1505 if self.const_accesses.has_key(access_location): 1506 access_location = self.const_accesses[access_location] 1507 1508 location, name, attrnames, access_number = access_location 1509 1510 # Alias references an attribute access. 1511 1512 if attrnames: 1513 attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]] 1514 refs.update(attrs) 1515 1516 # Alias references a name, not an access. 1517 1518 else: 1519 attr = self.get_initialised_name(access_location) 1520 attrs = attr and [attr] or [] 1521 if not attrs and self.provider_class_types.has_key(access_location): 1522 class_types = self.provider_class_types[access_location] 1523 instance_types = self.provider_instance_types[access_location] 1524 module_types = self.provider_module_types[access_location] 1525 attrs = combine_types(class_types, instance_types, module_types) 1526 if attrs: 1527 refs.update(attrs) 1528 1529 # Record reference details for the alias separately from accessors. 1530 1531 self.referenced_objects[accessor_location] = refs 1532 1533 def get_initialised_name(self, access_location): 1534 1535 """ 1536 Return references for any initialised names at 'access_location', or 1537 None if no such references exist. 1538 """ 1539 1540 location, name, attrnames, version = access_location 1541 path = get_name_path(location, name) 1542 1543 # Use initialiser information, if available. 1544 1545 refs = self.importer.all_initialised_names.get(path) 1546 if refs and refs.has_key(version): 1547 return refs[version] 1548 else: 1549 return None 1550 1551 def record_reference_types(self, location, class_types, instance_types, 1552 module_types, constrained, constrained_specific=False, invocations=None): 1553 1554 """ 1555 Associate attribute provider types with the given 'location', consisting 1556 of the given 'class_types', 'instance_types' and 'module_types'. 1557 1558 If 'constrained' is indicated, the constrained nature of the accessor is 1559 recorded for the location. 1560 1561 If 'constrained_specific' is indicated using a true value, instance types 1562 will not be added to class types to permit access via instances at the 1563 given location. This is only useful where a specific accessor is known 1564 to be a class. 1565 1566 Note that the specified types only indicate the provider types for 1567 attributes, whereas the recorded accessor types indicate the possible 1568 types of the actual objects used to access attributes. 1569 """ 1570 1571 # Update the type details for the location. 1572 1573 self.provider_class_types[location].update(class_types) 1574 self.provider_instance_types[location].update(instance_types) 1575 self.provider_module_types[location].update(module_types) 1576 1577 # Class types support classes and instances as accessors. 1578 # Instance-only and module types support only their own kinds as 1579 # accessors. 1580 1581 path, name, version, attrnames = location 1582 1583 if invocations: 1584 class_only_types = self.filter_for_invocations(class_types, invocations) 1585 else: 1586 class_only_types = class_types 1587 1588 # However, the nature of accessors can be further determined. 1589 # Any self variable may only refer to an instance. 1590 1591 if name != "self" or not self.in_method(path): 1592 self.accessor_class_types[location].update(class_only_types) 1593 1594 if not constrained_specific: 1595 self.accessor_instance_types[location].update(class_types) 1596 1597 self.accessor_instance_types[location].update(instance_types) 1598 1599 if name != "self" or not self.in_method(path): 1600 self.accessor_module_types[location].update(module_types) 1601 1602 if constrained: 1603 self.accessor_constrained.add(location) 1604 1605 def filter_for_invocations(self, class_types, attrnames): 1606 1607 """ 1608 From the given 'class_types', identify methods for the given 1609 'attrnames' that are being invoked, returning a filtered collection of 1610 class types. 1611 """ 1612 1613 to_filter = set() 1614 1615 for class_type in class_types: 1616 for attrname in attrnames: 1617 ref = self.importer.get_class_attribute(class_type, attrname) 1618 parent_class = ref and ref.parent() 1619 1620 if ref and ref.has_kind("<function>") and ( 1621 parent_class == class_type or 1622 class_type in self.descendants[parent_class]): 1623 1624 to_filter.add(class_type) 1625 break 1626 1627 return set(class_types).difference(to_filter) 1628 1629 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1630 1631 """ 1632 Identify reference attributes, associating them with the given 1633 'location', identifying the given 'attrname', employing the given 1634 'class_types', 'instance_types' and 'module_types'. 1635 1636 If 'constrained' is indicated, the constrained nature of the access is 1637 recorded for the location. 1638 """ 1639 1640 # Record the referenced objects. 1641 1642 self.referenced_attrs[location] = \ 1643 self._identify_reference_attribute(attrname, class_types, instance_types, module_types) 1644 1645 if constrained: 1646 self.access_constrained.add(location) 1647 1648 def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types): 1649 1650 """ 1651 Identify the reference attribute with the given 'attrname', employing 1652 the given 'class_types', 'instance_types' and 'module_types'. 1653 """ 1654 1655 attrs = set() 1656 1657 # The class types expose class attributes either directly or via 1658 # instances. 1659 1660 for object_type in class_types: 1661 ref = self.importer.get_class_attribute(object_type, attrname) 1662 if ref: 1663 attrs.add(("<class>", object_type, ref)) 1664 1665 # Add any distinct instance attributes that would be provided 1666 # by instances also providing indirect class attribute access. 1667 1668 for ref in self.importer.get_instance_attributes(object_type, attrname): 1669 attrs.add(("<instance>", object_type, ref)) 1670 1671 # The instance-only types expose instance attributes, but although 1672 # classes are excluded as potential accessors (since they do not provide 1673 # the instance attributes), the class types may still provide some 1674 # attributes. 1675 1676 for object_type in instance_types: 1677 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1678 1679 if instance_attrs: 1680 for ref in instance_attrs: 1681 attrs.add(("<instance>", object_type, ref)) 1682 else: 1683 ref = self.importer.get_class_attribute(object_type, attrname) 1684 if ref: 1685 attrs.add(("<class>", object_type, ref)) 1686 1687 # Module types expose module attributes for module accessors. 1688 1689 for object_type in module_types: 1690 ref = self.importer.get_module_attribute(object_type, attrname) 1691 if ref: 1692 attrs.add(("<module>", object_type, ref)) 1693 1694 return attrs 1695 1696 constrained_specific_tests = ( 1697 "constrained-specific-instance", 1698 "constrained-specific-type", 1699 "constrained-specific-object", 1700 ) 1701 1702 constrained_common_tests = ( 1703 "constrained-common-instance", 1704 "constrained-common-type", 1705 "constrained-common-object", 1706 ) 1707 1708 guarded_specific_tests = ( 1709 "guarded-specific-instance", 1710 "guarded-specific-type", 1711 "guarded-specific-object", 1712 ) 1713 1714 guarded_common_tests = ( 1715 "guarded-common-instance", 1716 "guarded-common-type", 1717 "guarded-common-object", 1718 ) 1719 1720 specific_tests = ( 1721 "specific-instance", 1722 "specific-type", 1723 "specific-object", 1724 ) 1725 1726 common_tests = ( 1727 "common-instance", 1728 "common-type", 1729 "common-object", 1730 ) 1731 1732 class_tests = ( 1733 "guarded-specific-type", 1734 "guarded-common-type", 1735 "specific-type", 1736 "common-type", 1737 ) 1738 1739 class_or_instance_tests = ( 1740 "guarded-specific-object", 1741 "guarded-common-object", 1742 "specific-object", 1743 "common-object", 1744 ) 1745 1746 def get_access_plan(self, location): 1747 1748 """ 1749 Return details of the access at the given 'location'. The details are as 1750 follows: 1751 1752 * the initial accessor (from which accesses will be performed if no 1753 computed static accessor is found) 1754 * details of any test required on the initial accessor 1755 * details of any type employed by the test 1756 * any static accessor (from which accesses will be performed in 1757 preference to the initial accessor) 1758 * attributes needing to be traversed from the base that yield 1759 unambiguous objects 1760 * remaining attributes needing to be tested and traversed 1761 * details of the context 1762 * the method of obtaining the final attribute 1763 * any static final attribute 1764 """ 1765 1766 const_access = self.const_accesses_rev.has_key(location) 1767 1768 path, name, attrnames, version = location 1769 remaining = attrnames.split(".") 1770 attrname = remaining[0] 1771 1772 # Obtain reference and accessor information, retaining also distinct 1773 # provider kind details. 1774 1775 attrs = [] 1776 objtypes = [] 1777 provider_kinds = set() 1778 1779 for attrtype, objtype, attr in self.referenced_attrs[location]: 1780 attrs.append(attr) 1781 objtypes.append(objtype) 1782 provider_kinds.add(attrtype) 1783 1784 # Obtain accessor type and kind information. 1785 1786 accessor_types = self.reference_all_accessor_types[location] 1787 accessor_general_types = self.reference_all_accessor_general_types[location] 1788 accessor_kinds = get_kinds(accessor_general_types) 1789 1790 # Determine any guard or test requirements. 1791 1792 constrained = location in self.access_constrained 1793 test = self.reference_test_types[location] 1794 test_type = self.reference_test_accessor_type.get(location) 1795 1796 # Determine the accessor and provider properties. 1797 1798 class_accessor = "<class>" in accessor_kinds 1799 module_accessor = "<module>" in accessor_kinds 1800 instance_accessor = "<instance>" in accessor_kinds 1801 provided_by_class = "<class>" in provider_kinds 1802 provided_by_instance = "<instance>" in provider_kinds 1803 1804 # Determine how attributes may be accessed relative to the accessor. 1805 1806 object_relative = class_accessor or module_accessor or provided_by_instance 1807 class_relative = instance_accessor and provided_by_class 1808 1809 # Identify the last static attribute for context acquisition. 1810 1811 base = None 1812 dynamic_base = None 1813 1814 # Constant accesses have static accessors. 1815 1816 if const_access: 1817 base = len(objtypes) == 1 and first(objtypes) 1818 1819 # Constant accessors are static. 1820 1821 else: 1822 ref = self.importer.identify("%s.%s" % (path, name)) 1823 if ref: 1824 base = ref.get_origin() 1825 1826 # Usage of previously-generated guard and test details. 1827 1828 elif test in self.constrained_specific_tests: 1829 ref = first(accessor_types) 1830 1831 elif test in self.constrained_common_tests: 1832 ref = first(accessor_general_types) 1833 1834 elif test in self.guarded_specific_tests: 1835 ref = first(accessor_types) 1836 1837 elif test in self.guarded_common_tests: 1838 ref = first(accessor_general_types) 1839 1840 # For attribute-based tests, tentatively identify a dynamic base. 1841 # Such tests allow single or multiple kinds of a type. 1842 1843 elif test in self.common_tests or test in self.specific_tests: 1844 dynamic_base = test_type 1845 1846 # Static accessors. 1847 1848 if not base and test in self.class_tests: 1849 base = ref and ref.get_origin() or dynamic_base 1850 1851 # Accessors that are not static but whose nature is determined. 1852 1853 elif not base and ref: 1854 dynamic_base = ref.get_origin() 1855 1856 traversed = [] 1857 1858 # Traverse remaining attributes. 1859 1860 while len(attrs) == 1: 1861 attr = first(attrs) 1862 1863 traversed.append(attrname) 1864 del remaining[0] 1865 1866 if not remaining: 1867 break 1868 1869 # Update the last static attribute. 1870 1871 if attr.static(): 1872 base = attr.get_origin() 1873 traversed = [] 1874 1875 # Get the next attribute. 1876 1877 attrname = remaining[0] 1878 attrs = self.importer.get_attributes(attr, attrname) 1879 1880 # Where many attributes are suggested, no single attribute identity can 1881 # be loaded. 1882 1883 else: 1884 attr = None 1885 1886 # Determine the method of access. 1887 1888 # Identified attribute that must be accessed via its parent. 1889 1890 if attr and attr.get_name() and location in self.reference_assignments: 1891 final_method = "assign"; origin = attr.get_name() 1892 1893 # Static, identified attribute. 1894 1895 elif attr and attr.static(): 1896 final_method = "static"; origin = attr.final() 1897 1898 # All other methods of access involve traversal. 1899 1900 else: 1901 final_method = "access"; origin = None 1902 1903 # First attribute accessed at a known position via the accessor. 1904 1905 if base or dynamic_base: 1906 first_method = "relative" + (object_relative and "-object" or "") + \ 1907 (class_relative and "-class" or "") 1908 1909 # The fallback case is always run-time testing and access. 1910 1911 else: 1912 first_method = "check" + (object_relative and "-object" or "") + \ 1913 (class_relative and "-class" or "") 1914 1915 # Determine the nature of the context. 1916 1917 context = len(traversed or remaining) == 1 and (base and "base" or "original-accessor") or "final-accessor" 1918 1919 return name, test, test_type, base, traversed, remaining, context, first_method, final_method, origin 1920 1921 # vim: tabstop=4 expandtab shiftwidth=4