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