1 #!/usr/bin/env python 2 3 """ 4 Deduce types for usage observations. 5 6 Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from common import first, get_attrname_from_location, get_attrnames, \ 23 get_invoked_attributes, get_name_path, init_item, \ 24 sorted_output, CommonOutput 25 from encoders import encode_attrnames, encode_access_location, \ 26 encode_constrained, encode_location, encode_usage, \ 27 get_kinds, test_for_kinds, test_for_type 28 from errors import DeduceError 29 from os.path import join 30 from referencing import combine_types, is_single_class_type, separate_types, \ 31 Reference 32 33 class Deducer(CommonOutput): 34 35 "Deduce types in a program." 36 37 def __init__(self, importer, output): 38 39 """ 40 Initialise an instance using the given 'importer' that will perform 41 deductions on the program information, writing the results to the given 42 'output' directory. 43 """ 44 45 self.importer = importer 46 self.output = output 47 48 # Descendants of classes. 49 50 self.descendants = {} 51 self.init_descendants() 52 self.init_special_attributes() 53 54 # Map locations to usage in order to determine specific types. 55 56 self.location_index = {} 57 58 # Map access locations to definition locations. 59 60 self.access_index = {} 61 62 # Map aliases to accesses that define them. 63 64 self.alias_index = {} 65 66 # Map constant accesses to redefined accesses. 67 68 self.const_accesses = {} 69 self.const_accesses_rev = {} 70 71 # Map usage observations to assigned attributes. 72 73 self.assigned_attrs = {} 74 75 # Map usage observations to objects. 76 77 self.attr_class_types = {} 78 self.attr_instance_types = {} 79 self.attr_module_types = {} 80 81 # Modified attributes from usage observations. 82 83 self.modified_attributes = {} 84 85 # Accesses that are assignments. 86 87 self.reference_assignments = set() 88 89 # Map locations to types, constrained indicators and attributes. 90 91 self.accessor_class_types = {} 92 self.accessor_instance_types = {} 93 self.accessor_module_types = {} 94 self.provider_class_types = {} 95 self.provider_instance_types = {} 96 self.provider_module_types = {} 97 self.accessor_constrained = set() 98 self.access_constrained = set() 99 self.referenced_attrs = {} 100 self.referenced_objects = {} 101 102 # Details of access operations. 103 104 self.access_plans = {} 105 106 # Accumulated information about accessors and providers. 107 108 self.accessor_general_class_types = {} 109 self.accessor_general_instance_types = {} 110 self.accessor_general_module_types = {} 111 self.accessor_all_types = {} 112 self.accessor_all_general_types = {} 113 self.provider_all_types = {} 114 self.accessor_guard_tests = {} 115 116 # Accumulated information about accessed attributes and 117 # access/attribute-specific accessor tests. 118 119 self.reference_all_attrs = {} 120 self.reference_all_attrtypes = {} 121 self.reference_all_accessor_types = {} 122 self.reference_all_accessor_general_types = {} 123 self.reference_test_types = {} 124 self.reference_test_accessor_type = {} 125 126 # The processing workflow itself. 127 128 self.init_usage_index() 129 self.init_accessors() 130 self.init_accesses() 131 self.init_aliases() 132 self.init_attr_type_indexes() 133 self.modify_mutated_attributes() 134 self.identify_references() 135 self.classify_accessors() 136 self.classify_accesses() 137 self.initialise_access_plans() 138 139 def to_output(self): 140 141 "Write the output files using deduction information." 142 143 self.check_output() 144 145 self.write_mutations() 146 self.write_accessors() 147 self.write_accesses() 148 self.write_access_plans() 149 150 def write_mutations(self): 151 152 """ 153 Write mutation-related output in the following format: 154 155 qualified name " " original object type 156 157 Object type can be "<class>", "<function>" or "<var>". 158 """ 159 160 f = open(join(self.output, "mutations"), "w") 161 try: 162 attrs = self.modified_attributes.items() 163 attrs.sort() 164 165 for attr, value in attrs: 166 print >>f, attr, value 167 finally: 168 f.close() 169 170 def write_accessors(self): 171 172 """ 173 Write reference-related output in the following format for types: 174 175 location " " ( "constrained" | "deduced" ) " " attribute type " " most general type names " " number of specific types 176 177 Note that multiple lines can be given for each location, one for each 178 attribute type. 179 180 Locations have the following format: 181 182 qualified name of scope "." local name ":" name version 183 184 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 185 where the latter indicates an absence of suitable references. 186 187 Type names indicate the type providing the attributes, being either a 188 class or module qualified name. 189 190 ---- 191 192 A summary of accessor types is formatted as follows: 193 194 location " " ( "constrained" | "deduced" ) " " ( "specific" | "common" | "unguarded" ) " " most general type names " " number of specific types 195 196 This summary groups all attribute types (class, instance, module) into a 197 single line in order to determine the complexity of identifying an 198 accessor. 199 200 ---- 201 202 References that cannot be supported by any types are written to a 203 warnings file in the following format: 204 205 location 206 207 ---- 208 209 For each location where a guard would be asserted to guarantee the 210 nature of an object, the following format is employed: 211 212 location " " ( "specific" | "common" ) " " object kind " " object types 213 214 Object kind can be "<class>", "<instance>" or "<module>". 215 """ 216 217 f_type_summary = open(join(self.output, "type_summary"), "w") 218 f_types = open(join(self.output, "types"), "w") 219 f_warnings = open(join(self.output, "type_warnings"), "w") 220 f_guards = open(join(self.output, "guards"), "w") 221 222 try: 223 locations = self.accessor_class_types.keys() 224 locations.sort() 225 226 for location in locations: 227 constrained = location in self.accessor_constrained 228 229 # Accessor information. 230 231 class_types = self.accessor_class_types[location] 232 instance_types = self.accessor_instance_types[location] 233 module_types = self.accessor_module_types[location] 234 235 general_class_types = self.accessor_general_class_types[location] 236 general_instance_types = self.accessor_general_instance_types[location] 237 general_module_types = self.accessor_general_module_types[location] 238 239 all_types = self.accessor_all_types[location] 240 all_general_types = self.accessor_all_general_types[location] 241 242 if class_types: 243 print >>f_types, encode_location(location), encode_constrained(constrained), "<class>", \ 244 sorted_output(general_class_types), len(class_types) 245 246 if instance_types: 247 print >>f_types, encode_location(location), encode_constrained(constrained), "<instance>", \ 248 sorted_output(general_instance_types), len(instance_types) 249 250 if module_types: 251 print >>f_types, encode_location(location), encode_constrained(constrained), "<module>", \ 252 sorted_output(general_module_types), len(module_types) 253 254 if not all_types: 255 print >>f_types, encode_location(location), "deduced", "<>", 0 256 attrnames = list(self.location_index[location]) 257 attrnames.sort() 258 print >>f_warnings, encode_location(location), "; ".join(map(encode_usage, attrnames)) 259 260 guard_test = self.accessor_guard_tests.get(location) 261 262 # Write specific type guard details. 263 264 if guard_test and guard_test.startswith("specific"): 265 print >>f_guards, encode_location(location), guard_test, \ 266 get_kinds(all_types)[0], \ 267 sorted_output(all_types) 268 269 # Write common type guard details. 270 271 elif guard_test and guard_test.startswith("common"): 272 print >>f_guards, encode_location(location), guard_test, \ 273 get_kinds(all_general_types)[0], \ 274 sorted_output(all_general_types) 275 276 print >>f_type_summary, encode_location(location), encode_constrained(constrained), \ 277 guard_test or "unguarded", sorted_output(all_general_types), len(all_types) 278 279 finally: 280 f_type_summary.close() 281 f_types.close() 282 f_warnings.close() 283 f_guards.close() 284 285 def write_accesses(self): 286 287 """ 288 Specific attribute output is produced in the following format: 289 290 location " " ( "constrained" | "deduced" ) " " attribute type " " attribute references 291 292 Note that multiple lines can be given for each location and attribute 293 name, one for each attribute type. 294 295 Locations have the following format: 296 297 qualified name of scope "." local name " " attribute name ":" access number 298 299 The attribute type can be "<class>", "<instance>", "<module>" or "<>", 300 where the latter indicates an absence of suitable references. 301 302 Attribute references have the following format: 303 304 object type ":" qualified name 305 306 Object type can be "<class>", "<function>" or "<var>". 307 308 ---- 309 310 A summary of attributes is formatted as follows: 311 312 location " " attribute name " " ( "constrained" | "deduced" ) " " test " " attribute references 313 314 This summary groups all attribute types (class, instance, module) into a 315 single line in order to determine the complexity of each access. 316 317 Tests can be "validate", "specific", "untested", "guarded-validate" or "guarded-specific". 318 319 ---- 320 321 For each access where a test would be asserted to guarantee the 322 nature of an attribute, the following formats are employed: 323 324 location " " attribute name " " "validate" 325 location " " attribute name " " "specific" " " attribute type " " object type 326 327 ---- 328 329 References that cannot be supported by any types are written to a 330 warnings file in the following format: 331 332 location 333 """ 334 335 f_attr_summary = open(join(self.output, "attribute_summary"), "w") 336 f_attrs = open(join(self.output, "attributes"), "w") 337 f_tests = open(join(self.output, "tests"), "w") 338 f_warnings = open(join(self.output, "attribute_warnings"), "w") 339 340 try: 341 locations = self.referenced_attrs.keys() 342 locations.sort() 343 344 for location in locations: 345 constrained = location in self.access_constrained 346 347 # Attribute information, both name-based and anonymous. 348 349 referenced_attrs = self.referenced_attrs[location] 350 351 if referenced_attrs: 352 attrname = get_attrname_from_location(location) 353 354 all_accessed_attrs = self.reference_all_attrs[location] 355 356 for attrtype, attrs in self.get_referenced_attrs(location): 357 print >>f_attrs, encode_access_location(location), encode_constrained(constrained), attrtype, sorted_output(attrs) 358 359 test_type = self.reference_test_types.get(location) 360 361 # Write the need to test at run time. 362 363 if test_type == "validate": 364 print >>f_tests, encode_access_location(location), test_type 365 366 # Write any type checks for anonymous accesses. 367 368 elif test_type and self.reference_test_accessor_type.get(location): 369 print >>f_tests, encode_access_location(location), test_type, \ 370 sorted_output(all_accessed_attrs), \ 371 self.reference_test_accessor_type[location] 372 373 print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \ 374 test_type or "untested", sorted_output(all_accessed_attrs) 375 376 else: 377 print >>f_warnings, encode_access_location(location) 378 379 finally: 380 f_attr_summary.close() 381 f_attrs.close() 382 f_tests.close() 383 f_warnings.close() 384 385 def write_access_plans(self): 386 387 "Each attribute access is written out as a plan." 388 389 f_attrs = open(join(self.output, "attribute_plans"), "w") 390 391 try: 392 locations = self.access_plans.keys() 393 locations.sort() 394 395 for location in locations: 396 name, test, test_type, base, traversed, attrnames, context, \ 397 method, attr = self.access_plans[location] 398 399 print >>f_attrs, encode_access_location(location), \ 400 name, test, test_type or "{}", \ 401 base or "{}", \ 402 ".".join(traversed) or "{}", \ 403 ".".join(attrnames) or "{}", \ 404 context, method, attr or "{}" 405 406 finally: 407 f_attrs.close() 408 409 def classify_accessors(self): 410 411 "For each program location, classify accessors." 412 413 # Where instance and module types are defined, class types are also 414 # defined. See: init_definition_details 415 416 locations = self.accessor_class_types.keys() 417 418 for location in locations: 419 constrained = location in self.accessor_constrained 420 421 # Provider information. 422 423 class_types = self.provider_class_types[location] 424 instance_types = self.provider_instance_types[location] 425 module_types = self.provider_module_types[location] 426 427 # Collect specific and general type information. 428 429 self.provider_all_types[location] = \ 430 combine_types(class_types, instance_types, module_types) 431 432 # Accessor information. 433 434 class_types = self.accessor_class_types[location] 435 self.accessor_general_class_types[location] = \ 436 general_class_types = self.get_most_general_class_types(class_types) 437 438 instance_types = self.accessor_instance_types[location] 439 self.accessor_general_instance_types[location] = \ 440 general_instance_types = self.get_most_general_class_types(instance_types) 441 442 module_types = self.accessor_module_types[location] 443 self.accessor_general_module_types[location] = \ 444 general_module_types = self.get_most_general_module_types(module_types) 445 446 # Collect specific and general type information. 447 448 self.accessor_all_types[location] = all_types = \ 449 combine_types(class_types, instance_types, module_types) 450 451 self.accessor_all_general_types[location] = all_general_types = \ 452 combine_types(general_class_types, general_instance_types, general_module_types) 453 454 # Record guard information. 455 456 if not constrained: 457 458 # Record specific type guard details. 459 460 if len(all_types) == 1: 461 self.accessor_guard_tests[location] = test_for_type("specific", first(all_types)) 462 elif is_single_class_type(all_types): 463 self.accessor_guard_tests[location] = "specific-object" 464 465 # Record common type guard details. 466 467 elif len(all_general_types) == 1: 468 self.accessor_guard_tests[location] = test_for_type("common", first(all_types)) 469 elif is_single_class_type(all_general_types): 470 self.accessor_guard_tests[location] = "common-object" 471 472 # Otherwise, no convenient guard can be defined. 473 474 def classify_accesses(self): 475 476 "For each program location, classify accesses." 477 478 # Attribute accesses use potentially different locations to those of 479 # accessors. 480 481 locations = self.referenced_attrs.keys() 482 483 for location in locations: 484 constrained = location in self.access_constrained 485 486 # Combine type information from all accessors supplying the access. 487 488 accessor_locations = self.get_accessors_for_access(location) 489 490 all_provider_types = set() 491 all_accessor_types = set() 492 all_accessor_general_types = set() 493 494 for accessor_location in accessor_locations: 495 496 # Obtain the provider types for guard-related attribute access 497 # checks. 498 499 all_provider_types.update(self.provider_all_types.get(accessor_location)) 500 501 # Obtain the accessor guard types (specific and general). 502 503 all_accessor_types.update(self.accessor_all_types.get(accessor_location)) 504 all_accessor_general_types.update(self.accessor_all_general_types.get(accessor_location)) 505 506 # Obtain basic properties of the types involved in the access. 507 508 single_accessor_type = len(all_accessor_types) == 1 509 single_accessor_class_type = is_single_class_type(all_accessor_types) 510 single_accessor_general_type = len(all_accessor_general_types) == 1 511 single_accessor_general_class_type = is_single_class_type(all_accessor_general_types) 512 513 # Determine whether the attribute access is guarded or not. 514 515 guarded = ( 516 single_accessor_type or single_accessor_class_type or 517 single_accessor_general_type or single_accessor_general_class_type 518 ) 519 520 if guarded: 521 (guard_class_types, guard_instance_types, guard_module_types, 522 _function_types, _var_types) = separate_types(all_provider_types) 523 524 self.reference_all_accessor_types[location] = all_accessor_types 525 self.reference_all_accessor_general_types[location] = all_accessor_general_types 526 527 # Attribute information, both name-based and anonymous. 528 529 referenced_attrs = self.referenced_attrs[location] 530 531 if not referenced_attrs: 532 raise DeduceError, repr(location) 533 534 # Record attribute information for each name used on the 535 # accessor. 536 537 attrname = get_attrname_from_location(location) 538 539 all_accessed_attrs = set() 540 all_providers = set() 541 542 # Obtain provider and attribute details for this kind of 543 # object. 544 545 for attrtype, object_type, attr in referenced_attrs: 546 all_accessed_attrs.add(attr) 547 all_providers.add(object_type) 548 549 all_general_providers = self.get_most_general_types(all_providers) 550 551 # Determine which attributes would be provided by the 552 # accessor types upheld by a guard. 553 554 if guarded: 555 guard_attrs = set() 556 for _attrtype, object_type, attr in \ 557 self._identify_reference_attribute(attrname, guard_class_types, guard_instance_types, guard_module_types): 558 guard_attrs.add(attr) 559 else: 560 guard_attrs = None 561 562 self.reference_all_attrs[location] = all_accessed_attrs 563 564 # Constrained accesses guarantee the nature of the accessor. 565 # However, there may still be many types involved. 566 567 if constrained: 568 if single_accessor_type: 569 self.reference_test_types[location] = test_for_type("constrained-specific", first(all_accessor_types)) 570 elif single_accessor_class_type: 571 self.reference_test_types[location] = "constrained-specific-object" 572 elif single_accessor_general_type: 573 self.reference_test_types[location] = test_for_type("constrained-common", first(all_accessor_general_types)) 574 elif single_accessor_general_class_type: 575 self.reference_test_types[location] = "constrained-common-object" 576 else: 577 self.reference_test_types[location] = "constrained-many" 578 579 # Suitably guarded accesses, where the nature of the 580 # accessor can be guaranteed, do not require the attribute 581 # involved to be validated. Otherwise, for unguarded 582 # accesses, access-level tests are required. 583 584 elif guarded and all_accessed_attrs.issubset(guard_attrs): 585 if single_accessor_type: 586 self.reference_test_types[location] = test_for_type("guarded-specific", first(all_accessor_types)) 587 elif single_accessor_class_type: 588 self.reference_test_types[location] = "guarded-specific-object" 589 elif single_accessor_general_type: 590 self.reference_test_types[location] = test_for_type("guarded-common", first(all_accessor_general_types)) 591 elif single_accessor_general_class_type: 592 self.reference_test_types[location] = "guarded-common-object" 593 594 # Record the need to test the type of anonymous and 595 # unconstrained accessors. 596 597 elif len(all_providers) == 1: 598 provider = first(all_providers) 599 if provider != '__builtins__.object': 600 all_accessor_kinds = set(get_kinds(all_accessor_types)) 601 if len(all_accessor_kinds) == 1: 602 test_type = test_for_kinds("specific", all_accessor_kinds) 603 else: 604 test_type = "specific-object" 605 self.reference_test_types[location] = test_type 606 self.reference_test_accessor_type[location] = provider 607 608 elif len(all_general_providers) == 1: 609 provider = first(all_general_providers) 610 if provider != '__builtins__.object': 611 all_accessor_kinds = set(get_kinds(all_accessor_general_types)) 612 if len(all_accessor_kinds) == 1: 613 test_type = test_for_kinds("common", all_accessor_kinds) 614 else: 615 test_type = "common-object" 616 self.reference_test_types[location] = test_type 617 self.reference_test_accessor_type[location] = provider 618 619 # Record the need to test the identity of the attribute. 620 621 else: 622 self.reference_test_types[location] = "validate" 623 624 def initialise_access_plans(self): 625 626 "Define attribute access plans." 627 628 for location in self.referenced_attrs.keys(): 629 original_location = self.const_accesses_rev.get(location) 630 self.access_plans[original_location or location] = self.get_access_plan(location) 631 632 def get_referenced_attrs(self, location): 633 634 """ 635 Return attributes referenced at the given access 'location' by the given 636 'attrname' as a list of (attribute type, attribute set) tuples. 637 """ 638 639 d = {} 640 for attrtype, objtype, attr in self.referenced_attrs[location]: 641 init_item(d, attrtype, set) 642 d[attrtype].add(attr) 643 l = d.items() 644 l.sort() # class, module, instance 645 return l 646 647 # Initialisation methods. 648 649 def init_descendants(self): 650 651 "Identify descendants of each class." 652 653 for name in self.importer.classes.keys(): 654 self.get_descendants_for_class(name) 655 656 def get_descendants_for_class(self, name): 657 658 """ 659 Use subclass information to deduce the descendants for the class of the 660 given 'name'. 661 """ 662 663 if not self.descendants.has_key(name): 664 descendants = set() 665 666 for subclass in self.importer.subclasses[name]: 667 descendants.update(self.get_descendants_for_class(subclass)) 668 descendants.add(subclass) 669 670 self.descendants[name] = descendants 671 672 return self.descendants[name] 673 674 def init_special_attributes(self): 675 676 "Add special attributes to the classes for inheritance-related tests." 677 678 all_class_attrs = self.importer.all_class_attrs 679 680 for name, descendants in self.descendants.items(): 681 for descendant in descendants: 682 all_class_attrs[descendant]["#%s" % name] = name 683 684 for name in all_class_attrs.keys(): 685 all_class_attrs[name]["#%s" % name] = name 686 687 def init_usage_index(self): 688 689 """ 690 Create indexes for module and function attribute usage and for anonymous 691 accesses. 692 """ 693 694 for module in self.importer.get_modules(): 695 for path, assignments in module.attr_usage.items(): 696 self.add_usage(assignments, path) 697 698 for location, all_attrnames in self.importer.all_attr_accesses.items(): 699 for attrnames in all_attrnames: 700 attrname = get_attrnames(attrnames)[-1] 701 access_location = (location, None, attrnames, 0) 702 self.add_usage_term(access_location, ((attrname, False),)) 703 704 def add_usage(self, assignments, path): 705 706 """ 707 Collect usage from the given 'assignments', adding 'path' details to 708 each record if specified. Add the usage to an index mapping to location 709 information, as well as to an index mapping locations to usages. 710 """ 711 712 for name, versions in assignments.items(): 713 for i, usages in enumerate(versions): 714 location = (path, name, None, i) 715 716 for usage in usages: 717 self.add_usage_term(location, usage) 718 719 def add_usage_term(self, location, usage): 720 721 """ 722 For 'location' and using 'usage' as a description of usage, record 723 in the usage index a mapping from the usage to 'location', and record in 724 the location index a mapping from 'location' to the usage. 725 """ 726 727 init_item(self.location_index, location, set) 728 self.location_index[location].add(usage) 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 if assignment: 805 init_item(self.assigned_attrs, usage, set) 806 self.assigned_attrs[usage].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 # More efficient usage-to-type indexing and retrieval. 973 974 def init_attr_type_indexes(self): 975 976 "Identify the types that can support each attribute name." 977 978 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 979 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs) 980 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 981 982 def _init_attr_type_index(self, attr_types, attrs): 983 984 """ 985 Initialise the 'attr_types' attribute-to-types mapping using the given 986 'attrs' type-to-attributes mapping. 987 """ 988 989 for name, attrnames in attrs.items(): 990 for attrname in attrnames: 991 init_item(attr_types, attrname, set) 992 attr_types[attrname].add(name) 993 994 def get_class_types_for_usage(self, usage): 995 996 "Return names of classes supporting the given 'usage'." 997 998 return self._get_types_for_usage(usage, self.attr_class_types, self.importer.all_class_attrs) 999 1000 def get_instance_types_for_usage(self, usage): 1001 1002 """ 1003 Return names of classes whose instances support the given 'usage' 1004 (as either class or instance attributes). 1005 """ 1006 1007 return self._get_types_for_usage(usage, self.attr_instance_types, self.importer.all_combined_attrs) 1008 1009 def get_module_types_for_usage(self, usage): 1010 1011 "Return names of modules supporting the given 'usage'." 1012 1013 return self._get_types_for_usage(usage, self.attr_module_types, self.importer.all_module_attrs) 1014 1015 def _get_types_for_usage(self, usage, attr_types, attrs): 1016 1017 """ 1018 For the given 'usage' representing attribute usage, return types 1019 recorded in the 'attr_types' attribute-to-types mapping that support 1020 such usage, with the given 'attrs' type-to-attributes mapping used to 1021 quickly assess whether a type supports all of the stated attributes. 1022 """ 1023 1024 # Where no attributes are used, any type would be acceptable. 1025 1026 if not usage: 1027 return attrs.keys() 1028 1029 attrnames = [] 1030 for attrname, invocation in usage: 1031 attrnames.append(attrname) 1032 1033 types = [] 1034 1035 # Obtain types supporting the first attribute name... 1036 1037 for name in attr_types.get(attrnames[0]) or []: 1038 1039 # Record types that support all of the other attributes as well. 1040 1041 _attrnames = attrs[name] 1042 if set(attrnames).issubset(_attrnames): 1043 types.append(name) 1044 1045 return types 1046 1047 # Reference identification. 1048 1049 def identify_references(self): 1050 1051 "Identify references using usage and name reference information." 1052 1053 # Names with associated attribute usage. 1054 1055 for location, usages in self.location_index.items(): 1056 1057 # Obtain attribute usage associated with a name, deducing the nature 1058 # of the name. Obtain types only for branches involving attribute 1059 # usage. (In the absence of usage, any type could be involved, but 1060 # then no accesses exist to require knowledge of the type.) 1061 1062 have_usage = False 1063 have_no_usage_branch = False 1064 1065 for usage in usages: 1066 if not usage: 1067 have_no_usage_branch = True 1068 continue 1069 elif not have_usage: 1070 self.init_definition_details(location) 1071 have_usage = True 1072 self.record_types_for_usage(location, usage) 1073 1074 # Where some usage occurs, but where branches without usage also 1075 # occur, record the types for those branches anyway. 1076 1077 if have_usage and have_no_usage_branch: 1078 self.init_definition_details(location) 1079 self.record_types_for_usage(location, None) 1080 1081 # Specific name-based attribute accesses. 1082 1083 alias_accesses = set() 1084 1085 for access_location, accessor_locations in self.access_index.items(): 1086 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1087 1088 # Anonymous references with attribute chains. 1089 1090 for location, accesses in self.importer.all_attr_accesses.items(): 1091 1092 # Get distinct attribute names. 1093 1094 all_attrnames = set() 1095 1096 for attrnames in accesses: 1097 all_attrnames.update(get_attrnames(attrnames)) 1098 1099 # Get attribute and accessor details for each attribute name. 1100 1101 for attrname in all_attrnames: 1102 access_location = (location, None, attrname, 0) 1103 self.record_types_for_attribute(access_location, attrname) 1104 1105 # References via constant/identified objects. 1106 1107 for location, name_accesses in self.importer.all_const_accesses.items(): 1108 1109 # A mapping from the original name and attributes to resolved access 1110 # details. 1111 1112 for original_access, access in name_accesses.items(): 1113 original_name, original_attrnames = original_access 1114 objpath, ref, attrnames = access 1115 1116 # Build an accessor combining the name and attribute names used. 1117 1118 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1119 1120 # Direct accesses to attributes. 1121 1122 if not attrnames: 1123 1124 # Build a descriptive location based on the original 1125 # details, exposing the final attribute name. 1126 1127 oa, attrname = original_accessor[:-1], original_accessor[-1] 1128 oa = ".".join(oa) 1129 1130 access_location = (location, oa, attrname, 0) 1131 accessor_location = (location, oa, None, 0) 1132 self.access_index[access_location] = [accessor_location] 1133 1134 self.init_access_details(access_location) 1135 self.init_definition_details(accessor_location) 1136 1137 # Obtain a reference for the accessor in order to properly 1138 # determine its type. 1139 1140 if ref.get_kind() != "<instance>": 1141 objpath = ref.get_origin() 1142 1143 objpath = objpath.rsplit(".", 1)[0] 1144 1145 # Where the object name conflicts with the module 1146 # providing it, obtain the module details. 1147 1148 if objpath in self.importer.modules: 1149 accessor = Reference("<module>", objpath) 1150 else: 1151 accessor = self.importer.get_object(objpath) 1152 1153 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1154 self.access_constrained.add(access_location) 1155 1156 class_types, instance_types, module_types = accessor.get_types() 1157 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1158 1159 else: 1160 1161 # Build a descriptive location based on the original 1162 # details, employing the first remaining attribute name. 1163 1164 l = get_attrnames(attrnames) 1165 attrname = l[0] 1166 1167 oa = original_accessor[:-len(l)] 1168 oa = ".".join(oa) 1169 1170 access_location = (location, oa, attrnames, 0) 1171 accessor_location = (location, oa, None, 0) 1172 self.access_index[access_location] = [accessor_location] 1173 1174 self.init_access_details(access_location) 1175 self.init_definition_details(accessor_location) 1176 1177 class_types, instance_types, module_types = ref.get_types() 1178 1179 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1180 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1181 1182 original_location = (location, original_name, original_attrnames, 0) 1183 1184 if original_location != access_location: 1185 self.const_accesses[original_location] = access_location 1186 self.const_accesses_rev[access_location] = original_location 1187 1188 # Aliased name definitions. All aliases with usage will have been 1189 # defined, but they may be refined according to referenced accesses. 1190 1191 for accessor_location in self.alias_index.keys(): 1192 self.record_types_for_alias(accessor_location) 1193 1194 # Update accesses employing aliases. 1195 1196 for access_location in alias_accesses: 1197 self.record_types_for_access(access_location, self.access_index[access_location]) 1198 1199 def constrain_types(self, path, class_types, instance_types, module_types): 1200 1201 """ 1202 Using the given 'path' to an object, constrain the given 'class_types', 1203 'instance_types' and 'module_types'. 1204 1205 Return the class, instance, module types plus whether the types are 1206 constrained to a specific kind of type. 1207 """ 1208 1209 ref = self.importer.identify(path) 1210 if ref: 1211 1212 # Constrain usage suggestions using the identified object. 1213 1214 if ref.has_kind("<class>"): 1215 return ( 1216 set(class_types).intersection([ref.get_origin()]), [], [], True 1217 ) 1218 elif ref.has_kind("<module>"): 1219 return ( 1220 [], [], set(module_types).intersection([ref.get_origin()]), True 1221 ) 1222 1223 return class_types, instance_types, module_types, False 1224 1225 def get_target_types(self, location, usage): 1226 1227 """ 1228 Return the class, instance and module types constrained for the name at 1229 the given 'location' exhibiting the given 'usage'. Whether the types 1230 have been constrained using contextual information is also indicated, 1231 plus whether the types have been constrained to a specific kind of type. 1232 """ 1233 1234 unit_path, name, attrnames, version = location 1235 1236 # Detect any initialised name for the location. 1237 1238 if name: 1239 ref = self.get_initialised_name(location) 1240 if ref: 1241 (class_types, only_instance_types, module_types, 1242 _function_types, _var_types) = separate_types([ref]) 1243 return class_types, only_instance_types, module_types, True, False 1244 1245 # Retrieve the recorded types for the usage. 1246 1247 class_types = self.get_class_types_for_usage(usage) 1248 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1249 module_types = self.get_module_types_for_usage(usage) 1250 1251 # Merge usage deductions with observations to obtain reference types 1252 # for names involved with attribute accesses. 1253 1254 if not name: 1255 return class_types, only_instance_types, module_types, False, False 1256 1257 # Obtain references to known objects. 1258 1259 path = get_name_path(unit_path, name) 1260 1261 class_types, only_instance_types, module_types, constrained_specific = \ 1262 self.constrain_types(path, class_types, only_instance_types, module_types) 1263 1264 if constrained_specific: 1265 return class_types, only_instance_types, module_types, constrained_specific, constrained_specific 1266 1267 # Constrain "self" references. 1268 1269 if name == "self": 1270 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1271 if t: 1272 class_types, only_instance_types, module_types, constrained = t 1273 return class_types, only_instance_types, module_types, constrained, False 1274 1275 return class_types, only_instance_types, module_types, False, False 1276 1277 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1278 1279 """ 1280 Where the name "self" appears in a method, attempt to constrain the 1281 classes involved. 1282 1283 Return the class, instance, module types plus whether the types are 1284 constrained. 1285 """ 1286 1287 class_name = self.in_method(unit_path) 1288 1289 if not class_name: 1290 return None 1291 1292 classes = set([class_name]) 1293 classes.update(self.get_descendants_for_class(class_name)) 1294 1295 # Note that only instances will be expected for these references but 1296 # either classes or instances may provide the attributes. 1297 1298 return ( 1299 set(class_types).intersection(classes), 1300 set(only_instance_types).intersection(classes), 1301 [], True 1302 ) 1303 1304 def in_method(self, path): 1305 1306 "Return whether 'path' refers to a method." 1307 1308 class_name, method_name = path.rsplit(".", 1) 1309 return self.importer.classes.has_key(class_name) and class_name 1310 1311 def init_reference_details(self, location): 1312 1313 "Initialise reference-related details for 'location'." 1314 1315 self.init_definition_details(location) 1316 self.init_access_details(location) 1317 1318 def init_definition_details(self, location): 1319 1320 "Initialise name definition details for 'location'." 1321 1322 self.accessor_class_types[location] = set() 1323 self.accessor_instance_types[location] = set() 1324 self.accessor_module_types[location] = set() 1325 self.provider_class_types[location] = set() 1326 self.provider_instance_types[location] = set() 1327 self.provider_module_types[location] = set() 1328 1329 def init_access_details(self, location): 1330 1331 "Initialise access details at 'location'." 1332 1333 self.referenced_attrs[location] = {} 1334 1335 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1336 1337 """ 1338 Define types for the 'access_location' associated with the given 1339 'accessor_locations'. 1340 """ 1341 1342 path, name, attrnames, version = access_location 1343 if not attrnames: 1344 return 1345 1346 attrname = get_attrnames(attrnames)[0] 1347 1348 # Collect all suggested types for the accessors. Accesses may 1349 # require accessors from of a subset of the complete set of types. 1350 1351 class_types = set() 1352 module_types = set() 1353 instance_types = set() 1354 1355 constrained = True 1356 1357 for location in accessor_locations: 1358 1359 # Remember accesses employing aliases. 1360 1361 if alias_accesses is not None and self.alias_index.has_key(location): 1362 alias_accesses.add(access_location) 1363 1364 # Use the type information deduced for names from above. 1365 1366 if self.accessor_class_types.has_key(location): 1367 class_types.update(self.accessor_class_types[location]) 1368 module_types.update(self.accessor_module_types[location]) 1369 instance_types.update(self.accessor_instance_types[location]) 1370 1371 # Where accesses are associated with assignments but where no 1372 # attribute usage observations have caused such an association, 1373 # the attribute name is considered by itself. 1374 1375 else: 1376 self.init_definition_details(location) 1377 self.record_types_for_usage(location, [(attrname, False)]) 1378 1379 constrained = location in self.accessor_constrained and constrained 1380 1381 self.init_access_details(access_location) 1382 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1383 1384 def record_types_for_usage(self, accessor_location, usage): 1385 1386 """ 1387 Record types for the given 'accessor_location' according to the given 1388 'usage' observations which may be None to indicate an absence of usage. 1389 """ 1390 1391 (class_types, 1392 instance_types, 1393 module_types, 1394 constrained, 1395 constrained_specific) = self.get_target_types(accessor_location, usage) 1396 1397 invocations = get_invoked_attributes(usage) 1398 1399 self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific, invocations) 1400 1401 def record_types_for_attribute(self, access_location, attrname): 1402 1403 """ 1404 Record types for the 'access_location' employing only the given 1405 'attrname' for type deduction. 1406 """ 1407 1408 usage = ((attrname, False),) 1409 1410 class_types = self.get_class_types_for_usage(usage) 1411 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1412 module_types = self.get_module_types_for_usage(usage) 1413 1414 self.init_reference_details(access_location) 1415 1416 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1417 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1418 1419 def record_types_for_alias(self, accessor_location): 1420 1421 """ 1422 Define types for the 'accessor_location' not having associated usage. 1423 """ 1424 1425 have_access = self.provider_class_types.has_key(accessor_location) 1426 1427 # With an access, attempt to narrow the existing selection of provider 1428 # types. 1429 1430 if have_access: 1431 provider_class_types = self.provider_class_types[accessor_location] 1432 provider_instance_types = self.provider_instance_types[accessor_location] 1433 provider_module_types = self.provider_module_types[accessor_location] 1434 1435 # Find details for any corresponding access. 1436 1437 all_class_types = set() 1438 all_instance_types = set() 1439 all_module_types = set() 1440 1441 for access_location in self.alias_index[accessor_location]: 1442 location, name, attrnames, access_number = access_location 1443 1444 # Alias references an attribute access. 1445 1446 if attrnames: 1447 1448 # Obtain attribute references for the access. 1449 1450 attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]] 1451 1452 # Separate the different attribute types. 1453 1454 (class_types, instance_types, module_types, 1455 function_types, var_types) = separate_types(attrs) 1456 1457 # Where non-accessor types are found, do not attempt to refine 1458 # the defined accessor types. 1459 1460 if function_types or var_types: 1461 return 1462 1463 class_types = set(provider_class_types).intersection(class_types) 1464 instance_types = set(provider_instance_types).intersection(instance_types) 1465 module_types = set(provider_module_types).intersection(module_types) 1466 1467 # Alias references a name, not an access. 1468 1469 else: 1470 # Attempt to refine the types using initialised names. 1471 1472 attr = self.get_initialised_name(access_location) 1473 if attr: 1474 (class_types, instance_types, module_types, 1475 _function_types, _var_types) = separate_types([attr]) 1476 1477 # Where no further information is found, do not attempt to 1478 # refine the defined accessor types. 1479 1480 else: 1481 return 1482 1483 all_class_types.update(class_types) 1484 all_instance_types.update(instance_types) 1485 all_module_types.update(module_types) 1486 1487 # Record refined type details for the alias as an accessor. 1488 1489 self.init_definition_details(accessor_location) 1490 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1491 1492 # Without an access, attempt to identify references for the alias. 1493 1494 else: 1495 refs = set() 1496 1497 for access_location in self.alias_index[accessor_location]: 1498 1499 # Obtain any redefined constant access location. 1500 1501 if self.const_accesses.has_key(access_location): 1502 access_location = self.const_accesses[access_location] 1503 1504 location, name, attrnames, access_number = access_location 1505 1506 # Alias references an attribute access. 1507 1508 if attrnames: 1509 attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]] 1510 refs.update(attrs) 1511 1512 # Alias references a name, not an access. 1513 1514 else: 1515 attr = self.get_initialised_name(access_location) 1516 attrs = attr and [attr] or [] 1517 if not attrs and self.provider_class_types.has_key(access_location): 1518 class_types = self.provider_class_types[access_location] 1519 instance_types = self.provider_instance_types[access_location] 1520 module_types = self.provider_module_types[access_location] 1521 attrs = combine_types(class_types, instance_types, module_types) 1522 if attrs: 1523 refs.update(attrs) 1524 1525 # Record reference details for the alias separately from accessors. 1526 1527 self.referenced_objects[accessor_location] = refs 1528 1529 def get_initialised_name(self, access_location): 1530 1531 """ 1532 Return references for any initialised names at 'access_location', or 1533 None if no such references exist. 1534 """ 1535 1536 location, name, attrnames, version = access_location 1537 path = get_name_path(location, name) 1538 1539 # Use initialiser information, if available. 1540 1541 refs = self.importer.all_initialised_names.get(path) 1542 if refs and refs.has_key(version): 1543 return refs[version] 1544 else: 1545 return None 1546 1547 def record_reference_types(self, location, class_types, instance_types, 1548 module_types, constrained, constrained_specific=False, invocations=None): 1549 1550 """ 1551 Associate attribute provider types with the given 'location', consisting 1552 of the given 'class_types', 'instance_types' and 'module_types'. 1553 1554 If 'constrained' is indicated, the constrained nature of the accessor is 1555 recorded for the location. 1556 1557 If 'constrained_specific' is indicated using a true value, instance types 1558 will not be added to class types to permit access via instances at the 1559 given location. This is only useful where a specific accessor is known 1560 to be a class. 1561 1562 Note that the specified types only indicate the provider types for 1563 attributes, whereas the recorded accessor types indicate the possible 1564 types of the actual objects used to access attributes. 1565 """ 1566 1567 # Update the type details for the location. 1568 1569 self.provider_class_types[location].update(class_types) 1570 self.provider_instance_types[location].update(instance_types) 1571 self.provider_module_types[location].update(module_types) 1572 1573 # Class types support classes and instances as accessors. 1574 # Instance-only and module types support only their own kinds as 1575 # accessors. 1576 1577 path, name, version, attrnames = location 1578 1579 if invocations: 1580 class_only_types = self.filter_for_invocations(class_types, invocations) 1581 else: 1582 class_only_types = class_types 1583 1584 # However, the nature of accessors can be further determined. 1585 # Any self variable may only refer to an instance. 1586 1587 if name != "self" or not self.in_method(path): 1588 self.accessor_class_types[location].update(class_only_types) 1589 1590 if not constrained_specific: 1591 self.accessor_instance_types[location].update(class_types) 1592 1593 self.accessor_instance_types[location].update(instance_types) 1594 1595 if name != "self" or not self.in_method(path): 1596 self.accessor_module_types[location].update(module_types) 1597 1598 if constrained: 1599 self.accessor_constrained.add(location) 1600 1601 def filter_for_invocations(self, class_types, attrnames): 1602 1603 """ 1604 From the given 'class_types', identify methods for the given 1605 'attrnames' that are being invoked, returning a filtered collection of 1606 class types. 1607 """ 1608 1609 to_filter = set() 1610 1611 for class_type in class_types: 1612 for attrname in attrnames: 1613 ref = self.importer.get_class_attribute(class_type, attrname) 1614 parent_class = ref and ref.parent() 1615 1616 if ref and ref.has_kind("<function>") and ( 1617 parent_class == class_type or 1618 class_type in self.descendants[parent_class]): 1619 1620 to_filter.add(class_type) 1621 break 1622 1623 return set(class_types).difference(to_filter) 1624 1625 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1626 1627 """ 1628 Identify reference attributes, associating them with the given 1629 'location', identifying the given 'attrname', employing the given 1630 'class_types', 'instance_types' and 'module_types'. 1631 1632 If 'constrained' is indicated, the constrained nature of the access is 1633 recorded for the location. 1634 """ 1635 1636 # Record the referenced objects. 1637 1638 self.referenced_attrs[location] = \ 1639 self._identify_reference_attribute(attrname, class_types, instance_types, module_types) 1640 1641 if constrained: 1642 self.access_constrained.add(location) 1643 1644 def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types): 1645 1646 """ 1647 Identify the reference attribute with the given 'attrname', employing 1648 the given 'class_types', 'instance_types' and 'module_types'. 1649 """ 1650 1651 attrs = set() 1652 1653 # The class types expose class attributes either directly or via 1654 # instances. 1655 1656 for object_type in class_types: 1657 ref = self.importer.get_class_attribute(object_type, attrname) 1658 if ref: 1659 attrs.add(("<class>", object_type, ref)) 1660 1661 # Add any distinct instance attributes that would be provided 1662 # by instances also providing indirect class attribute access. 1663 1664 for ref in self.importer.get_instance_attributes(object_type, attrname): 1665 attrs.add(("<instance>", object_type, ref)) 1666 1667 # The instance-only types expose instance attributes, but although 1668 # classes are excluded as potential accessors (since they do not provide 1669 # the instance attributes), the class types may still provide some 1670 # attributes. 1671 1672 for object_type in instance_types: 1673 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1674 1675 if instance_attrs: 1676 for ref in instance_attrs: 1677 attrs.add(("<instance>", object_type, ref)) 1678 else: 1679 ref = self.importer.get_class_attribute(object_type, attrname) 1680 if ref: 1681 attrs.add(("<class>", object_type, ref)) 1682 1683 # Module types expose module attributes for module accessors. 1684 1685 for object_type in module_types: 1686 ref = self.importer.get_module_attribute(object_type, attrname) 1687 if ref: 1688 attrs.add(("<module>", object_type, ref)) 1689 1690 return attrs 1691 1692 constrained_specific_tests = ( 1693 "constrained-specific-instance", 1694 "constrained-specific-type", 1695 "constrained-specific-object", 1696 ) 1697 1698 constrained_common_tests = ( 1699 "constrained-common-instance", 1700 "constrained-common-type", 1701 "constrained-common-object", 1702 ) 1703 1704 guarded_specific_tests = ( 1705 "guarded-specific-instance", 1706 "guarded-specific-type", 1707 "guarded-specific-object", 1708 ) 1709 1710 guarded_common_tests = ( 1711 "guarded-common-instance", 1712 "guarded-common-type", 1713 "guarded-common-object", 1714 ) 1715 1716 specific_tests = ( 1717 "specific-instance", 1718 "specific-type", 1719 "specific-object", 1720 ) 1721 1722 common_tests = ( 1723 "common-instance", 1724 "common-type", 1725 "common-object", 1726 ) 1727 1728 class_tests = ( 1729 "guarded-specific-type", 1730 "guarded-common-type", 1731 "specific-type", 1732 "common-type", 1733 ) 1734 1735 class_or_instance_tests = ( 1736 "guarded-specific-object", 1737 "guarded-common-object", 1738 "specific-object", 1739 "common-object", 1740 ) 1741 1742 def get_access_plan(self, location): 1743 1744 """ 1745 Return details of the access at the given 'location'. The details are as 1746 follows: 1747 1748 * the initial accessor (from which accesses will be performed if no 1749 computed static accessor is found) 1750 * details of any test required on the initial accessor 1751 * details of any type employed by the test 1752 * any static accessor (from which accesses will be performed in 1753 preference to the initial accessor) 1754 * attributes needing to be traversed from the base that yield 1755 unambiguous objects 1756 * remaining attributes needing to be tested and traversed 1757 * details of the context 1758 * the method of obtaining the final attribute 1759 * any static final attribute 1760 """ 1761 1762 const_access = self.const_accesses_rev.has_key(location) 1763 1764 path, name, attrnames, version = location 1765 remaining = attrnames.split(".") 1766 attrname = remaining[0] 1767 1768 # Obtain reference and accessor information, retaining also distinct 1769 # provider kind details. 1770 1771 attrs = [] 1772 objtypes = [] 1773 provider_kinds = set() 1774 1775 for attrtype, objtype, attr in self.referenced_attrs[location]: 1776 attrs.append(attr) 1777 objtypes.append(objtype) 1778 provider_kinds.add(attrtype) 1779 1780 # Obtain accessor type and kind information. 1781 1782 accessor_types = self.reference_all_accessor_types[location] 1783 accessor_general_types = self.reference_all_accessor_general_types[location] 1784 accessor_kinds = get_kinds(accessor_general_types) 1785 1786 # Determine any guard or test requirements. 1787 1788 constrained = location in self.access_constrained 1789 test = self.reference_test_types[location] 1790 test_type = self.reference_test_accessor_type.get(location) 1791 1792 # Determine the accessor and provider properties. 1793 1794 class_accessor = "<class>" in accessor_kinds 1795 module_accessor = "<module>" in accessor_kinds 1796 instance_accessor = "<instance>" in accessor_kinds 1797 provided_by_class = "<class>" in provider_kinds 1798 provided_by_instance = "<instance>" in provider_kinds 1799 1800 # Determine how attributes may be accessed relative to the accessor. 1801 1802 object_relative = class_accessor or module_accessor or provided_by_instance 1803 class_relative = instance_accessor and provided_by_class 1804 1805 # Identify the last static attribute for context acquisition. 1806 1807 base = None 1808 dynamic_base = None 1809 1810 # Constant accesses have static accessors. 1811 1812 if const_access: 1813 base = len(objtypes) == 1 and first(objtypes) 1814 1815 # Constant accessors are static. 1816 1817 else: 1818 ref = self.importer.identify("%s.%s" % (path, name)) 1819 if ref: 1820 base = ref.get_origin() 1821 1822 # Usage of previously-generated guard and test details. 1823 1824 elif test in self.constrained_specific_tests: 1825 ref = first(accessor_types) 1826 1827 elif test in self.constrained_common_tests: 1828 ref = first(accessor_general_types) 1829 1830 elif test in self.guarded_specific_tests: 1831 ref = first(accessor_types) 1832 1833 elif test in self.guarded_common_tests: 1834 ref = first(accessor_general_types) 1835 1836 # For attribute-based tests, tentatively identify a dynamic base. 1837 # Such tests allow single or multiple kinds of a type. 1838 1839 elif test in self.common_tests or test in self.specific_tests: 1840 dynamic_base = test_type 1841 1842 # Static accessors. 1843 1844 if not base and test in self.class_tests: 1845 base = ref and ref.get_origin() or dynamic_base 1846 1847 # Accessors that are not static but whose nature is determined. 1848 1849 elif not base and ref: 1850 dynamic_base = ref.get_origin() 1851 1852 traversed = [] 1853 1854 # Traverse remaining attributes. 1855 1856 while len(attrs) == 1: 1857 attr = first(attrs) 1858 1859 traversed.append(attrname) 1860 del remaining[0] 1861 1862 if not remaining: 1863 break 1864 1865 # Update the last static attribute. 1866 1867 if attr.static(): 1868 base = attr.get_origin() 1869 traversed = [] 1870 1871 # Get the next attribute. 1872 1873 attrname = remaining[0] 1874 attrs = self.importer.get_attributes(attr, attrname) 1875 1876 # Where many attributes are suggested, no single attribute identity can 1877 # be loaded. 1878 1879 else: 1880 attr = None 1881 1882 # Determine the method of access. 1883 1884 # Identified attribute that must be accessed via its parent. 1885 1886 if attr and attr.get_name() and location in self.reference_assignments: 1887 method = "direct"; origin = attr.get_name() 1888 1889 # Static, identified attribute. 1890 1891 elif attr and attr.static(): 1892 method = "static"; origin = attr.final() 1893 1894 # Attribute accessed at a known position via its parent. 1895 1896 elif base or dynamic_base: 1897 method = "relative" + (object_relative and "-object" or "") + \ 1898 (class_relative and "-class" or "") 1899 origin = None 1900 1901 # The fallback case is always run-time testing and access. 1902 1903 else: 1904 method = "check" + (object_relative and "-object" or "") + \ 1905 (class_relative and "-class" or "") 1906 origin = None 1907 1908 # Determine the nature of the context. 1909 1910 context = base and "base" or len(traversed or remaining) > 1 and "traversal" or "accessor" 1911 1912 return name, test, test_type, base, traversed, remaining, context, method, origin 1913 1914 # vim: tabstop=4 expandtab shiftwidth=4