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