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