Lichen

Annotated resolving.py

649:7c8a7ad8dc50
2017-03-03 Paul Boddie Permit the parameterisation of the special instance indicator, making it match the position of the __class__ attribute, thus being different to any class type attribute position.
paul@12 1
#!/usr/bin/env python
paul@12 2
paul@12 3
"""
paul@12 4
Name resolution.
paul@12 5
paul@597 6
Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>
paul@12 7
paul@12 8
This program is free software; you can redistribute it and/or modify it under
paul@12 9
the terms of the GNU General Public License as published by the Free Software
paul@12 10
Foundation; either version 3 of the License, or (at your option) any later
paul@12 11
version.
paul@12 12
paul@12 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@12 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@12 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@12 16
details.
paul@12 17
paul@12 18
You should have received a copy of the GNU General Public License along with
paul@12 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@12 20
"""
paul@12 21
paul@135 22
from common import init_item
paul@12 23
from results import AccessRef, InstanceRef, InvocationRef, LocalNameRef, \
paul@12 24
                    NameRef, ResolvedNameRef
paul@12 25
from referencing import Reference
paul@12 26
import sys
paul@12 27
paul@12 28
class NameResolving:
paul@12 29
paul@12 30
    "Resolving names mix-in for inspected modules."
paul@12 31
paul@12 32
    # Post-inspection resolution activities.
paul@12 33
paul@12 34
    def resolve(self):
paul@12 35
paul@12 36
        "Resolve dependencies and complete definitions."
paul@12 37
paul@12 38
        self.resolve_class_bases()
paul@21 39
        self.check_special()
paul@12 40
        self.check_names_used()
paul@27 41
        self.check_invocations()
paul@12 42
        self.resolve_initialisers()
paul@12 43
        self.resolve_literals()
paul@12 44
paul@12 45
    def resolve_class_bases(self):
paul@12 46
paul@12 47
        "Resolve all class bases since some of them may have been deferred."
paul@12 48
paul@12 49
        for name, bases in self.classes.items():
paul@12 50
            resolved = []
paul@12 51
            bad = []
paul@12 52
paul@12 53
            for base in bases:
paul@27 54
                ref = self.importer.identify(base.get_origin())
paul@12 55
paul@12 56
                # Obtain the origin of the base class reference.
paul@12 57
paul@12 58
                if not ref or not ref.has_kind("<class>"):
paul@12 59
                    bad.append(base)
paul@12 60
                    break
paul@12 61
paul@12 62
                resolved.append(ref)
paul@12 63
paul@12 64
            if bad:
paul@72 65
                print >>sys.stderr, "Bases of class %s were not classes: %s" % (name, ", ".join(map(str, bad)))
paul@12 66
            else:
paul@12 67
                self.importer.classes[name] = self.classes[name] = resolved
paul@12 68
paul@12 69
    def check_special(self):
paul@12 70
paul@12 71
        "Check special names."
paul@12 72
paul@12 73
        for name, value in self.special.items():
paul@423 74
            ref, paths = value
paul@423 75
            self.special[name] = self.importer.identify(ref.get_origin()), paths
paul@12 76
paul@12 77
    def check_names_used(self):
paul@12 78
paul@27 79
        "Check the external names used by each scope."
paul@12 80
paul@27 81
        for key, ref in self.name_references.items():
paul@27 82
            path, name = key.rsplit(".", 1)
paul@27 83
            self.resolve_accesses(path, name, ref)
paul@12 84
paul@27 85
    def check_invocations(self):
paul@12 86
paul@27 87
        "Find invocations amongst module data and replace their results."
paul@12 88
paul@27 89
        # Find members and replace invocation results with values. This is
paul@27 90
        # effectively the same as is done for initialised names, but refers to
paul@27 91
        # any unchanging value after initialisation.
paul@12 92
paul@27 93
        for key, ref in self.objects.items():
paul@27 94
            if ref.has_kind("<invoke>"):
paul@27 95
                ref = self.convert_invocation(ref)
paul@27 96
                self.importer.objects[key] = self.objects[key] = ref
paul@12 97
paul@169 98
        # Convert name references.
paul@169 99
paul@169 100
        for key, ref in self.name_references.items():
paul@169 101
            if ref.has_kind("<invoke>"):
paul@169 102
                ref = self.convert_invocation(ref)
paul@169 103
                self.importer.all_name_references[key] = self.name_references[key] = ref
paul@169 104
paul@31 105
        # Convert function defaults, which are effectively extra members of the
paul@31 106
        # module, and function locals.
paul@12 107
paul@31 108
        for fname, parameters in self.function_defaults.items():
paul@27 109
            l = []
paul@27 110
            for pname, ref in parameters:
paul@27 111
                if ref.has_kind("<invoke>"):
paul@27 112
                    ref = self.convert_invocation(ref)
paul@27 113
                l.append((pname, ref))
paul@129 114
            self.importer.function_defaults[fname] = self.function_defaults[fname] = l
paul@12 115
paul@31 116
        # Convert function locals referencing invocations.
paul@31 117
paul@31 118
        for fname, names in self.function_locals.items():
paul@31 119
            for name, ref in names.items():
paul@31 120
                if ref.has_kind("<invoke>"):
paul@31 121
                    ref = self.convert_invocation(ref)
paul@31 122
                    names[name] = ref
paul@31 123
paul@27 124
    def convert_invocation(self, ref):
paul@27 125
paul@27 126
        "Convert the given invocation 'ref', handling instantiation."
paul@27 127
paul@175 128
        alias = ref.get_name()
paul@27 129
        ref = self.importer.identify(ref.get_origin())
paul@175 130
        ref = ref and ref.has_kind("<class>") and ref.instance_of() or Reference("<var>")
paul@175 131
        return ref and ref.alias(alias) or None
paul@12 132
paul@12 133
    def resolve_accesses(self, path, name, ref):
paul@12 134
paul@12 135
        """
paul@12 136
        Resolve any unresolved accesses in the function at the given 'path'
paul@12 137
        for the given 'name' corresponding to the indicated 'ref'. Note that
paul@12 138
        this mechanism cannot resolve things like inherited methods because
paul@12 139
        they are not recorded as program objects in their inherited locations.
paul@12 140
        """
paul@12 141
paul@12 142
        attr_accesses = self.global_attr_accesses.get(path)
paul@12 143
        all_attrnames = attr_accesses and attr_accesses.get(name)
paul@12 144
paul@12 145
        if not all_attrnames:
paul@12 146
            return
paul@12 147
paul@12 148
        # Insist on constant accessors.
paul@12 149
paul@12 150
        if not ref.has_kind(["<class>", "<module>"]):
paul@12 151
            return
paul@12 152
paul@12 153
        found_attrnames = set()
paul@12 154
paul@12 155
        for attrnames in all_attrnames:
paul@12 156
paul@12 157
            # Start with the resolved name, adding attributes.
paul@12 158
paul@12 159
            attrs = ref.get_path()
paul@12 160
            remaining = attrnames.split(".")
paul@12 161
            last_ref = ref
paul@12 162
paul@12 163
            # Add each component, testing for a constant object.
paul@12 164
paul@12 165
            while remaining:
paul@12 166
                attrname = remaining[0]
paul@12 167
                attrs.append(attrname)
paul@12 168
                del remaining[0]
paul@12 169
paul@12 170
                # Find any constant object reference.
paul@12 171
paul@12 172
                attr_ref = self.get_resolved_object(".".join(attrs))
paul@12 173
paul@12 174
                # Non-constant accessors terminate the traversal.
paul@12 175
paul@12 176
                if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]):
paul@12 177
paul@12 178
                    # Provide the furthest constant accessor unless the final
paul@12 179
                    # access can be resolved.
paul@12 180
paul@12 181
                    if remaining:
paul@12 182
                        remaining.insert(0, attrs.pop())
paul@12 183
                    else:
paul@12 184
                        last_ref = attr_ref
paul@12 185
                    break
paul@12 186
paul@12 187
                # Follow any reference to a constant object.
paul@12 188
                # Where the given name refers to an object in another location,
paul@12 189
                # switch to the other location in order to be able to test its
paul@12 190
                # attributes.
paul@12 191
paul@12 192
                last_ref = attr_ref
paul@12 193
                attrs = attr_ref.get_path()
paul@12 194
paul@12 195
            # Record a constant accessor only if an object was found
paul@12 196
            # that is different from the namespace involved.
paul@12 197
paul@12 198
            if last_ref:
paul@12 199
                objpath = ".".join(attrs)
paul@12 200
                if objpath != path:
paul@12 201
paul@27 202
                    if last_ref.has_kind("<invoke>"):
paul@27 203
                        last_ref = self.convert_invocation(last_ref)
paul@27 204
paul@12 205
                    # Establish a constant access.
paul@12 206
paul@12 207
                    init_item(self.const_accesses, path, dict)
paul@12 208
                    self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))
paul@12 209
paul@12 210
                    if len(attrs) > 1:
paul@12 211
                        found_attrnames.add(attrs[1])
paul@12 212
paul@12 213
        # Remove any usage records for the name.
paul@12 214
paul@12 215
        if found_attrnames:
paul@12 216
paul@12 217
            # NOTE: Should be only one name version.
paul@12 218
paul@12 219
            versions = []
paul@12 220
            for version in self.attr_usage[path][name]:
paul@12 221
                new_usage = set()
paul@12 222
                for usage in version:
paul@12 223
                    if found_attrnames.intersection(usage):
paul@12 224
                        new_usage.add(tuple(set(usage).difference(found_attrnames)))
paul@12 225
                    else:
paul@12 226
                        new_usage.add(usage)
paul@12 227
                versions.append(new_usage)
paul@12 228
paul@12 229
            self.attr_usage[path][name] = versions
paul@12 230
paul@12 231
    def resolve_initialisers(self):
paul@12 232
paul@12 233
        "Resolve initialiser values for names."
paul@12 234
paul@12 235
        # Get the initialisers in each namespace.
paul@12 236
paul@12 237
        for path, name_initialisers in self.name_initialisers.items():
paul@12 238
            const_accesses = self.const_accesses.get(path)
paul@12 239
paul@12 240
            # Resolve values for each name in a scope.
paul@12 241
paul@12 242
            for name, values in name_initialisers.items():
paul@12 243
                if path == self.name:
paul@12 244
                    assigned_path = name
paul@12 245
                else:
paul@12 246
                    assigned_path = "%s.%s" % (path, name)
paul@12 247
paul@12 248
                initialised_names = {}
paul@12 249
                aliased_names = {}
paul@12 250
paul@12 251
                for i, name_ref in enumerate(values):
paul@12 252
paul@12 253
                    # Unwrap invocations.
paul@12 254
paul@12 255
                    if isinstance(name_ref, InvocationRef):
paul@12 256
                        invocation = True
paul@12 257
                        name_ref = name_ref.name_ref
paul@12 258
                    else:
paul@12 259
                        invocation = False
paul@12 260
paul@12 261
                    # Obtain a usable reference from names or constants.
paul@12 262
paul@12 263
                    if isinstance(name_ref, ResolvedNameRef):
paul@12 264
                        if not name_ref.reference():
paul@12 265
                            continue
paul@12 266
                        ref = name_ref.reference()
paul@12 267
paul@12 268
                    # Obtain a reference from instances.
paul@12 269
paul@12 270
                    elif isinstance(name_ref, InstanceRef):
paul@12 271
                        if not name_ref.reference():
paul@12 272
                            continue
paul@12 273
                        ref = name_ref.reference()
paul@12 274
paul@12 275
                    # Resolve accesses that employ constants.
paul@12 276
paul@12 277
                    elif isinstance(name_ref, AccessRef):
paul@12 278
                        ref = None
paul@12 279
paul@12 280
                        if const_accesses:
paul@12 281
                            resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
paul@12 282
                            if resolved_access:
paul@12 283
                                objpath, ref, remaining_attrnames = resolved_access
paul@12 284
                                if remaining_attrnames:
paul@12 285
                                    ref = None
paul@12 286
paul@12 287
                        # Accesses that do not employ constants cannot be resolved,
paul@12 288
                        # but they may be resolvable later.
paul@12 289
paul@12 290
                        if not ref:
paul@12 291
                            if not invocation:
paul@597 292
paul@597 293
                                # Record the path used for tracking purposes
paul@597 294
                                # alongside original name, attribute and access
paul@597 295
                                # number details.
paul@597 296
paul@597 297
                                aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number
paul@597 298
paul@12 299
                            continue
paul@12 300
paul@12 301
                    # Attempt to resolve a plain name reference.
paul@12 302
paul@12 303
                    elif isinstance(name_ref, LocalNameRef):
paul@12 304
                        key = "%s.%s" % (path, name_ref.name)
paul@12 305
                        origin = self.name_references.get(key)
paul@12 306
paul@12 307
                        # Accesses that do not refer to known static objects
paul@12 308
                        # cannot be resolved, but they may be resolvable later.
paul@12 309
paul@12 310
                        if not origin:
paul@12 311
                            if not invocation:
paul@597 312
paul@597 313
                                # Record the path used for tracking purposes
paul@597 314
                                # alongside original name, attribute and access
paul@597 315
                                # number details.
paul@597 316
paul@597 317
                                aliased_names[i] = path, name_ref.name, None, name_ref.number
paul@597 318
paul@12 319
                            continue
paul@12 320
paul@12 321
                        ref = self.get_resolved_object(origin)
paul@12 322
                        if not ref:
paul@12 323
                            continue
paul@12 324
paul@12 325
                    elif isinstance(name_ref, NameRef):
paul@12 326
                        key = "%s.%s" % (path, name_ref.name)
paul@12 327
                        origin = self.name_references.get(key)
paul@12 328
                        if not origin:
paul@12 329
                            continue
paul@12 330
paul@12 331
                        ref = self.get_resolved_object(origin)
paul@12 332
                        if not ref:
paul@12 333
                            continue
paul@12 334
paul@12 335
                    else:
paul@12 336
                        continue
paul@12 337
paul@22 338
                    # Resolve any hidden dependencies involving external objects
paul@22 339
                    # or unresolved names referring to globals or built-ins.
paul@22 340
paul@22 341
                    if ref.has_kind("<depends>"):
paul@27 342
                        ref = self.importer.identify(ref.get_origin())
paul@22 343
paul@12 344
                    # Convert class invocations to instances.
paul@12 345
paul@58 346
                    if ref and invocation:
paul@27 347
                        ref = self.convert_invocation(ref)
paul@12 348
paul@27 349
                    if ref and not ref.has_kind("<var>"):
paul@12 350
                        initialised_names[i] = ref
paul@12 351
paul@12 352
                if initialised_names:
paul@12 353
                    self.initialised_names[assigned_path] = initialised_names
paul@12 354
                if aliased_names:
paul@12 355
                    self.aliased_names[assigned_path] = aliased_names
paul@12 356
paul@12 357
    def resolve_literals(self):
paul@12 358
paul@12 359
        "Resolve constant value types."
paul@12 360
paul@12 361
        # Get the constants defined in each namespace.
paul@12 362
paul@12 363
        for path, constants in self.constants.items():
paul@12 364
            for constant, n in constants.items():
paul@12 365
                objpath = "%s.$c%d" % (path, n)
paul@406 366
                _constant, value_type, encoding = self.constant_values[objpath]
paul@12 367
                self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
paul@12 368
paul@12 369
        # Get the literals defined in each namespace.
paul@12 370
paul@12 371
        for path, literals in self.literals.items():
paul@12 372
            for n in range(0, literals):
paul@12 373
                objpath = "%s.$C%d" % (path, n)
paul@12 374
                value_type = self.literal_types[objpath]
paul@12 375
                self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
paul@12 376
paul@29 377
    # Object resolution.
paul@29 378
paul@29 379
    def get_resolved_object(self, path):
paul@29 380
paul@29 381
        """
paul@29 382
        Get the details of an object with the given 'path' within this module.
paul@29 383
        Where the object has not been resolved, None is returned. This differs
paul@29 384
        from the get_object method used elsewhere in that it does not return an
paul@29 385
        unresolved object reference.
paul@29 386
        """
paul@29 387
paul@29 388
        if self.objects.has_key(path):
paul@29 389
            ref = self.objects[path]
paul@29 390
            if ref.has_kind("<depends>"):
paul@29 391
                return None
paul@29 392
            else:
paul@29 393
                return ref
paul@29 394
        else:
paul@29 395
            return None
paul@29 396
paul@12 397
# vim: tabstop=4 expandtab shiftwidth=4