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