Lichen

Annotated deducer.py

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