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 providers = self.get_unbound_method_providers(self.get_class_types_for_usage(usage), attrname) 963 964 # Record all class types providing the unbound method. 965 966 init_item(self.invoked_attr_types, location, set) 967 self.invoked_attr_types[location].update(providers) 968 969 def get_unbound_method_providers(self, class_types, attrname): 970 971 providers = set() 972 973 for class_type in class_types: 974 ref = self.importer.get_class_attribute(class_type, attrname) 975 976 # Test the attribute for being an unbound method provided by 977 # the class or an ancestor. 978 979 parent_class = ref and ref.parent() 980 981 if ref and ref.has_kind("<function>") and ( 982 class_type == parent_class or 983 class_type in self.descendants[parent_class]): 984 985 providers.add(class_type) 986 987 return providers 988 989 # Simplification of types. 990 991 def get_most_general_types(self, types): 992 993 "Return the most general types for the given 'types'." 994 995 module_types = set() 996 class_types = set() 997 998 for type in types: 999 ref = self.importer.identify(type) 1000 if ref.has_kind("<module>"): 1001 module_types.add(type) 1002 else: 1003 class_types.add(type) 1004 1005 types = set(self.get_most_general_module_types(module_types)) 1006 types.update(self.get_most_general_class_types(class_types)) 1007 return types 1008 1009 def get_most_general_class_types(self, class_types): 1010 1011 "Return the most general types for the given 'class_types'." 1012 1013 class_types = set(class_types) 1014 to_remove = set() 1015 1016 for class_type in class_types: 1017 for base in self.importer.classes[class_type]: 1018 base = base.get_origin() 1019 descendants = self.descendants[base] 1020 if base in class_types and descendants.issubset(class_types): 1021 to_remove.update(descendants) 1022 1023 class_types.difference_update(to_remove) 1024 return class_types 1025 1026 def get_most_general_module_types(self, module_types): 1027 1028 "Return the most general type for the given 'module_types'." 1029 1030 # Where all modules are provided, an object would provide the same 1031 # attributes. 1032 1033 if len(module_types) == len(self.importer.modules): 1034 return ["__builtins__.object"] 1035 else: 1036 return module_types 1037 1038 # Type deduction for usage. 1039 1040 def get_types_for_usage(self, attrnames, objects): 1041 1042 """ 1043 Identify the types that can support the given 'attrnames', using the 1044 given 'objects' as the catalogue of type details. 1045 """ 1046 1047 types = [] 1048 for name, _attrnames in objects.items(): 1049 if set(attrnames).issubset(_attrnames): 1050 types.append(name) 1051 return types 1052 1053 # More efficient usage-to-type indexing and retrieval. 1054 1055 def init_attr_type_indexes(self): 1056 1057 "Identify the types that can support each attribute name." 1058 1059 self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs) 1060 self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs) 1061 self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs) 1062 1063 def _init_attr_type_index(self, attr_types, attrs): 1064 1065 """ 1066 Initialise the 'attr_types' attribute-to-types mapping using the given 1067 'attrs' type-to-attributes mapping. 1068 """ 1069 1070 for name, attrnames in attrs.items(): 1071 for attrname in attrnames: 1072 init_item(attr_types, attrname, set) 1073 attr_types[attrname].add(name) 1074 1075 def get_class_types_for_usage(self, attrnames): 1076 1077 "Return names of classes supporting the given 'attrnames'." 1078 1079 return self._get_types_for_usage(attrnames, self.attr_class_types, self.importer.all_class_attrs) 1080 1081 def get_instance_types_for_usage(self, attrnames): 1082 1083 """ 1084 Return names of classes whose instances support the given 'attrnames' 1085 (as either class or instance attributes). 1086 """ 1087 1088 return self._get_types_for_usage(attrnames, self.attr_instance_types, self.importer.all_combined_attrs) 1089 1090 def get_module_types_for_usage(self, attrnames): 1091 1092 "Return names of modules supporting the given 'attrnames'." 1093 1094 return self._get_types_for_usage(attrnames, self.attr_module_types, self.importer.all_module_attrs) 1095 1096 def _get_types_for_usage(self, attrnames, attr_types, attrs): 1097 1098 """ 1099 For the given 'attrnames' representing attribute usage, return types 1100 recorded in the 'attr_types' attribute-to-types mapping that support 1101 such usage, with the given 'attrs' type-to-attributes mapping used to 1102 quickly assess whether a type supports all of the stated attributes. 1103 """ 1104 1105 # Where no attributes are used, any type would be acceptable. 1106 1107 if not attrnames: 1108 return attrs.keys() 1109 1110 types = [] 1111 1112 # Obtain types supporting the first attribute name... 1113 1114 for name in attr_types.get(attrnames[0]) or []: 1115 1116 # Record types that support all of the other attributes as well. 1117 1118 _attrnames = attrs[name] 1119 if set(attrnames).issubset(_attrnames): 1120 types.append(name) 1121 1122 return types 1123 1124 # Reference identification. 1125 1126 def identify_references(self): 1127 1128 "Identify references using usage and name reference information." 1129 1130 # Names with associated attribute usage. 1131 1132 for location, usages in self.location_index.items(): 1133 1134 # Obtain attribute usage associated with a name, deducing the nature 1135 # of the name. Obtain types only for branches involving attribute 1136 # usage. (In the absence of usage, any type could be involved, but 1137 # then no accesses exist to require knowledge of the type.) 1138 1139 have_usage = False 1140 have_no_usage_branch = False 1141 1142 for usage in usages: 1143 if not usage: 1144 have_no_usage_branch = True 1145 continue 1146 elif not have_usage: 1147 self.init_definition_details(location) 1148 have_usage = True 1149 self.record_types_for_usage(location, usage) 1150 1151 # Where some usage occurs, but where branches without usage also 1152 # occur, record the types for those branches anyway. 1153 1154 if have_usage and have_no_usage_branch: 1155 self.init_definition_details(location) 1156 self.record_types_for_usage(location, None) 1157 1158 # Specific name-based attribute accesses. 1159 1160 alias_accesses = set() 1161 1162 for access_location, accessor_locations in self.access_index.items(): 1163 self.record_types_for_access(access_location, accessor_locations, alias_accesses) 1164 1165 # Anonymous references with attribute chains. 1166 1167 for location, accesses in self.importer.all_attr_accesses.items(): 1168 1169 # Get distinct attribute names. 1170 1171 all_attrnames = set() 1172 1173 for attrnames in accesses: 1174 all_attrnames.update(get_attrnames(attrnames)) 1175 1176 # Get attribute and accessor details for each attribute name. 1177 1178 for attrname in all_attrnames: 1179 access_location = (location, None, attrname, 0) 1180 self.record_types_for_attribute(access_location, attrname) 1181 1182 # References via constant/identified objects. 1183 1184 for location, name_accesses in self.importer.all_const_accesses.items(): 1185 1186 # A mapping from the original name and attributes to resolved access 1187 # details. 1188 1189 for original_access, access in name_accesses.items(): 1190 original_name, original_attrnames = original_access 1191 objpath, ref, attrnames = access 1192 1193 # Build an accessor combining the name and attribute names used. 1194 1195 original_accessor = tuple([original_name] + original_attrnames.split(".")) 1196 1197 # Direct accesses to attributes. 1198 1199 if not attrnames: 1200 1201 # Build a descriptive location based on the original 1202 # details, exposing the final attribute name. 1203 1204 oa, attrname = original_accessor[:-1], original_accessor[-1] 1205 oa = ".".join(oa) 1206 1207 access_location = (location, oa, attrname, 0) 1208 accessor_location = (location, oa, None, 0) 1209 self.access_index[access_location] = [accessor_location] 1210 1211 self.init_access_details(access_location) 1212 self.init_definition_details(accessor_location) 1213 1214 # Obtain a reference for the accessor in order to properly 1215 # determine its type. 1216 1217 if ref.get_kind() != "<instance>": 1218 objpath = ref.get_origin() 1219 1220 objpath = objpath.rsplit(".", 1)[0] 1221 1222 # Where the object name conflicts with the module 1223 # providing it, obtain the module details. 1224 1225 if objpath in self.importer.modules: 1226 accessor = Reference("<module>", objpath) 1227 else: 1228 accessor = self.importer.get_object(objpath) 1229 1230 self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)] 1231 self.access_constrained.add(access_location) 1232 1233 class_types, instance_types, module_types = accessor.get_types() 1234 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1235 1236 else: 1237 1238 # Build a descriptive location based on the original 1239 # details, employing the first remaining attribute name. 1240 1241 l = get_attrnames(attrnames) 1242 attrname = l[0] 1243 1244 oa = original_accessor[:-len(l)] 1245 oa = ".".join(oa) 1246 1247 access_location = (location, oa, attrnames, 0) 1248 accessor_location = (location, oa, None, 0) 1249 self.access_index[access_location] = [accessor_location] 1250 1251 self.init_access_details(access_location) 1252 self.init_definition_details(accessor_location) 1253 1254 class_types, instance_types, module_types = ref.get_types() 1255 1256 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True) 1257 self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True) 1258 1259 original_location = (location, original_name, original_attrnames, 0) 1260 1261 if original_location != access_location: 1262 self.const_accesses[original_location] = access_location 1263 self.const_accesses_rev[access_location] = original_location 1264 1265 # Aliased name definitions. All aliases with usage will have been 1266 # defined, but they may be refined according to referenced accesses. 1267 1268 for accessor_location in self.alias_index.keys(): 1269 self.record_types_for_alias(accessor_location) 1270 1271 # Update accesses employing aliases. 1272 1273 for access_location in alias_accesses: 1274 self.record_types_for_access(access_location, self.access_index[access_location]) 1275 1276 def constrain_types(self, path, class_types, instance_types, module_types): 1277 1278 """ 1279 Using the given 'path' to an object, constrain the given 'class_types', 1280 'instance_types' and 'module_types'. 1281 1282 Return the class, instance, module types plus whether the types are 1283 constrained to a specific kind of type. 1284 """ 1285 1286 ref = self.importer.identify(path) 1287 if ref: 1288 1289 # Constrain usage suggestions using the identified object. 1290 1291 if ref.has_kind("<class>"): 1292 return ( 1293 set(class_types).intersection([ref.get_origin()]), [], [], True 1294 ) 1295 elif ref.has_kind("<module>"): 1296 return ( 1297 [], [], set(module_types).intersection([ref.get_origin()]), True 1298 ) 1299 1300 return class_types, instance_types, module_types, False 1301 1302 def get_target_types(self, location, usage): 1303 1304 """ 1305 Return the class, instance and module types constrained for the name at 1306 the given 'location' exhibiting the given 'usage'. Whether the types 1307 have been constrained using contextual information is also indicated, 1308 plus whether the types have been constrained to a specific kind of type. 1309 """ 1310 1311 unit_path, name, attrnames, version = location 1312 1313 # Detect any initialised name for the location. 1314 1315 if name: 1316 ref = self.get_initialised_name(location) 1317 if ref: 1318 (class_types, only_instance_types, module_types, 1319 _function_types, _var_types) = separate_types([ref]) 1320 return class_types, only_instance_types, module_types, True, False 1321 1322 # Retrieve the recorded types for the usage. 1323 1324 class_types = self.get_class_types_for_usage(usage) 1325 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1326 module_types = self.get_module_types_for_usage(usage) 1327 1328 # Merge usage deductions with observations to obtain reference types 1329 # for names involved with attribute accesses. 1330 1331 if not name: 1332 return class_types, only_instance_types, module_types, False, False 1333 1334 # Obtain references to known objects. 1335 1336 path = get_name_path(unit_path, name) 1337 1338 class_types, only_instance_types, module_types, constrained_specific = \ 1339 self.constrain_types(path, class_types, only_instance_types, module_types) 1340 1341 if constrained_specific: 1342 return class_types, only_instance_types, module_types, constrained_specific, constrained_specific 1343 1344 # Constrain "self" references. 1345 1346 if name == "self": 1347 t = self.constrain_self_reference(unit_path, class_types, only_instance_types) 1348 if t: 1349 class_types, only_instance_types, module_types, constrained = t 1350 return class_types, only_instance_types, module_types, constrained, False 1351 1352 return class_types, only_instance_types, module_types, False, False 1353 1354 def constrain_self_reference(self, unit_path, class_types, only_instance_types): 1355 1356 """ 1357 Where the name "self" appears in a method, attempt to constrain the 1358 classes involved. 1359 1360 Return the class, instance, module types plus whether the types are 1361 constrained. 1362 """ 1363 1364 class_name = self.in_method(unit_path) 1365 1366 if not class_name: 1367 return None 1368 1369 classes = set([class_name]) 1370 classes.update(self.get_descendants_for_class(class_name)) 1371 1372 # Note that only instances will be expected for these references but 1373 # either classes or instances may provide the attributes. 1374 1375 return ( 1376 set(class_types).intersection(classes), 1377 set(only_instance_types).intersection(classes), 1378 [], True 1379 ) 1380 1381 def in_method(self, path): 1382 1383 "Return whether 'path' refers to a method." 1384 1385 class_name, method_name = path.rsplit(".", 1) 1386 return self.importer.classes.has_key(class_name) and class_name 1387 1388 def init_reference_details(self, location): 1389 1390 "Initialise reference-related details for 'location'." 1391 1392 self.init_definition_details(location) 1393 self.init_access_details(location) 1394 1395 def init_definition_details(self, location): 1396 1397 "Initialise name definition details for 'location'." 1398 1399 self.accessor_class_types[location] = set() 1400 self.accessor_instance_types[location] = set() 1401 self.accessor_module_types[location] = set() 1402 self.provider_class_types[location] = set() 1403 self.provider_instance_types[location] = set() 1404 self.provider_module_types[location] = set() 1405 1406 def init_access_details(self, location): 1407 1408 "Initialise access details at 'location'." 1409 1410 self.referenced_attrs[location] = {} 1411 1412 def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): 1413 1414 """ 1415 Define types for the 'access_location' associated with the given 1416 'accessor_locations'. 1417 """ 1418 1419 path, name, attrnames, version = access_location 1420 if not attrnames: 1421 return 1422 1423 attrname = get_attrnames(attrnames)[0] 1424 1425 # Collect all suggested types for the accessors. Accesses may 1426 # require accessors from of a subset of the complete set of types. 1427 1428 class_types = set() 1429 module_types = set() 1430 instance_types = set() 1431 1432 constrained = True 1433 1434 for location in accessor_locations: 1435 1436 # Remember accesses employing aliases. 1437 1438 if alias_accesses is not None and self.alias_index.has_key(location): 1439 alias_accesses.add(access_location) 1440 1441 # Use the type information deduced for names from above. 1442 1443 if self.accessor_class_types.has_key(location): 1444 class_types.update(self.accessor_class_types[location]) 1445 module_types.update(self.accessor_module_types[location]) 1446 instance_types.update(self.accessor_instance_types[location]) 1447 1448 # Where accesses are associated with assignments but where no 1449 # attribute usage observations have caused such an association, 1450 # the attribute name is considered by itself. 1451 1452 else: 1453 self.init_definition_details(location) 1454 self.record_types_for_usage(location, [attrname]) 1455 1456 constrained = location in self.accessor_constrained and constrained 1457 1458 self.init_access_details(access_location) 1459 self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained) 1460 1461 def record_types_for_usage(self, accessor_location, usage): 1462 1463 """ 1464 Record types for the given 'accessor_location' according to the given 1465 'usage' observations which may be None to indicate an absence of usage. 1466 """ 1467 1468 (class_types, 1469 instance_types, 1470 module_types, 1471 constrained, 1472 constrained_specific) = self.get_target_types(accessor_location, usage) 1473 1474 self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific) 1475 1476 def record_types_for_attribute(self, access_location, attrname): 1477 1478 """ 1479 Record types for the 'access_location' employing only the given 1480 'attrname' for type deduction. 1481 """ 1482 1483 usage = [attrname] 1484 1485 class_types = self.get_class_types_for_usage(usage) 1486 only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types) 1487 module_types = self.get_module_types_for_usage(usage) 1488 1489 self.init_reference_details(access_location) 1490 1491 self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False) 1492 self.record_reference_types(access_location, class_types, only_instance_types, module_types, False) 1493 1494 def record_types_for_alias(self, accessor_location): 1495 1496 """ 1497 Define types for the 'accessor_location' not having associated usage. 1498 """ 1499 1500 have_access = self.provider_class_types.has_key(accessor_location) 1501 1502 # With an access, attempt to narrow the existing selection of provider 1503 # types. 1504 1505 if have_access: 1506 provider_class_types = self.provider_class_types[accessor_location] 1507 provider_instance_types = self.provider_instance_types[accessor_location] 1508 provider_module_types = self.provider_module_types[accessor_location] 1509 1510 # Find details for any corresponding access. 1511 1512 all_class_types = set() 1513 all_instance_types = set() 1514 all_module_types = set() 1515 1516 for access_location in self.alias_index[accessor_location]: 1517 location, name, attrnames, access_number = access_location 1518 1519 # Alias references an attribute access. 1520 1521 if attrnames: 1522 1523 # Obtain attribute references for the access. 1524 1525 attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]] 1526 1527 # Separate the different attribute types. 1528 1529 (class_types, instance_types, module_types, 1530 function_types, var_types) = separate_types(attrs) 1531 1532 # Where non-accessor types are found, do not attempt to refine 1533 # the defined accessor types. 1534 1535 if function_types or var_types: 1536 return 1537 1538 class_types = set(provider_class_types).intersection(class_types) 1539 instance_types = set(provider_instance_types).intersection(instance_types) 1540 module_types = set(provider_module_types).intersection(module_types) 1541 1542 # Alias references a name, not an access. 1543 1544 else: 1545 # Attempt to refine the types using initialised names. 1546 1547 attr = self.get_initialised_name(access_location) 1548 if attr: 1549 (class_types, instance_types, module_types, 1550 _function_types, _var_types) = separate_types([attr]) 1551 1552 # Where no further information is found, do not attempt to 1553 # refine the defined accessor types. 1554 1555 else: 1556 return 1557 1558 all_class_types.update(class_types) 1559 all_instance_types.update(instance_types) 1560 all_module_types.update(module_types) 1561 1562 # Record refined type details for the alias as an accessor. 1563 1564 self.init_definition_details(accessor_location) 1565 self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False) 1566 1567 # Without an access, attempt to identify references for the alias. 1568 1569 else: 1570 refs = set() 1571 1572 for access_location in self.alias_index[accessor_location]: 1573 1574 # Obtain any redefined constant access location. 1575 1576 if self.const_accesses.has_key(access_location): 1577 access_location = self.const_accesses[access_location] 1578 1579 location, name, attrnames, access_number = access_location 1580 1581 # Alias references an attribute access. 1582 1583 if attrnames: 1584 attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]] 1585 refs.update(attrs) 1586 1587 # Alias references a name, not an access. 1588 1589 else: 1590 attr = self.get_initialised_name(access_location) 1591 attrs = attr and [attr] or [] 1592 if not attrs and self.provider_class_types.has_key(access_location): 1593 class_types = self.provider_class_types[access_location] 1594 instance_types = self.provider_instance_types[access_location] 1595 module_types = self.provider_module_types[access_location] 1596 attrs = combine_types(class_types, instance_types, module_types) 1597 if attrs: 1598 refs.update(attrs) 1599 1600 # Record reference details for the alias separately from accessors. 1601 1602 self.referenced_objects[accessor_location] = refs 1603 1604 def get_initialised_name(self, access_location): 1605 1606 """ 1607 Return references for any initialised names at 'access_location', or 1608 None if no such references exist. 1609 """ 1610 1611 location, name, attrnames, version = access_location 1612 path = get_name_path(location, name) 1613 1614 # Use initialiser information, if available. 1615 1616 refs = self.importer.all_initialised_names.get(path) 1617 if refs and refs.has_key(version): 1618 return refs[version] 1619 else: 1620 return None 1621 1622 def record_reference_types(self, location, class_types, instance_types, 1623 module_types, constrained, constrained_specific=False): 1624 1625 """ 1626 Associate attribute provider types with the given 'location', consisting 1627 of the given 'class_types', 'instance_types' and 'module_types'. 1628 1629 If 'constrained' is indicated, the constrained nature of the accessor is 1630 recorded for the location. 1631 1632 If 'constrained_specific' is indicated using a true value, instance types 1633 will not be added to class types to permit access via instances at the 1634 given location. This is only useful where a specific accessor is known 1635 to be a class. 1636 1637 Note that the specified types only indicate the provider types for 1638 attributes, whereas the recorded accessor types indicate the possible 1639 types of the actual objects used to access attributes. 1640 """ 1641 1642 path, name, version, attrnames = location 1643 1644 # Update the type details for the location. 1645 1646 self.provider_class_types[location].update(class_types) 1647 self.provider_instance_types[location].update(instance_types) 1648 self.provider_module_types[location].update(module_types) 1649 1650 # Class types support classes and instances as accessors. 1651 # Instance-only and module types support only their own kinds as 1652 # accessors. 1653 1654 if self.invoked_attr_types.has_key(location): 1655 class_only_types = set(class_types).difference(self.invoked_attr_types[location]) 1656 else: 1657 class_only_types = class_types 1658 1659 # However, the nature of accessors can be further determined. 1660 # Any self variable may only refer to an instance. 1661 # Invocations of bound methods also require instances. 1662 1663 if name != "self" or not self.in_method(path): 1664 self.accessor_class_types[location].update(class_only_types) 1665 1666 if not constrained_specific: 1667 self.accessor_instance_types[location].update(class_types) 1668 1669 self.accessor_instance_types[location].update(instance_types) 1670 1671 if name != "self" or not self.in_method(path): 1672 self.accessor_module_types[location].update(module_types) 1673 1674 if constrained: 1675 self.accessor_constrained.add(location) 1676 1677 def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): 1678 1679 """ 1680 Identify reference attributes, associating them with the given 1681 'location', identifying the given 'attrname', employing the given 1682 'class_types', 'instance_types' and 'module_types'. 1683 1684 If 'constrained' is indicated, the constrained nature of the access is 1685 recorded for the location. 1686 """ 1687 1688 # Record the referenced objects. 1689 1690 self.referenced_attrs[location] = \ 1691 self._identify_reference_attribute(attrname, class_types, instance_types, module_types) 1692 1693 if constrained: 1694 self.access_constrained.add(location) 1695 1696 def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types): 1697 1698 """ 1699 Identify the reference attribute with the given 'attrname', employing 1700 the given 'class_types', 'instance_types' and 'module_types'. 1701 """ 1702 1703 attrs = set() 1704 1705 # The class types expose class attributes either directly or via 1706 # instances. 1707 1708 for object_type in class_types: 1709 ref = self.importer.get_class_attribute(object_type, attrname) 1710 if ref: 1711 attrs.add(("<class>", object_type, ref)) 1712 1713 # Add any distinct instance attributes that would be provided 1714 # by instances also providing indirect class attribute access. 1715 1716 for ref in self.importer.get_instance_attributes(object_type, attrname): 1717 attrs.add(("<instance>", object_type, ref)) 1718 1719 # The instance-only types expose instance attributes, but although 1720 # classes are excluded as potential accessors (since they do not provide 1721 # the instance attributes), the class types may still provide some 1722 # attributes. 1723 1724 for object_type in instance_types: 1725 instance_attrs = self.importer.get_instance_attributes(object_type, attrname) 1726 1727 if instance_attrs: 1728 for ref in instance_attrs: 1729 attrs.add(("<instance>", object_type, ref)) 1730 else: 1731 ref = self.importer.get_class_attribute(object_type, attrname) 1732 if ref: 1733 attrs.add(("<class>", object_type, ref)) 1734 1735 # Module types expose module attributes for module accessors. 1736 1737 for object_type in module_types: 1738 ref = self.importer.get_module_attribute(object_type, attrname) 1739 if ref: 1740 attrs.add(("<module>", object_type, ref)) 1741 1742 return attrs 1743 1744 constrained_specific_tests = ( 1745 "constrained-specific-instance", 1746 "constrained-specific-type", 1747 "constrained-specific-object", 1748 ) 1749 1750 constrained_common_tests = ( 1751 "constrained-common-instance", 1752 "constrained-common-type", 1753 "constrained-common-object", 1754 ) 1755 1756 guarded_specific_tests = ( 1757 "guarded-specific-instance", 1758 "guarded-specific-type", 1759 "guarded-specific-object", 1760 ) 1761 1762 guarded_common_tests = ( 1763 "guarded-common-instance", 1764 "guarded-common-type", 1765 "guarded-common-object", 1766 ) 1767 1768 specific_tests = ( 1769 "specific-instance", 1770 "specific-type", 1771 "specific-object", 1772 ) 1773 1774 common_tests = ( 1775 "common-instance", 1776 "common-type", 1777 "common-object", 1778 ) 1779 1780 class_tests = ( 1781 "guarded-specific-type", 1782 "guarded-common-type", 1783 "specific-type", 1784 "common-type", 1785 ) 1786 1787 class_or_instance_tests = ( 1788 "guarded-specific-object", 1789 "guarded-common-object", 1790 "specific-object", 1791 "common-object", 1792 ) 1793 1794 def get_access_plan(self, location): 1795 1796 """ 1797 Return details of the access at the given 'location'. The details are as 1798 follows: 1799 1800 * the initial accessor (from which accesses will be performed if no 1801 computed static accessor is found) 1802 * details of any test required on the initial accessor 1803 * details of any type employed by the test 1804 * any static accessor (from which accesses will be performed in 1805 preference to the initial accessor) 1806 * attributes needing to be traversed from the base that yield 1807 unambiguous objects 1808 * remaining attributes needing to be tested and traversed 1809 * details of the context 1810 * the method of obtaining the final attribute 1811 * any static final attribute 1812 """ 1813 1814 const_access = self.const_accesses_rev.has_key(location) 1815 1816 path, name, attrnames, version = location 1817 remaining = attrnames.split(".") 1818 attrname = remaining[0] 1819 1820 # Obtain reference and accessor information, retaining also distinct 1821 # provider kind details. 1822 1823 attrs = [] 1824 objtypes = [] 1825 provider_kinds = set() 1826 1827 for attrtype, objtype, attr in self.referenced_attrs[location]: 1828 attrs.append(attr) 1829 objtypes.append(objtype) 1830 provider_kinds.add(attrtype) 1831 1832 # Obtain accessor type and kind information. 1833 1834 accessor_types = self.reference_all_accessor_types[location] 1835 accessor_general_types = self.reference_all_accessor_general_types[location] 1836 accessor_kinds = get_kinds(accessor_general_types) 1837 1838 # Determine any guard or test requirements. 1839 1840 constrained = location in self.access_constrained 1841 test = self.reference_test_types[location] 1842 test_type = self.reference_test_accessor_type.get(location) 1843 1844 # Determine the accessor and provider properties. 1845 1846 class_accessor = "<class>" in accessor_kinds 1847 module_accessor = "<module>" in accessor_kinds 1848 instance_accessor = "<instance>" in accessor_kinds 1849 provided_by_class = "<class>" in provider_kinds 1850 provided_by_instance = "<instance>" in provider_kinds 1851 1852 # Determine how attributes may be accessed relative to the accessor. 1853 1854 object_relative = class_accessor or module_accessor or provided_by_instance 1855 class_relative = instance_accessor and provided_by_class 1856 1857 # Identify the last static attribute for context acquisition. 1858 1859 base = None 1860 dynamic_base = None 1861 1862 # Constant accesses have static accessors. 1863 1864 if const_access: 1865 base = len(objtypes) == 1 and first(objtypes) 1866 1867 # Constant accessors are static. 1868 1869 else: 1870 ref = self.importer.identify("%s.%s" % (path, name)) 1871 if ref: 1872 base = ref.get_origin() 1873 1874 # Usage of previously-generated guard and test details. 1875 1876 elif test in self.constrained_specific_tests: 1877 ref = first(accessor_types) 1878 1879 elif test in self.constrained_common_tests: 1880 ref = first(accessor_general_types) 1881 1882 elif test in self.guarded_specific_tests: 1883 ref = first(accessor_types) 1884 1885 elif test in self.guarded_common_tests: 1886 ref = first(accessor_general_types) 1887 1888 # For attribute-based tests, tentatively identify a dynamic base. 1889 # Such tests allow single or multiple kinds of a type. 1890 1891 elif test in self.common_tests or test in self.specific_tests: 1892 dynamic_base = test_type 1893 1894 # Static accessors. 1895 1896 if not base and test in self.class_tests: 1897 base = ref and ref.get_origin() or dynamic_base 1898 1899 # Accessors that are not static but whose nature is determined. 1900 1901 elif not base and ref: 1902 dynamic_base = ref.get_origin() 1903 1904 traversed = [] 1905 1906 # Traverse remaining attributes. 1907 1908 while len(attrs) == 1: 1909 attr = first(attrs) 1910 1911 traversed.append(attrname) 1912 del remaining[0] 1913 1914 if not remaining: 1915 break 1916 1917 # Update the last static attribute. 1918 1919 if attr.static(): 1920 base = attr.get_origin() 1921 traversed = [] 1922 1923 # Get the next attribute. 1924 1925 attrname = remaining[0] 1926 attrs = self.importer.get_attributes(attr, attrname) 1927 1928 # Where many attributes are suggested, no single attribute identity can 1929 # be loaded. 1930 1931 else: 1932 attr = None 1933 1934 # Determine the method of access. 1935 1936 # Identified attribute that must be accessed via its parent. 1937 1938 if attr and attr.get_name() and location in self.reference_assignments: 1939 method = "direct"; origin = attr.get_name() 1940 1941 # Static, identified attribute. 1942 1943 elif attr and attr.static(): 1944 method = "static"; origin = attr.final() 1945 1946 # Attribute accessed at a known position via its parent. 1947 1948 elif base or dynamic_base: 1949 method = "relative" + (object_relative and "-object" or "") + \ 1950 (class_relative and "-class" or "") 1951 origin = None 1952 1953 # The fallback case is always run-time testing and access. 1954 1955 else: 1956 method = "check" + (object_relative and "-object" or "") + \ 1957 (class_relative and "-class" or "") 1958 origin = None 1959 1960 # Determine the nature of the context. 1961 1962 context = base and "base" or len(traversed or remaining) > 1 and "traversal" or "accessor" 1963 1964 return name, test, test_type, base, traversed, remaining, context, method, origin 1965 1966 # vim: tabstop=4 expandtab shiftwidth=4