Lichen

resolving.py

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