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