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