Lichen

Annotated deducer.py

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