Lichen

Annotated deducer.py

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