Lichen

resolving.py

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