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