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, assignment 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 if assignment: 798 self.reference_assignments.add(access_location) 799 800 # Associate assignments with usage. 801 802 accessor_locations = self.get_accessors_for_access(access_location) 803 804 for location in accessor_locations: 805 for usage in self.location_index[location]: 806 key = make_key(usage) 807 808 if assignment: 809 init_item(self.assigned_attrs, key, set) 810 self.assigned_attrs[key].add((path, name, attrnames)) 811 812 def init_aliases(self): 813 814 "Expand aliases so that alias-based accesses can be resolved." 815 816 # Get aliased names with details of their accesses. 817 818 for name_path, all_aliases in self.importer.all_aliased_names.items(): 819 path, name = name_path.rsplit(".", 1) 820 821 # For each version of the name, obtain the access location. 822 823 for version, (original_name, attrnames, access_number) in all_aliases.items(): 824 accessor_location = (path, name, None, version) 825 access_location = (path, original_name, attrnames, access_number) 826 init_item(self.alias_index, accessor_location, list) 827 self.alias_index[accessor_location].append(access_location) 828 829 # Get aliases in terms of non-aliases and accesses. 830 831 for accessor_location, access_locations in self.alias_index.items(): 832 self.update_aliases(accessor_location, access_locations) 833 834 def update_aliases(self, accessor_location, access_locations, visited=None): 835 836 """ 837 Update the given 'accessor_location' defining an alias, update 838 'access_locations' to refer to non-aliases, following name references 839 via the access index. 840 841 If 'visited' is specified, it contains a set of accessor locations (and 842 thus keys to the alias index) that are currently being defined. 843 """ 844 845 if visited is None: 846 visited = set() 847 848 updated_locations = set() 849 850 for access_location in access_locations: 851 (path, original_name, attrnames, access_number) = access_location 852 853 # Where an alias refers to a name access, obtain the original name 854 # version details. 855 856 if attrnames is None: 857 858 # For each name version, attempt to determine any accesses that 859 # initialise the name. 860 861 for name_accessor_location in self.access_index[access_location]: 862 863 # Already-visited aliases do not contribute details. 864 865 if name_accessor_location in visited: 866 continue 867 868 visited.add(name_accessor_location) 869 870 name_access_locations = self.alias_index.get(name_accessor_location) 871 if name_access_locations: 872 updated_locations.update(self.update_aliases(name_accessor_location, name_access_locations, visited)) 873 else: 874 updated_locations.add(name_accessor_location) 875 876 # Otherwise, record the access details. 877 878 else: 879 updated_locations.add(access_location) 880 881 self.alias_index[accessor_location] = updated_locations 882 return updated_locations 883 884 # Attribute mutation for types. 885 886 def modify_mutated_attributes(self): 887 888 "Identify known, mutated attributes and change their state." 889 890 # Usage-based accesses. 891 892 for usage, all_attrnames in self.assigned_attrs.items(): 893 if not usage: 894 continue 895 896 for path, name, attrnames in all_attrnames: 897 class_types = self.get_class_types_for_usage(usage) 898 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 899 module_types = self.get_module_types_for_usage(usage) 900 901 # Detect self usage within methods in order to narrow the scope 902 # of the mutation. 903 904 t = name == "self" and self.constrain_self_reference(path, class_types, only_instance_types) 905 if t: 906 class_types, only_instance_types, module_types, constrained = t 907 objects = set(class_types).union(only_instance_types).union(module_types) 908 909 self.mutate_attribute(objects, attrnames) 910 911 def mutate_attribute(self, objects, attrnames): 912 913 "Mutate static 'objects' with the given 'attrnames'." 914 915 for name in objects: 916 attr = "%s.%s" % (name, attrnames) 917 value = self.importer.get_object(attr) 918 919 # If the value is None, the attribute is 920 # inherited and need not be set explicitly on 921 # the class concerned. 922 923 if value: 924 self.modified_attributes[attr] = value 925 self.importer.set_object(attr, value.as_var()) 926 927 # Simplification of types. 928 929 def get_most_general_types(self, types): 930 931 "Return the most general types for the given 'types'." 932 933 module_types = set() 934 class_types = set() 935 936 for type in types: 937 ref = self.importer.identify(type) 938 if ref.has_kind("<module>"): 939 module_types.add(type) 940 else: 941 class_types.add(type) 942 943 types = set(self.get_most_general_module_types(module_types)) 944 types.update(self.get_most_general_class_types(class_types)) 945 return types 946 947 def get_most_general_class_types(self, class_types): 948 949 "Return the most general types for the given 'class_types'." 950 951 class_types = set(class_types) 952 to_remove = set() 953 954 for class_type in class_types: 955 for base in self.importer.classes[class_type]: 956 base = base.get_origin() 957 descendants = self.descendants[base] 958 if base in class_types and descendants.issubset(class_types): 959 to_remove.update(descendants) 960 961 class_types.difference_update(to_remove) 962 return class_types 963 964 def get_most_general_module_types(self, module_types): 965 966 "Return the most general type for the given 'module_types'." 967 968 # Where all modules are provided, an object would provide the same 969 # attributes. 970 971 if len(module_types) == len(self.importer.modules): 972 return ["__builtins__.object"] 973 else: 974 return module_types 975 976 # Type deduction for usage. 977 978 def get_types_for_usage(self, attrnames, objects): 979 980 """ 981 Identify the types that can support the given 'attrnames', using the 982 given 'objects' as the catalogue of type details. 983 """ 984 985 types = [] 986 for name, _attrnames in objects.items(): 987 if set(attrnames).issubset(_attrnames): 988 types.append(name) 989 return types 990 991 # More efficient usage-to-type indexing and retrieval. 992 993 def init_attr_type_indexes(self): 994 995 "Identify the types that can support each attribute name." 996 997 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 998 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs) 999 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 1000 1001 def _init_attr_type_index(self, attr_types, attrs): 1002 1003 """ 1004 Initialise the 'attr_types' attribute-to-types mapping using the given 1005 'attrs' type-to-attributes mapping. 1006 """ 1007 1008 for name, attrnames in attrs.items(): 1009 for attrname in attrnames: 1010 init_item(attr_types, attrname, set) 1011 attr_types[attrname].add(name) 1012 1013 def get_class_types_for_usage(self, attrnames): 1014 1015 "Return names of classes supporting the given 'attrnames'." 1016 1017 return self._get_types_for_usage(attrnames, self.attr_class_types, self.importer.all_class_attrs) 1018 1019 def get_instance_types_for_usage(self, attrnames): 1020 1021 """ 1022 Return names of classes whose instances support the given 'attrnames' 1023 (as either class or instance attributes). 1024 """ 1025 1026 return self._get_types_for_usage(attrnames, self.attr_instance_types, self.importer.all_combined_attrs) 1027 1028 def get_module_types_for_usage(self, attrnames): 1029 1030 "Return names of modules supporting the given 'attrnames'." 1031 1032 return self._get_types_for_usage(attrnames, self.attr_module_types, self.importer.all_module_attrs) 1033 1034 def _get_types_for_usage(self, attrnames, attr_types, attrs): 1035 1036 """ 1037 For the given 'attrnames' representing attribute usage, return types 1038 recorded in the 'attr_types' attribute-to-types mapping that support 1039 such usage, with the given 'attrs' type-to-attributes mapping used to 1040 quickly assess whether a type supports all of the stated attributes. 1041 """ 1042 1043 # Where no attributes are used, any type would be acceptable. 1044 1045 if not attrnames: 1046 return attrs.keys() 1047 1048 types = [] 1049 1050 # Obtain types supporting the first attribute name... 1051 1052 for name in attr_types.get(attrnames[0]) or []: 1053 1054 # Record types that support all of the other attributes as well. 1055 1056 _attrnames = attrs[name] 1057 if set(attrnames).issubset(_attrnames): 1058 types.append(name) 1059 1060 return types 1061 1062 # Reference identification. 1063 1064 def identify_references(self): 1065 1066 "Identify references using usage and name reference information." 1067 1068 # Names with associated attribute usage. 1069 1070 for location, usages in self.location_index.items(): 1071 1072 # Obtain attribute usage associated with a name, deducing the nature 1073 # of the name. Obtain types only for branches involving attribute 1074 # usage. (In the absence of usage, any type could be involved, but 1075 # then no accesses exist to require knowledge of the type.) 1076 1077 have_usage = False 1078 have_no_usage_branch = False 1079 1080 for usage in usages: 1081 if not usage: 1082 have_no_usage_branch = True 1083 continue 1084 elif not have_usage: 1085 self.init_definition_details(location) 1086 have_usage = True 1087 self.record_types_for_usage(location, usage) 1088 1089 # Where some usage occurs, but where branches without usage also 1090 # occur, record the types for those branches anyway. 1091 1092 if have_usage and have_no_usage_branch: 1093 self.init_definition_details(location) 1094 self.record_types_for_usage(location, None) 1095 1096 # Specific name-based attribute accesses. 1097 1098 alias_accesses = set() 1099 1100 for access_location, accessor_locations in self.access_index.items(): 1101 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1102 1103 # Anonymous references with attribute chains. 1104 1105 for location, accesses in self.importer.all_attr_accesses.items(): 1106 1107 # Get distinct attribute names. 1108 1109 all_attrnames = set() 1110 1111 for attrnames in accesses: 1112 all_attrnames.update(get_attrnames(attrnames)) 1113 1114 # Get attribute and accessor details for each attribute name. 1115 1116 for attrname in all_attrnames: 1117 access_location = (location, None, attrname, 0) 1118 self.record_types_for_attribute(access_location, attrname) 1119 1120 # References via constant/identified objects. 1121 1122 for location, name_accesses in self.importer.all_const_accesses.items(): 1123 1124 # A mapping from the original name and attributes to resolved access 1125 # details. 1126 1127 for original_access, access in name_accesses.items(): 1128 original_name, original_attrnames = original_access 1129 objpath, ref, attrnames = access 1130 1131 # Build an accessor combining the name and attribute names used. 1132 1133 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1134 1135 # Direct accesses to attributes. 1136 1137 if not attrnames: 1138 1139 # Build a descriptive location based on the original 1140 # details, exposing the final attribute name. 1141 1142 oa, attrname = original_accessor[:-1], original_accessor[-1] 1143 oa = ".".join(oa) 1144 1145 access_location = (location, oa, attrname, 0) 1146 accessor_location = (location, oa, None, 0) 1147 self.access_index[access_location] = [accessor_location] 1148 1149 self.init_access_details(access_location) 1150 self.init_definition_details(accessor_location) 1151 1152 # Obtain a reference for the accessor in order to properly 1153 # determine its type. 1154 1155 if ref.get_kind() != "<instance>": 1156 objpath = ref.get_origin() 1157 1158 objpath = objpath.rsplit(".", 1)[0] 1159 1160 # Where the object name conflicts with the module 1161 # providing it, obtain the module details. 1162 1163 if objpath in self.importer.modules: 1164 accessor = Reference("<module>", objpath) 1165 else: 1166 accessor = self.importer.get_object(objpath) 1167 1168 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1169 self.access_constrained.add(access_location) 1170 1171 class_types, instance_types, module_types = accessor.get_types() 1172 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1173 1174 else: 1175 1176 # Build a descriptive location based on the original 1177 # details, employing the first remaining attribute name. 1178 1179 l = get_attrnames(attrnames) 1180 attrname = l[0] 1181 1182 oa = original_accessor[:-len(l)] 1183 oa = ".".join(oa) 1184 1185 access_location = (location, oa, attrnames, 0) 1186 accessor_location = (location, oa, None, 0) 1187 self.access_index[access_location] = [accessor_location] 1188 1189 self.init_access_details(access_location) 1190 self.init_definition_details(accessor_location) 1191 1192 class_types, instance_types, module_types = ref.get_types() 1193 1194 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1195 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1196 1197 original_location = (location, original_name, original_attrnames, 0) 1198 1199 if original_location != access_location: 1200 self.const_accesses[original_location] = access_location 1201 self.const_accesses_rev[access_location] = original_location 1202 1203 # Aliased name definitions. All aliases with usage will have been 1204 # defined, but they may be refined according to referenced accesses. 1205 1206 for accessor_location in self.alias_index.keys(): 1207 self.record_types_for_alias(accessor_location) 1208 1209 # Update accesses employing aliases. 1210 1211 for access_location in alias_accesses: 1212 self.record_types_for_access(access_location, self.access_index[access_location]) 1213 1214 def constrain_types(self, path, class_types, instance_types, module_types): 1215 1216 """ 1217 Using the given 'path' to an object, constrain the given 'class_types', 1218 'instance_types' and 'module_types'. 1219 1220 Return the class, instance, module types plus whether the types are 1221 constrained to a specific kind of type. 1222 """ 1223 1224 ref = self.importer.identify(path) 1225 if ref: 1226 1227 # Constrain usage suggestions using the identified object. 1228 1229 if ref.has_kind("<class>"): 1230 return ( 1231 set(class_types).intersection([ref.get_origin()]), [], [], True 1232 ) 1233 elif ref.has_kind("<module>"): 1234 return ( 1235 [], [], set(module_types).intersection([ref.get_origin()]), True 1236 ) 1237 1238 return class_types, instance_types, module_types, False 1239 1240 def get_target_types(self, location, usage): 1241 1242 """ 1243 Return the class, instance and module types constrained for the name at 1244 the given 'location' exhibiting the given 'usage'. Whether the types 1245 have been constrained using contextual information is also indicated, 1246 plus whether the types have been constrained to a specific kind of type. 1247 """ 1248 1249 unit_path, name, attrnames, version = location 1250 1251 # Detect any initialised name for the location. 1252 1253 if name: 1254 ref = self.get_initialised_name(location) 1255 if ref: 1256 (class_types, only_instance_types, module_types, 1257 _function_types, _var_types) = separate_types([ref]) 1258 return class_types, only_instance_types, module_types, True, False 1259 1260 # Retrieve the recorded types for the usage. 1261 1262 class_types = self.get_class_types_for_usage(usage) 1263 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1264 module_types = self.get_module_types_for_usage(usage) 1265 1266 # Merge usage deductions with observations to obtain reference types 1267 # for names involved with attribute accesses. 1268 1269 if not name: 1270 return class_types, only_instance_types, module_types, False, False 1271 1272 # Obtain references to known objects. 1273 1274 path = get_name_path(unit_path, name) 1275 1276 class_types, only_instance_types, module_types, constrained_specific = \ 1277 self.constrain_types(path, class_types, only_instance_types, module_types) 1278 1279 if constrained_specific: 1280 return class_types, only_instance_types, module_types, constrained_specific, constrained_specific 1281 1282 # Constrain "self" references. 1283 1284 if name == "self": 1285 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1286 if t: 1287 class_types, only_instance_types, module_types, constrained = t 1288 return class_types, only_instance_types, module_types, constrained, False 1289 1290 return class_types, only_instance_types, module_types, False, False 1291 1292 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1293 1294 """ 1295 Where the name "self" appears in a method, attempt to constrain the 1296 classes involved. 1297 1298 Return the class, instance, module types plus whether the types are 1299 constrained. 1300 """ 1301 1302 class_name = self.in_method(unit_path) 1303 1304 if not class_name: 1305 return None 1306 1307 classes = set([class_name]) 1308 classes.update(self.get_descendants_for_class(class_name)) 1309 1310 # Note that only instances will be expected for these references but 1311 # either classes or instances may provide the attributes. 1312 1313 return ( 1314 set(class_types).intersection(classes), 1315 set(only_instance_types).intersection(classes), 1316 [], True 1317 ) 1318 1319 def in_method(self, path): 1320 1321 "Return whether 'path' refers to a method." 1322 1323 class_name, method_name = path.rsplit(".", 1) 1324 return self.importer.classes.has_key(class_name) and class_name 1325 1326 def init_reference_details(self, location): 1327 1328 "Initialise reference-related details for 'location'." 1329 1330 self.init_definition_details(location) 1331 self.init_access_details(location) 1332 1333 def init_definition_details(self, location): 1334 1335 "Initialise name definition details for 'location'." 1336 1337 self.accessor_class_types[location] = set() 1338 self.accessor_instance_types[location] = set() 1339 self.accessor_module_types[location] = set() 1340 self.provider_class_types[location] = set() 1341 self.provider_instance_types[location] = set() 1342 self.provider_module_types[location] = set() 1343 1344 def init_access_details(self, location): 1345 1346 "Initialise access details at 'location'." 1347 1348 self.referenced_attrs[location] = {} 1349 1350 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1351 1352 """ 1353 Define types for the 'access_location' associated with the given 1354 'accessor_locations'. 1355 """ 1356 1357 path, name, attrnames, version = access_location 1358 if not attrnames: 1359 return 1360 1361 attrname = get_attrnames(attrnames)[0] 1362 1363 # Collect all suggested types for the accessors. Accesses may 1364 # require accessors from of a subset of the complete set of types. 1365 1366 class_types = set() 1367 module_types = set() 1368 instance_types = set() 1369 1370 constrained = True 1371 1372 for location in accessor_locations: 1373 1374 # Remember accesses employing aliases. 1375 1376 if alias_accesses is not None and self.alias_index.has_key(location): 1377 alias_accesses.add(access_location) 1378 1379 # Use the type information deduced for names from above. 1380 1381 if self.accessor_class_types.has_key(location): 1382 class_types.update(self.accessor_class_types[location]) 1383 module_types.update(self.accessor_module_types[location]) 1384 instance_types.update(self.accessor_instance_types[location]) 1385 1386 # Where accesses are associated with assignments but where no 1387 # attribute usage observations have caused such an association, 1388 # the attribute name is considered by itself. 1389 1390 else: 1391 self.init_definition_details(location) 1392 self.record_types_for_usage(location, [attrname]) 1393 1394 constrained = location in self.accessor_constrained and constrained 1395 1396 self.init_access_details(access_location) 1397 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1398 1399 def record_types_for_usage(self, accessor_location, usage): 1400 1401 """ 1402 Record types for the given 'accessor_location' according to the given 1403 'usage' observations which may be None to indicate an absence of usage. 1404 """ 1405 1406 (class_types, 1407 instance_types, 1408 module_types, 1409 constrained, 1410 constrained_specific) = self.get_target_types(accessor_location, usage) 1411 1412 self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific) 1413 1414 def record_types_for_attribute(self, access_location, attrname): 1415 1416 """ 1417 Record types for the 'access_location' employing only the given 1418 'attrname' for type deduction. 1419 """ 1420 1421 usage = [attrname] 1422 1423 class_types = self.get_class_types_for_usage(usage) 1424 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1425 module_types = self.get_module_types_for_usage(usage) 1426 1427 self.init_reference_details(access_location) 1428 1429 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1430 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1431 1432 def record_types_for_alias(self, accessor_location): 1433 1434 """ 1435 Define types for the 'accessor_location' not having associated usage. 1436 """ 1437 1438 have_access = self.provider_class_types.has_key(accessor_location) 1439 1440 # With an access, attempt to narrow the existing selection of provider 1441 # types. 1442 1443 if have_access: 1444 provider_class_types = self.provider_class_types[accessor_location] 1445 provider_instance_types = self.provider_instance_types[accessor_location] 1446 provider_module_types = self.provider_module_types[accessor_location] 1447 1448 # Find details for any corresponding access. 1449 1450 all_class_types = set() 1451 all_instance_types = set() 1452 all_module_types = set() 1453 1454 for access_location in self.alias_index[accessor_location]: 1455 location, name, attrnames, access_number = access_location 1456 1457 # Alias references an attribute access. 1458 1459 if attrnames: 1460 1461 # Obtain attribute references for the access. 1462 1463 attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]] 1464 1465 # Separate the different attribute types. 1466 1467 (class_types, instance_types, module_types, 1468 function_types, var_types) = separate_types(attrs) 1469 1470 # Where non-accessor types are found, do not attempt to refine 1471 # the defined accessor types. 1472 1473 if function_types or var_types: 1474 return 1475 1476 class_types = set(provider_class_types).intersection(class_types) 1477 instance_types = set(provider_instance_types).intersection(instance_types) 1478 module_types = set(provider_module_types).intersection(module_types) 1479 1480 # Alias references a name, not an access. 1481 1482 else: 1483 # Attempt to refine the types using initialised names. 1484 1485 attr = self.get_initialised_name(access_location) 1486 if attr: 1487 (class_types, instance_types, module_types, 1488 _function_types, _var_types) = separate_types([attr]) 1489 1490 # Where no further information is found, do not attempt to 1491 # refine the defined accessor types. 1492 1493 else: 1494 return 1495 1496 all_class_types.update(class_types) 1497 all_instance_types.update(instance_types) 1498 all_module_types.update(module_types) 1499 1500 # Record refined type details for the alias as an accessor. 1501 1502 self.init_definition_details(accessor_location) 1503 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1504 1505 # Without an access, attempt to identify references for the alias. 1506 1507 else: 1508 refs = set() 1509 1510 for access_location in self.alias_index[accessor_location]: 1511 1512 # Obtain any redefined constant access location. 1513 1514 if self.const_accesses.has_key(access_location): 1515 access_location = self.const_accesses[access_location] 1516 1517 location, name, attrnames, access_number = access_location 1518 1519 # Alias references an attribute access. 1520 1521 if attrnames: 1522 attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]] 1523 refs.update(attrs) 1524 1525 # Alias references a name, not an access. 1526 1527 else: 1528 attr = self.get_initialised_name(access_location) 1529 attrs = attr and [attr] or [] 1530 if not attrs and self.provider_class_types.has_key(access_location): 1531 class_types = self.provider_class_types[access_location] 1532 instance_types = self.provider_instance_types[access_location] 1533 module_types = self.provider_module_types[access_location] 1534 attrs = combine_types(class_types, instance_types, module_types) 1535 if attrs: 1536 refs.update(attrs) 1537 1538 # Record reference details for the alias separately from accessors. 1539 1540 self.referenced_objects[accessor_location] = refs 1541 1542 def get_initialised_name(self, access_location): 1543 1544 """ 1545 Return references for any initialised names at 'access_location', or 1546 None if no such references exist. 1547 """ 1548 1549 location, name, attrnames, version = access_location 1550 path = get_name_path(location, name) 1551 1552 # Use initialiser information, if available. 1553 1554 refs = self.importer.all_initialised_names.get(path) 1555 if refs and refs.has_key(version): 1556 return refs[version] 1557 else: 1558 return None 1559 1560 def record_reference_types(self, location, class_types, instance_types, 1561 module_types, constrained, constrained_specific=False): 1562 1563 """ 1564 Associate attribute provider types with the given 'location', consisting 1565 of the given 'class_types', 'instance_types' and 'module_types'. 1566 1567 If 'constrained' is indicated, the constrained nature of the accessor is 1568 recorded for the location. 1569 1570 If 'constrained_specific' is indicated using a true value, instance types 1571 will not be added to class types to permit access via instances at the 1572 given location. This is only useful where a specific accessor is known 1573 to be a class. 1574 1575 Note that the specified types only indicate the provider types for 1576 attributes, whereas the recorded accessor types indicate the possible 1577 types of the actual objects used to access attributes. 1578 """ 1579 1580 # Update the type details for the location. 1581 1582 self.provider_class_types[location].update(class_types) 1583 self.provider_instance_types[location].update(instance_types) 1584 self.provider_module_types[location].update(module_types) 1585 1586 # Class types support classes and instances as accessors. 1587 # Instance-only and module types support only their own kinds as 1588 # accessors. 1589 1590 # However, the nature of accessors can be further determined. 1591 # Any self variable may only refer to an instance. 1592 1593 path, name, version, attrnames = location 1594 if name != "self" or not self.in_method(path): 1595 self.accessor_class_types[location].update(class_types) 1596 1597 if not constrained_specific: 1598 self.accessor_instance_types[location].update(class_types) 1599 1600 self.accessor_instance_types[location].update(instance_types) 1601 1602 if name != "self" or not self.in_method(path): 1603 self.accessor_module_types[location].update(module_types) 1604 1605 if constrained: 1606 self.accessor_constrained.add(location) 1607 1608 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1609 1610 """ 1611 Identify reference attributes, associating them with the given 1612 'location', identifying the given 'attrname', employing the given 1613 'class_types', 'instance_types' and 'module_types'. 1614 1615 If 'constrained' is indicated, the constrained nature of the access is 1616 recorded for the location. 1617 """ 1618 1619 # Record the referenced objects. 1620 1621 self.referenced_attrs[location] = \ 1622 self._identify_reference_attribute(attrname, class_types, instance_types, module_types) 1623 1624 if constrained: 1625 self.access_constrained.add(location) 1626 1627 def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types): 1628 1629 """ 1630 Identify the reference attribute with the given 'attrname', employing 1631 the given 'class_types', 'instance_types' and 'module_types'. 1632 """ 1633 1634 attrs = set() 1635 1636 # The class types expose class attributes either directly or via 1637 # instances. 1638 1639 for object_type in class_types: 1640 ref = self.importer.get_class_attribute(object_type, attrname) 1641 if ref: 1642 attrs.add(("<class>", object_type, ref)) 1643 1644 # Add any distinct instance attributes that would be provided 1645 # by instances also providing indirect class attribute access. 1646 1647 for ref in self.importer.get_instance_attributes(object_type, attrname): 1648 attrs.add(("<instance>", object_type, ref)) 1649 1650 # The instance-only types expose instance attributes, but although 1651 # classes are excluded as potential accessors (since they do not provide 1652 # the instance attributes), the class types may still provide some 1653 # attributes. 1654 1655 for object_type in instance_types: 1656 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1657 1658 if instance_attrs: 1659 for ref in instance_attrs: 1660 attrs.add(("<instance>", object_type, ref)) 1661 else: 1662 ref = self.importer.get_class_attribute(object_type, attrname) 1663 if ref: 1664 attrs.add(("<class>", object_type, ref)) 1665 1666 # Module types expose module attributes for module accessors. 1667 1668 for object_type in module_types: 1669 ref = self.importer.get_module_attribute(object_type, attrname) 1670 if ref: 1671 attrs.add(("<module>", object_type, ref)) 1672 1673 return attrs 1674 1675 constrained_specific_tests = ( 1676 "constrained-specific-instance", 1677 "constrained-specific-type", 1678 "constrained-specific-object", 1679 ) 1680 1681 constrained_common_tests = ( 1682 "constrained-common-instance", 1683 "constrained-common-type", 1684 "constrained-common-object", 1685 ) 1686 1687 guarded_specific_tests = ( 1688 "guarded-specific-instance", 1689 "guarded-specific-type", 1690 "guarded-specific-object", 1691 ) 1692 1693 guarded_common_tests = ( 1694 "guarded-common-instance", 1695 "guarded-common-type", 1696 "guarded-common-object", 1697 ) 1698 1699 specific_tests = ( 1700 "specific-instance", 1701 "specific-type", 1702 "specific-object", 1703 ) 1704 1705 common_tests = ( 1706 "common-instance", 1707 "common-type", 1708 "common-object", 1709 ) 1710 1711 class_tests = ( 1712 "guarded-specific-type", 1713 "guarded-common-type", 1714 "specific-type", 1715 "common-type", 1716 ) 1717 1718 class_or_instance_tests = ( 1719 "guarded-specific-object", 1720 "guarded-common-object", 1721 "specific-object", 1722 "common-object", 1723 ) 1724 1725 def get_access_plan(self, location): 1726 1727 """ 1728 Return details of the access at the given 'location'. The details are as 1729 follows: 1730 1731 * the initial accessor (from which accesses will be performed if no 1732 computed static accessor is found) 1733 * details of any test required on the initial accessor 1734 * details of any type employed by the test 1735 * any static accessor (from which accesses will be performed in 1736 preference to the initial accessor) 1737 * attributes needing to be traversed from the base that yield 1738 unambiguous objects 1739 * remaining attributes needing to be tested and traversed 1740 * details of the context 1741 * the method of obtaining the final attribute 1742 * any static final attribute 1743 """ 1744 1745 const_access = self.const_accesses_rev.has_key(location) 1746 1747 path, name, attrnames, version = location 1748 remaining = attrnames.split(".") 1749 attrname = remaining[0] 1750 1751 # Obtain reference and accessor information, retaining also distinct 1752 # provider kind details. 1753 1754 attrs = [] 1755 objtypes = [] 1756 provider_kinds = set() 1757 1758 for attrtype, objtype, attr in self.referenced_attrs[location]: 1759 attrs.append(attr) 1760 objtypes.append(objtype) 1761 provider_kinds.add(attrtype) 1762 1763 # Obtain accessor type and kind information. 1764 1765 accessor_types = self.reference_all_accessor_types[location] 1766 accessor_general_types = self.reference_all_accessor_general_types[location] 1767 accessor_kinds = get_kinds(accessor_general_types) 1768 1769 # Determine any guard or test requirements. 1770 1771 constrained = location in self.access_constrained 1772 test = self.reference_test_types[location] 1773 test_type = self.reference_test_accessor_type.get(location) 1774 1775 # Determine the accessor and provider properties. 1776 1777 class_accessor = "<class>" in accessor_kinds 1778 module_accessor = "<module>" in accessor_kinds 1779 instance_accessor = "<instance>" in accessor_kinds 1780 provided_by_class = "<class>" in provider_kinds 1781 provided_by_instance = "<instance>" in provider_kinds 1782 1783 # Determine how attributes may be accessed relative to the accessor. 1784 1785 object_relative = class_accessor or module_accessor or provided_by_instance 1786 class_relative = instance_accessor and provided_by_class 1787 1788 # Identify the last static attribute for context acquisition. 1789 1790 base = None 1791 dynamic_base = None 1792 1793 # Constant accesses have static accessors. 1794 1795 if const_access: 1796 base = len(objtypes) == 1 and first(objtypes) 1797 1798 # Constant accessors are static. 1799 1800 else: 1801 ref = self.importer.identify("%s.%s" % (path, name)) 1802 if ref: 1803 base = ref.get_origin() 1804 1805 # Usage of previously-generated guard and test details. 1806 1807 elif test in self.constrained_specific_tests: 1808 ref = first(accessor_types) 1809 1810 elif test in self.constrained_common_tests: 1811 ref = first(accessor_general_types) 1812 1813 elif test in self.guarded_specific_tests: 1814 ref = first(accessor_types) 1815 1816 elif test in self.guarded_common_tests: 1817 ref = first(accessor_general_types) 1818 1819 # For attribute-based tests, tentatively identify a dynamic base. 1820 # Such tests allow single or multiple kinds of a type. 1821 1822 elif test in self.common_tests or test in self.specific_tests: 1823 dynamic_base = test_type 1824 1825 # Static accessors. 1826 1827 if not base and test in self.class_tests: 1828 base = ref and ref.get_origin() or dynamic_base 1829 1830 # Accessors that are not static but whose nature is determined. 1831 1832 elif not base and ref: 1833 dynamic_base = ref.get_origin() 1834 1835 traversed = [] 1836 1837 # Traverse remaining attributes. 1838 1839 while len(attrs) == 1: 1840 attr = first(attrs) 1841 1842 traversed.append(attrname) 1843 del remaining[0] 1844 1845 if not remaining: 1846 break 1847 1848 # Update the last static attribute. 1849 1850 if attr.static(): 1851 base = attr.get_origin() 1852 traversed = [] 1853 1854 # Get the next attribute. 1855 1856 attrname = remaining[0] 1857 attrs = self.importer.get_attributes(attr, attrname) 1858 1859 # Where many attributes are suggested, no single attribute identity can 1860 # be loaded. 1861 1862 else: 1863 attr = None 1864 1865 # Determine the method of access. 1866 1867 # Identified attribute that must be accessed via its parent. 1868 1869 if attr and attr.get_name() and location in self.reference_assignments: 1870 method = "direct"; origin = attr.get_name() 1871 1872 # Static, identified attribute. 1873 1874 elif attr and attr.static(): 1875 method = "static"; origin = attr.final() 1876 1877 # Attribute accessed at a known position via its parent. 1878 1879 elif base or dynamic_base: 1880 method = "relative" + (object_relative and "-object" or "") + \ 1881 (class_relative and "-class" or "") 1882 origin = None 1883 1884 # The fallback case is always run-time testing and access. 1885 1886 else: 1887 method = "check" + (object_relative and "-object" or "") + \ 1888 (class_relative and "-class" or "") 1889 origin = None 1890 1891 # Determine the nature of the context. 1892 1893 context = base and "base" or len(traversed or remaining) > 1 and "traversal" or "accessor" 1894 1895 return name, test, test_type, base, traversed, remaining, context, method, origin 1896 1897 # vim: tabstop=4 expandtab shiftwidth=4