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