Lichen

Annotated deducer.py

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