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