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