Lichen

resolving.py

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