Lichen

Annotated deducer.py

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