Lichen

Annotated deducer.py

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