Lichen

Annotated deducer.py

1027:dd0745ab8b8a
5 months ago Paul Boddie Reordered GCC arguments to prevent linking failures. Someone decided to change the GCC invocation or linking semantics at some point, meaning that libraries specified "too early" in the argument list no longer provide the symbols required by the program objects, whereas specifying them at the end of the argument list allows those symbols to be found and obtained.
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