1.1 --- a/importer.py Sat Sep 03 22:38:21 2016 +0200
1.2 +++ b/importer.py Sat Sep 03 22:43:44 2016 +0200
1.3 @@ -49,11 +49,10 @@
1.4 self.cache = cache
1.5 self.verbose = verbose
1.6
1.7 + self.to_import = set()
1.8 +
1.9 self.modules = {}
1.10 - self.modules_ordered = []
1.11 - self.loading = set()
1.12 - self.hidden = {}
1.13 - self.revealing = {}
1.14 + self.accessing_modules = {}
1.15 self.invalidated = set()
1.16
1.17 self.objects = {}
1.18 @@ -230,45 +229,33 @@
1.19
1.20 # Module management.
1.21
1.22 + def queue_module(self, name, module):
1.23 +
1.24 + """
1.25 + Queue the module with the given 'name' for import from the given
1.26 + 'module'.
1.27 + """
1.28 +
1.29 + if not self.modules.has_key(name):
1.30 + self.to_import.add(name)
1.31 +
1.32 + init_item(self.accessing_modules, name, set)
1.33 + self.accessing_modules[name].add(module.name)
1.34 +
1.35 def get_modules(self):
1.36
1.37 "Return all modules known to the importer."
1.38
1.39 return self.modules.values()
1.40
1.41 - def get_module(self, name, hidden=False):
1.42 + def get_module(self, name):
1.43
1.44 "Return the module with the given 'name'."
1.45
1.46 if not self.modules.has_key(name):
1.47 return None
1.48
1.49 - # Obtain the module and attempt to reveal it.
1.50 -
1.51 - module = self.modules[name]
1.52 - if not hidden:
1.53 - self.reveal_module(module)
1.54 - return module
1.55 -
1.56 - def reveal_module(self, module):
1.57 -
1.58 - "Check if 'module' is hidden and reveal it."
1.59 -
1.60 - if module.name in self.hidden:
1.61 - del self.hidden[module.name]
1.62 -
1.63 - # Reveal referenced modules.
1.64 -
1.65 - module.reveal_referenced()
1.66 -
1.67 - def set_revealing(self, module, name, instigator):
1.68 -
1.69 - """
1.70 - Make the revealing of 'module' conditional on 'name' for the given
1.71 - 'instigator' of the reveal operation.
1.72 - """
1.73 -
1.74 - self.revealing[module.name].add((name, instigator))
1.75 + return self.modules[name]
1.76
1.77 # Program operations.
1.78
1.79 @@ -287,10 +274,20 @@
1.80
1.81 m = self.load_from_file(filename)
1.82
1.83 + # Load any queued modules.
1.84 +
1.85 + while self.to_import:
1.86 + for name in list(self.to_import): # avoid mutation issue
1.87 + self.load(name)
1.88 +
1.89 + # Resolve dependencies between modules.
1.90 +
1.91 + self.resolve()
1.92 +
1.93 # Resolve dependencies within the program.
1.94
1.95 - for module in self.modules_ordered:
1.96 - module.resolve()
1.97 + for module in self.modules.values():
1.98 + module.complete()
1.99
1.100 return m
1.101
1.102 @@ -299,12 +296,38 @@
1.103 "Finalise the inspected program."
1.104
1.105 self.finalise_classes()
1.106 - self.remove_hidden()
1.107 self.to_cache()
1.108 self.set_class_types()
1.109 self.define_instantiators()
1.110 self.collect_constants()
1.111
1.112 + # Supporting operations.
1.113 +
1.114 + def resolve(self):
1.115 +
1.116 + "Resolve dependencies between modules."
1.117 +
1.118 + resolved = {}
1.119 +
1.120 + for name, ref in self.objects.items():
1.121 + if ref.has_kind("<depends>"):
1.122 + ref = self.find_dependency(ref)
1.123 + if ref:
1.124 + resolved[name] = ref
1.125 +
1.126 + for name, ref in resolved.items():
1.127 + self.objects[name] = ref
1.128 +
1.129 + def find_dependency(self, ref):
1.130 +
1.131 + "Find the ultimate dependency for 'ref'."
1.132 +
1.133 + found = set()
1.134 + while ref and ref.has_kind("<depends>") and not ref in found:
1.135 + found.add(ref)
1.136 + ref = self.objects.get(ref.get_origin())
1.137 + return ref
1.138 +
1.139 def finalise_classes(self):
1.140
1.141 "Finalise the class relationships and attributes."
1.142 @@ -395,41 +418,6 @@
1.143 if attrs:
1.144 self.all_shadowed_attrs[name] = attrs
1.145
1.146 - def remove_hidden(self):
1.147 -
1.148 - "Remove all hidden modules."
1.149 -
1.150 - # First reveal any modules exposing names.
1.151 -
1.152 - for modname, names in self.revealing.items():
1.153 - module = self.modules[modname]
1.154 -
1.155 - # Obtain the imported names and determine whether they should cause
1.156 - # the module to be revealed.
1.157 -
1.158 - for (name, instigator) in names:
1.159 - if module is not instigator:
1.160 -
1.161 - # Only if an object is provided by the module should the
1.162 - # module be revealed. References to objects in other modules
1.163 - # should not in themselves expose the module in which those
1.164 - # references occur.
1.165 -
1.166 - ref = module.get_global(name)
1.167 - if ref and ref.provided_by_module(module.name):
1.168 - self.reveal_module(module)
1.169 - instigator.revealed.add(module)
1.170 -
1.171 - # Then remove all modules that are still hidden.
1.172 -
1.173 - for modname in self.hidden:
1.174 - module = self.modules[modname]
1.175 - module.unpropagate()
1.176 - del self.modules[modname]
1.177 - ref = self.objects.get(modname)
1.178 - if ref and ref.get_kind() == "<module>":
1.179 - del self.objects[modname]
1.180 -
1.181 def set_class_types(self):
1.182
1.183 "Set the type of each class."
1.184 @@ -533,30 +521,22 @@
1.185 else:
1.186 return None
1.187
1.188 - def load(self, name, return_leaf=False, hidden=False):
1.189 + def load(self, name):
1.190
1.191 """
1.192 Load the module or package with the given 'name'. Return an object
1.193 referencing the loaded module or package, or None if no such module or
1.194 package exists.
1.195 -
1.196 - Where 'return_leaf' is specified, the final module in the chain is
1.197 - returned. Where 'hidden' is specified, the module is marked as hidden.
1.198 """
1.199
1.200 - if return_leaf:
1.201 - name_for_return = name
1.202 - else:
1.203 - name_for_return = name.split(".")[0]
1.204 -
1.205 # Loaded modules are returned immediately.
1.206 # Modules may be known but not yet loading (having been registered as
1.207 # submodules), loading, loaded, or completely unknown.
1.208
1.209 - module = self.get_module(name, hidden)
1.210 + module = self.get_module(name)
1.211
1.212 if module:
1.213 - return self.modules[name_for_return]
1.214 + return self.modules[name]
1.215
1.216 # Otherwise, modules are loaded.
1.217
1.218 @@ -568,7 +548,7 @@
1.219
1.220 path = name.split(".")
1.221 path_so_far = []
1.222 - top = module = None
1.223 + module = None
1.224
1.225 for p in path:
1.226
1.227 @@ -593,18 +573,11 @@
1.228 # Get the module itself.
1.229
1.230 d, filename = m
1.231 - submodule = self.load_from_file(filename, module_name, hidden)
1.232 -
1.233 - if module is None:
1.234 - top = submodule
1.235 + module = self.load_from_file(filename, module_name)
1.236
1.237 - module = submodule
1.238 -
1.239 - # Return either the deepest or the uppermost module.
1.240 + return module
1.241
1.242 - return return_leaf and module or top
1.243 -
1.244 - def load_from_file(self, filename, module_name=None, hidden=False):
1.245 + def load_from_file(self, filename, module_name=None):
1.246
1.247 "Load the module from the given 'filename'."
1.248
1.249 @@ -617,7 +590,7 @@
1.250
1.251 # Try to load from cache.
1.252
1.253 - module = self.load_from_cache(filename, module_name, hidden)
1.254 + module = self.load_from_cache(filename, module_name)
1.255 if module:
1.256 return module
1.257
1.258 @@ -627,10 +600,7 @@
1.259 self.add_module(module_name, module)
1.260 self.update_cache_validity(module)
1.261
1.262 - # Initiate loading if not already in progress.
1.263 -
1.264 - if not module.loaded and module not in self.loading:
1.265 - self._load(module, module_name, hidden, lambda m: m.parse, filename)
1.266 + self._load(module, module_name, lambda m: m.parse, filename)
1.267
1.268 return module
1.269
1.270 @@ -638,7 +608,9 @@
1.271
1.272 "Make 'module' valid in the cache, but invalidate accessing modules."
1.273
1.274 - self.invalidated.update(module.accessing_modules)
1.275 + accessing = self.accessing_modules.get(module.name)
1.276 + if accessing:
1.277 + self.invalidated.update(accessing)
1.278 if module.name in self.invalidated:
1.279 self.invalidated.remove(module.name)
1.280
1.281 @@ -654,48 +626,35 @@
1.282 else:
1.283 return True
1.284
1.285 - def load_from_cache(self, filename, module_name, hidden=False):
1.286 + def load_from_cache(self, filename, module_name):
1.287
1.288 "Return a module residing in the cache."
1.289
1.290 module = self.modules.get(module_name)
1.291
1.292 - if not self.source_is_new(filename, module_name):
1.293 + if not module and not self.source_is_new(filename, module_name):
1.294 + module = inspector.CachedModule(module_name, self)
1.295 + self.add_module(module_name, module)
1.296
1.297 - if not module:
1.298 - module = inspector.CachedModule(module_name, self)
1.299 - self.add_module(module_name, module)
1.300 -
1.301 - if not module.loaded and module not in self.loading:
1.302 - filename = join(self.cache, module_name)
1.303 - self._load(module, module_name, hidden, lambda m: m.from_cache, filename)
1.304 + filename = join(self.cache, module_name)
1.305 + self._load(module, module_name, lambda m: m.from_cache, filename)
1.306
1.307 return module
1.308
1.309 - def _load(self, module, module_name, hidden, fn, filename):
1.310 + def _load(self, module, module_name, fn, filename):
1.311
1.312 """
1.313 - Load 'module' for the given 'module_name', with the module being hidden
1.314 - if 'hidden' is a true value, and with 'fn' performing an invocation on
1.315 - the module with the given 'filename'.
1.316 + Load 'module' for the given 'module_name', and with 'fn' performing an
1.317 + invocation on the module with the given 'filename'.
1.318 """
1.319
1.320 - # Indicate that the module is hidden if requested.
1.321 + # Load the module.
1.322
1.323 - if hidden:
1.324 - self.hidden[module_name] = module
1.325 -
1.326 - # Indicate that loading is in progress and load the module.
1.327 -
1.328 - self.loading.add(module)
1.329 if self.verbose:
1.330 print >>sys.stderr, "Loading", filename
1.331 fn(module)(filename)
1.332 if self.verbose:
1.333 print >>sys.stderr, "Loaded", filename
1.334 - self.loading.remove(module)
1.335 -
1.336 - self.modules_ordered.append(module)
1.337
1.338 def add_module(self, module_name, module):
1.339
1.340 @@ -706,5 +665,7 @@
1.341
1.342 self.modules[module_name] = module
1.343 self.objects[module_name] = Reference("<module>", module_name)
1.344 + if module_name in self.to_import:
1.345 + self.to_import.remove(module_name)
1.346
1.347 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/inspector.py Sat Sep 03 22:38:21 2016 +0200
2.2 +++ b/inspector.py Sat Sep 03 22:43:44 2016 +0200
2.3 @@ -21,48 +21,23 @@
2.4 """
2.5
2.6 from branching import BranchTracker
2.7 -from common import *
2.8 -from modules import *
2.9 -from os import listdir
2.10 -from os.path import extsep, split, splitext
2.11 +from common import get_argnames, init_item, predefined_constants
2.12 +from modules import BasicModule, CacheWritingModule
2.13 from referencing import Reference
2.14 +from resolving import NameResolving
2.15 +from results import AccessRef, InstanceRef, InvocationRef, LiteralSequenceRef, \
2.16 + LocalNameRef, NameRef, ResolvedNameRef
2.17 import compiler
2.18 import sys
2.19
2.20 -class AccessRef(Result):
2.21 -
2.22 - """
2.23 - A reference to an attribute access that is generally only returned from a
2.24 - processed access for possible initialiser resolution for assignments.
2.25 - """
2.26 -
2.27 - def __init__(self, original_name, attrnames, number):
2.28 - self.original_name = original_name
2.29 - self.attrnames = attrnames
2.30 - self.number = number
2.31 -
2.32 - def reference(self):
2.33 - return None
2.34 -
2.35 - def __repr__(self):
2.36 - return "AccessRef(%r, %r, %r)" % (self.original_name, self.attrnames, self.number)
2.37 -
2.38 -class InvocationRef(Result):
2.39 -
2.40 - "An invocation of a name reference."
2.41 -
2.42 - def __init__(self, name_ref):
2.43 - self.name_ref = name_ref
2.44 -
2.45 - def __repr__(self):
2.46 - return "InvocationRef(%r)" % self.name_ref
2.47 -
2.48 -class InspectedModule(BasicModule, CacheWritingModule):
2.49 +class InspectedModule(BasicModule, CacheWritingModule, NameResolving):
2.50
2.51 "A module inspector."
2.52
2.53 def __init__(self, name, importer):
2.54 BasicModule.__init__(self, name, importer)
2.55 + NameResolving.__init__(self)
2.56 +
2.57 self.in_class = False
2.58 self.in_conditional = False
2.59 self.global_attr_accesses = {}
2.60 @@ -105,340 +80,21 @@
2.61
2.62 self.stop_tracking_in_module()
2.63
2.64 - # Check names used and resolve them.
2.65 -
2.66 - self.register_submodules()
2.67 - self.loaded = True
2.68 -
2.69 - def register_submodules(self):
2.70 -
2.71 - "For package modules add submodule details."
2.72 -
2.73 - if splitext(split(self.filename)[1])[0] == "__init__":
2.74 - for subname in listdir(split(self.filename)[0]):
2.75 - name, ext = splitext(subname)
2.76 -
2.77 - # Add modules whose names are not already defined as globals.
2.78 -
2.79 - if ext == ("%spy" % extsep) and name != "__init__" and not self.get_global(name):
2.80 - module_name = self.get_global_path(name)
2.81 - top, submodule = self.get_module(module_name, True)
2.82 - self.set_module(name, submodule, hidden=True)
2.83 -
2.84 - def check_special(self):
2.85 -
2.86 - "Check special names."
2.87 -
2.88 - for name, value in self.special.items():
2.89 - if value.has_kind("<depends>"):
2.90 - self.find_imported_name(name, self.name)
2.91 - self.special[name] = self.get_object(value.get_origin())
2.92 -
2.93 - def check_names_used(self):
2.94 -
2.95 - "Check the names used by each function."
2.96 -
2.97 - for path in self.names_used.keys():
2.98 - self.check_names_used_for_path(path)
2.99 -
2.100 - def check_names_used_for_path(self, path):
2.101 -
2.102 - "Check the names used by the given 'path'."
2.103 + def complete(self):
2.104
2.105 - names = self.names_used.get(path)
2.106 - if not names:
2.107 - return
2.108 -
2.109 - in_function = self.function_locals.has_key(path)
2.110 -
2.111 - for name in names:
2.112 - if name in predefined_constants or in_function and name in self.function_locals[path]:
2.113 - continue
2.114 -
2.115 - # Resolve names that have been imported locally.
2.116 -
2.117 - self.find_imported_name(name, path)
2.118 -
2.119 - # Find local definitions.
2.120 -
2.121 - key = "%s.%s" % (path, name)
2.122 - ref = self.get_object(key)
2.123 - if ref:
2.124 - self.name_references[key] = ref.final() or key
2.125 - self.resolve_accesses(path, name, ref)
2.126 - continue
2.127 -
2.128 - # Resolve names that have been imported globally.
2.129 -
2.130 - self.find_imported_name(name, self.name)
2.131 -
2.132 - # Find global or built-in definitions.
2.133 -
2.134 - ref = self.get_global_or_builtin(name)
2.135 - objpath = ref and (ref.final() or ref.get_name())
2.136 - if objpath:
2.137 - self.name_references[key] = objpath
2.138 - self.resolve_accesses(path, name, ref)
2.139 - continue
2.140 -
2.141 - print >>sys.stderr, "Name not recognised: %s in %s" % (name, path)
2.142 - init_item(self.names_missing, path, set)
2.143 - self.names_missing[path].add(name)
2.144 -
2.145 - def resolve_members(self):
2.146 + "Complete the module inspection."
2.147
2.148 - "Resolve any members referring to deferred references."
2.149 -
2.150 - for name, ref in self.objects.items():
2.151 - if ref.has_kind("<depends>"):
2.152 - ref = self.get_object(ref.get_origin())
2.153 - ref = ref.alias(name)
2.154 - self.importer.objects[name] = self.objects[name] = ref
2.155 -
2.156 - def resolve_accesses(self, path, name, ref):
2.157 -
2.158 - """
2.159 - Resolve any unresolved accesses in the function at the given 'path'
2.160 - for the given 'name' corresponding to the indicated 'ref'. Note that
2.161 - this mechanism cannot resolve things like inherited methods because
2.162 - they are not recorded as program objects in their inherited locations.
2.163 - """
2.164 -
2.165 - attr_accesses = self.global_attr_accesses.get(path)
2.166 - all_attrnames = attr_accesses and attr_accesses.get(name)
2.167 -
2.168 - if not all_attrnames:
2.169 - return
2.170 -
2.171 - # Insist on constant accessors.
2.172 -
2.173 - if not ref.has_kind(["<class>", "<module>"]):
2.174 - return
2.175 -
2.176 - found_attrnames = set()
2.177 -
2.178 - for attrnames in all_attrnames:
2.179 -
2.180 - # Start with the resolved name, adding attributes.
2.181 -
2.182 - attrs = ref.get_path()
2.183 - remaining = attrnames.split(".")
2.184 - last_ref = ref
2.185 -
2.186 - # Add each component, testing for a constant object.
2.187 + # Resolve names not definitively mapped to objects.
2.188
2.189 - while remaining:
2.190 - attrname = remaining[0]
2.191 - attrs.append(attrname)
2.192 - del remaining[0]
2.193 -
2.194 - # Find any constant object reference.
2.195 -
2.196 - attr_ref = self.get_object(".".join(attrs))
2.197 -
2.198 - # Non-constant accessors terminate the traversal.
2.199 -
2.200 - if not attr_ref.has_kind(["<class>", "<module>", "<function>"]):
2.201 -
2.202 - # Provide the furthest constant accessor unless the final
2.203 - # access can be resolved.
2.204 -
2.205 - if remaining:
2.206 - remaining.insert(0, attrs.pop())
2.207 - else:
2.208 - last_ref = attr_ref
2.209 - break
2.210 -
2.211 - # A module may expose an attribute imported from a hidden
2.212 - # module.
2.213 -
2.214 - elif last_ref.has_kind("<module>"):
2.215 - module, leaf_module = self.get_module(last_ref.get_origin())
2.216 - self.find_imported_name(attrname, module.name, module)
2.217 -
2.218 - # Follow any reference to a constant object.
2.219 - # Where the given name refers to an object in another location,
2.220 - # switch to the other location in order to be able to test its
2.221 - # attributes.
2.222 -
2.223 - last_ref = attr_ref
2.224 - attrs = attr_ref.get_path()
2.225 -
2.226 - # Record a constant accessor only if an object was found
2.227 - # that is different from the namespace involved.
2.228 -
2.229 - if last_ref:
2.230 - objpath = ".".join(attrs)
2.231 - if objpath != path:
2.232 + self.resolve()
2.233
2.234 - # Establish a constant access.
2.235 -
2.236 - init_item(self.const_accesses, path, dict)
2.237 - self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))
2.238 -
2.239 - if len(attrs) > 1:
2.240 - found_attrnames.add(attrs[1])
2.241 -
2.242 - # Remove any usage records for the name.
2.243 -
2.244 - if found_attrnames:
2.245 -
2.246 - # NOTE: Should be only one name version.
2.247 -
2.248 - versions = []
2.249 - for version in self.attr_usage[path][name]:
2.250 - new_usage = set()
2.251 - for usage in version:
2.252 - if found_attrnames.intersection(usage):
2.253 - new_usage.add(tuple(set(usage).difference(found_attrnames)))
2.254 - else:
2.255 - new_usage.add(usage)
2.256 - versions.append(new_usage)
2.257 -
2.258 - self.attr_usage[path][name] = versions
2.259 -
2.260 - def resolve_initialisers(self):
2.261 -
2.262 - "Resolve initialiser values for names."
2.263 -
2.264 - # Get the initialisers in each namespace.
2.265 -
2.266 - for path, name_initialisers in self.name_initialisers.items():
2.267 - const_accesses = self.const_accesses.get(path)
2.268 -
2.269 - # Resolve values for each name in a scope.
2.270 + # Define the invocation requirements in each namespace.
2.271
2.272 - for name, values in name_initialisers.items():
2.273 - if path == self.name:
2.274 - assigned_path = name
2.275 - else:
2.276 - assigned_path = "%s.%s" % (path, name)
2.277 -
2.278 - initialised_names = {}
2.279 - aliased_names = {}
2.280 -
2.281 - for i, name_ref in enumerate(values):
2.282 -
2.283 - # Unwrap invocations.
2.284 -
2.285 - if isinstance(name_ref, InvocationRef):
2.286 - invocation = True
2.287 - name_ref = name_ref.name_ref
2.288 - else:
2.289 - invocation = False
2.290 -
2.291 - # Obtain a usable reference from names or constants.
2.292 -
2.293 - if isinstance(name_ref, ResolvedNameRef):
2.294 - if not name_ref.reference():
2.295 - continue
2.296 - ref = name_ref.reference()
2.297 -
2.298 - # Obtain a reference from instances.
2.299 -
2.300 - elif isinstance(name_ref, InstanceRef):
2.301 - if not name_ref.reference():
2.302 - continue
2.303 - ref = name_ref.reference()
2.304 -
2.305 - # Resolve accesses that employ constants.
2.306 -
2.307 - elif isinstance(name_ref, AccessRef):
2.308 - ref = None
2.309 -
2.310 - if const_accesses:
2.311 - resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
2.312 - if resolved_access:
2.313 - objpath, ref, remaining_attrnames = resolved_access
2.314 - if remaining_attrnames:
2.315 - ref = None
2.316 + self.set_invocation_usage()
2.317
2.318 - # Accesses that do not employ constants cannot be resolved,
2.319 - # but they may be resolvable later.
2.320 -
2.321 - if not ref:
2.322 - if not invocation:
2.323 - aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number
2.324 - continue
2.325 -
2.326 - # Attempt to resolve a plain name reference.
2.327 -
2.328 - elif isinstance(name_ref, LocalNameRef):
2.329 - key = "%s.%s" % (path, name_ref.name)
2.330 - origin = self.name_references.get(key)
2.331 -
2.332 - # Accesses that do not refer to known static objects
2.333 - # cannot be resolved, but they may be resolvable later.
2.334 -
2.335 - if not origin:
2.336 - if not invocation:
2.337 - aliased_names[i] = name_ref.name, None, name_ref.number
2.338 - continue
2.339 -
2.340 - ref = self.get_object(origin)
2.341 - if not ref:
2.342 - continue
2.343 -
2.344 - elif isinstance(name_ref, NameRef):
2.345 - key = "%s.%s" % (path, name_ref.name)
2.346 - origin = self.name_references.get(key)
2.347 - if not origin:
2.348 - continue
2.349 -
2.350 - ref = self.get_object(origin)
2.351 - if not ref:
2.352 - continue
2.353 -
2.354 - else:
2.355 - continue
2.356 -
2.357 - # Convert class invocations to instances.
2.358 + # Propagate to the importer information needed in subsequent activities.
2.359
2.360 - if invocation:
2.361 - ref = ref.has_kind("<class>") and ref.instance_of() or None
2.362 -
2.363 - if ref:
2.364 - initialised_names[i] = ref
2.365 -
2.366 - if initialised_names:
2.367 - self.initialised_names[assigned_path] = initialised_names
2.368 - if aliased_names:
2.369 - self.aliased_names[assigned_path] = aliased_names
2.370 -
2.371 - def resolve_literals(self):
2.372 -
2.373 - "Resolve constant value types."
2.374 -
2.375 - # Get the constants defined in each namespace.
2.376 -
2.377 - for path, constants in self.constants.items():
2.378 - for constant, n in constants.items():
2.379 - objpath = "%s.$c%d" % (path, n)
2.380 - _constant, value_type = self.constant_values[objpath]
2.381 - self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
2.382 -
2.383 - # Get the literals defined in each namespace.
2.384 -
2.385 - for path, literals in self.literals.items():
2.386 - for n in range(0, literals):
2.387 - objpath = "%s.$C%d" % (path, n)
2.388 - value_type = self.literal_types[objpath]
2.389 - self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
2.390 -
2.391 - def remove_redundant_accessors(self):
2.392 -
2.393 - "Remove now-redundant modifier and accessor information."
2.394 -
2.395 - for path, const_accesses in self.const_accesses.items():
2.396 - accesses = self.attr_accessors.get(path)
2.397 - modifiers = self.attr_access_modifiers.get(path)
2.398 - if not accesses:
2.399 - continue
2.400 - for access in const_accesses.keys():
2.401 - if accesses.has_key(access):
2.402 - del accesses[access]
2.403 - if modifiers and modifiers.has_key(access):
2.404 - del modifiers[access]
2.405 + self.propagate()
2.406
2.407 def set_invocation_usage(self):
2.408
2.409 @@ -761,8 +417,8 @@
2.410 base_class = self.get_class(base)
2.411
2.412 if not base_class:
2.413 - print >>sys.stderr, "In %s, class %s has unidentifiable base classes." % (
2.414 - path, n.name)
2.415 + print >>sys.stderr, "In %s, class %s has unidentifiable base class: %s" % (
2.416 + path, n.name, base)
2.417 return
2.418 else:
2.419 bases.append(base_class)
2.420 @@ -804,73 +460,23 @@
2.421
2.422 path = self.get_namespace_path()
2.423
2.424 - modname, names = self.get_module_name(n)
2.425 -
2.426 - # Load the mentioned module.
2.427 + module_name, names = self.get_module_name(n)
2.428 + if module_name == self.name:
2.429 + raise InspectError("Cannot import from the current module.", path, n)
2.430
2.431 - top, module = self.get_module(modname, True)
2.432 - self.set_module(None, module, hidden=True)
2.433 -
2.434 - if not module:
2.435 - print >>sys.stderr, "In %s, from statement importing from %s failed." % (
2.436 - path, modname)
2.437 + self.importer.queue_module(module_name, self)
2.438
2.439 # Attempt to obtain the referenced objects.
2.440
2.441 for name, alias in n.names:
2.442 -
2.443 - # NOTE: Package submodules are not implicitly imported.
2.444 -
2.445 if name == "*":
2.446 - if module:
2.447 -
2.448 - # Warn about a circular import that probably doesn't find
2.449 - # the names.
2.450 -
2.451 - if not module.loaded:
2.452 - print >>sys.stderr, "In %s, from statement performs circular import %s of %s." % (
2.453 - path, modname)
2.454 -
2.455 - for name, value in module.get_globals().items():
2.456 - if name != "__name__":
2.457 - value = ResolvedNameRef(name, value)
2.458 - self.set_general_local(name, value)
2.459 - self.set_imported_name(name, modname)
2.460 - break
2.461 + raise InspectError("Only explicitly specified names can be imported from modules.", path, n)
2.462
2.463 # Explicit names.
2.464
2.465 - ref = self.import_name_from_module(name, modname, module, alias)
2.466 + ref = self.import_name_from_module(name, module_name)
2.467 value = ResolvedNameRef(alias or name, ref)
2.468 self.set_general_local(alias or name, value)
2.469 - self.set_imported_name(name, modname, alias)
2.470 -
2.471 - def import_name_from_module(self, name, modname, module, alias=None):
2.472 -
2.473 - """
2.474 - Import 'name' from the module having the given 'modname', with 'module'
2.475 - having been obtained for the module name, using 'alias' for the imported
2.476 - name in the current namespace.
2.477 - """
2.478 -
2.479 - path = self.get_namespace_path()
2.480 -
2.481 - if module and module.get_global(name):
2.482 - value = module.get_global(name)
2.483 -
2.484 - # Warn about an import that fails to provide a name, perhaps due
2.485 - # to a circular import.
2.486 -
2.487 - if not value:
2.488 - print >>sys.stderr, "In %s, from statement cannot import %s from %s%s." % (
2.489 - path, name, modname, not module.loaded and "(circular import)")
2.490 -
2.491 - return value
2.492 -
2.493 - # Record the name as a dependency.
2.494 -
2.495 - else:
2.496 - return Reference("<depends>", "%s.%s" % (modname, name))
2.497
2.498 def process_function_node(self, n, name):
2.499
2.500 @@ -1031,16 +637,13 @@
2.501 # Load the mentioned module.
2.502
2.503 for name, alias in n.names:
2.504 - module, leaf_module = self.get_module(name, alias)
2.505 + if name == self.name:
2.506 + raise InspectError("Cannot import the current module.", path, n)
2.507 + if not alias and len(n.names) > 1:
2.508 + raise InspectError("Imported modules must be aliased unless a simple module is imported.", path, n)
2.509
2.510 - if not module:
2.511 - print >>sys.stderr, "In %s, import statement importing from %s failed." % (
2.512 - path, name)
2.513 - if module and not module.loaded:
2.514 - print >>sys.stderr, "In %s, import statement performs circular import of %s." % (
2.515 - path, name)
2.516 -
2.517 - self.set_module(alias or name.split(".")[0], module, leaf_module)
2.518 + self.set_module(alias or name.split(".")[0], name)
2.519 + self.importer.queue_module(name, self)
2.520
2.521 def process_invocation_node(self, n):
2.522
2.523 @@ -1124,22 +727,12 @@
2.524
2.525 op = n.name[len("$op"):]
2.526
2.527 - # Access the operator module.
2.528 -
2.529 - top, module = self.get_module("operator", True)
2.530 - self.set_module(None, module, hidden=True)
2.531 -
2.532 - # Link the operation to the operator module definition in this
2.533 - # module.
2.534 -
2.535 - self.set_imported_name(op, "operator", n.name, self.name)
2.536 -
2.537 # Attempt to get a reference.
2.538
2.539 - ref = self.import_name_from_module(op, "operator", module)
2.540 - ref = self.get_object("operator.%s" % op) or ref
2.541 + ref = self.import_name_from_module(op, "operator")
2.542
2.543 # Record the imported name and provide the resolved name reference.
2.544 + # NOTE: Maybe use a different class.
2.545
2.546 value = ResolvedNameRef(n.name, ref)
2.547 self.set_special(n.name, value)
2.548 @@ -1491,29 +1084,15 @@
2.549 init_item(self.names_used, path, set)
2.550 self.names_used[path].add(name)
2.551
2.552 - def set_module(self, name, module, leaf_module=None, hidden=False):
2.553 + def set_module(self, name, module_name):
2.554
2.555 """
2.556 - Set a module in the current namespace using the given 'name' and
2.557 - corresponding 'module' object, with the 'leaf_module' being recorded
2.558 - if different. If 'hidden' is a true value, the modules are recorded as
2.559 - not necessarily being exposed by this module. This module is, however,
2.560 - recorded as accessing the given modules and is thus dependent on them.
2.561 + Set a module in the current namespace using the given 'name' associated
2.562 + with the corresponding 'module_name'.
2.563 """
2.564
2.565 if name:
2.566 - self.set_general_local(name, module and Reference("<module>", module.name) or None)
2.567 - if module:
2.568 - if hidden:
2.569 - self.imported_hidden.add(module)
2.570 - if leaf_module and leaf_module is not module:
2.571 - self.imported_hidden.add(leaf_module)
2.572 - else:
2.573 - self.imported.add(module)
2.574 - module.accessing_modules.add(self.name)
2.575 - if leaf_module and leaf_module is not module:
2.576 - self.imported.add(leaf_module)
2.577 - leaf_module.accessing_modules.add(self.name)
2.578 + self.set_general_local(name, Reference("<module>", module_name))
2.579
2.580 def set_definition(self, name, kind):
2.581
2.582 @@ -1688,16 +1267,16 @@
2.583
2.584 "Return a constant reference for the given type 'name' and 'value'."
2.585
2.586 - ref = self.get_literal_builtin(name)
2.587 + ref = self.get_builtin_class(name)
2.588 return self.get_constant_reference(ref, value)
2.589
2.590 def get_literal_instance(self, n, name):
2.591
2.592 "For node 'n', return a reference to an instance of 'name'."
2.593
2.594 - # Get a class reference.
2.595 + # Get a reference to the built-in class.
2.596
2.597 - ref = self.get_literal_builtin(name)
2.598 + ref = self.get_builtin_class(name)
2.599
2.600 # Obtain the details of the literal itself.
2.601 # An alias to the type is generated for sequences.
2.602 @@ -1711,31 +1290,6 @@
2.603 else:
2.604 return self.get_constant_reference(ref, n.value)
2.605
2.606 - def get_literal_builtin(self, name):
2.607 -
2.608 - "Return a reference for a built-in literal type of the given 'name'."
2.609 -
2.610 - ref = self.get_builtin(name)
2.611 - true_origin = "__builtins__.%s.%s" % (name, name)
2.612 - exposed_origin = "__builtins__.%s" % name
2.613 -
2.614 - # Obtain fully-imported built-in class references.
2.615 -
2.616 - if ref and ref.has_kind("<class>"):
2.617 - pass
2.618 -
2.619 - # Early-stage references need explicit references.
2.620 -
2.621 - elif ref:
2.622 - ref = Reference("<class>", true_origin)
2.623 -
2.624 - # Otherwise, the normal locations can be used.
2.625 -
2.626 - else:
2.627 - ref = Reference("<class>", true_origin, exposed_origin)
2.628 -
2.629 - return ref
2.630 -
2.631 # Functions and invocations.
2.632
2.633 def allocate_arguments(self, path, args):
3.1 --- a/modules.py Sat Sep 03 22:38:21 2016 +0200
3.2 +++ b/modules.py Sat Sep 03 22:43:44 2016 +0200
3.3 @@ -20,9 +20,10 @@
3.4 this program. If not, see <http://www.gnu.org/licenses/>.
3.5 """
3.6
3.7 -from common import *
3.8 +from common import CommonModule
3.9 from encoders import decode_modifier_term, encode_modifiers, encode_usage
3.10 from referencing import decode_reference, Reference
3.11 +from results import ResolvedNameRef
3.12 import sys
3.13
3.14 class BasicModule(CommonModule):
3.15 @@ -32,18 +33,6 @@
3.16 def __init__(self, name, importer):
3.17 CommonModule.__init__(self, name, importer)
3.18
3.19 - # Import machinery links.
3.20 -
3.21 - self.loaded = False
3.22 -
3.23 - # Module dependencies.
3.24 -
3.25 - self.imported = set()
3.26 - self.imported_hidden = set()
3.27 - self.imported_names = {}
3.28 - self.revealed = set()
3.29 - self.accessing_modules = set()
3.30 -
3.31 # Global name information.
3.32
3.33 self.objects = {}
3.34 @@ -64,7 +53,6 @@
3.35
3.36 self.names_used = {}
3.37 self.names_missing = {}
3.38 - self.name_references = {} # references to globals
3.39
3.40 # Function details.
3.41
3.42 @@ -96,31 +84,9 @@
3.43
3.44 self.attr_access_modifiers = {}
3.45
3.46 - # Initialisation-related details.
3.47 -
3.48 - self.initialised_names = {}
3.49 - self.aliased_names = {}
3.50 -
3.51 def __repr__(self):
3.52 return "BasicModule(%r, %r)" % (self.name, self.importer)
3.53
3.54 - def resolve(self):
3.55 -
3.56 - "Resolve dependencies and complete definitions."
3.57 -
3.58 - self.resolve_class_bases()
3.59 - self.check_special()
3.60 - self.check_names_used()
3.61 - self.resolve_members()
3.62 - self.resolve_initialisers()
3.63 - self.resolve_literals()
3.64 - self.remove_redundant_accessors()
3.65 - self.set_invocation_usage()
3.66 -
3.67 - # Propagate to the importer information needed in subsequent activities.
3.68 -
3.69 - self.propagate()
3.70 -
3.71 # Derived information methods.
3.72
3.73 def propagate(self):
3.74 @@ -176,36 +142,6 @@
3.75 if ref.get_kind() != "<module>":
3.76 del self.importer.objects[name]
3.77
3.78 - def resolve_class_bases(self):
3.79 -
3.80 - "Resolve all class bases since some of them may have been deferred."
3.81 -
3.82 - for name, bases in self.classes.items():
3.83 - resolved = []
3.84 - bad = []
3.85 -
3.86 - for base in bases:
3.87 -
3.88 - # Resolve dependencies.
3.89 -
3.90 - if base.has_kind("<depends>"):
3.91 - ref = self.importer.get_object(base.get_origin())
3.92 - else:
3.93 - ref = base
3.94 -
3.95 - # Obtain the origin of the base class reference.
3.96 -
3.97 - if not ref or not ref.has_kind("<class>"):
3.98 - bad.append(base)
3.99 - break
3.100 -
3.101 - resolved.append(ref)
3.102 -
3.103 - if bad:
3.104 - print >>sys.stderr, "Bases of class %s were not classes." % (name, ", ".join(map(str, bad)))
3.105 - else:
3.106 - self.importer.classes[name] = self.classes[name] = resolved
3.107 -
3.108 def propagate_attrs(self):
3.109
3.110 "Derive attributes from the class and module member details."
3.111 @@ -268,20 +204,6 @@
3.112 path = self.get_namespace_path()
3.113 return name in self.scope_globals.get(path, [])
3.114
3.115 - def get_globals(self):
3.116 -
3.117 - """
3.118 - Get the globals from this module, returning a dictionary mapping names
3.119 - to references incorporating original definition details.
3.120 - """
3.121 -
3.122 - l = []
3.123 - for name, value in self.objects.items():
3.124 - parent, attrname = name.rsplit(".", 1)
3.125 - if parent == self.name:
3.126 - l.append((attrname, value))
3.127 - return dict(l)
3.128 -
3.129 def get_global(self, name):
3.130
3.131 """
3.132 @@ -297,57 +219,38 @@
3.133 def get_global_or_builtin(self, name):
3.134
3.135 """
3.136 - Return the object recorded the given 'name' from this module or from the
3.137 - __builtins__ module. If no object has yet been defined, perhaps due to
3.138 - circular module references, None is returned.
3.139 + Return a reference for the given 'name' found in this module or in the
3.140 + __builtins__.
3.141 """
3.142
3.143 return self.get_global(name) or self.get_builtin(name)
3.144
3.145 def get_builtin(self, name):
3.146
3.147 - "Return the object providing the given built-in 'name'."
3.148 -
3.149 - path = "__builtins__.%s" % name
3.150 -
3.151 - # Obtain __builtins__.
3.152 + "Return a reference to the built-in with the given 'name'."
3.153
3.154 - module = self.ensure_builtins(path)
3.155 -
3.156 - # Attempt to find the named object within __builtins__.
3.157 -
3.158 - if module:
3.159 - self.find_imported_name(name, module.name, module)
3.160 + self.importer.queue_module("__builtins__", self)
3.161 + return Reference("<depends>", "__builtins__.%s" % name)
3.162
3.163 - # Return the path and any reference for the named object.
3.164 + def get_builtin_class(self, name):
3.165
3.166 - return self.importer.get_object(path)
3.167 -
3.168 - def ensure_builtins(self, path):
3.169 + "Return a reference to the actual object providing 'name'."
3.170
3.171 - """
3.172 - If 'path' is a reference to an object within __builtins__, return the
3.173 - __builtins__ module.
3.174 - """
3.175 + # NOTE: This makes assumptions about the __builtins__ structure.
3.176
3.177 - if path.split(".", 1)[0] == "__builtins__":
3.178 - return self.importer.load("__builtins__", True, True)
3.179 - else:
3.180 - return None
3.181 + return Reference("<class>", "__builtins__.%s.%s" % (name, name))
3.182
3.183 def get_object(self, path):
3.184
3.185 """
3.186 - Get the details of an object with the given 'path', either from this
3.187 - module or from the whole program. Return a tuple containing the path and
3.188 - any object found.
3.189 + Get the details of an object with the given 'path'. Where the object
3.190 + cannot be resolved, an unresolved reference is returned.
3.191 """
3.192
3.193 if self.objects.has_key(path):
3.194 return self.objects[path]
3.195 else:
3.196 - self.ensure_builtins(path)
3.197 - return self.importer.get_object(path)
3.198 + return Reference("<depends>", path)
3.199
3.200 def set_object(self, name, value=None):
3.201
3.202 @@ -357,6 +260,14 @@
3.203 multiple = self.objects.has_key(name) and self.objects[name].get_kind() != ref.get_kind()
3.204 self.importer.objects[name] = self.objects[name] = multiple and ref.as_var() or ref
3.205
3.206 + def import_name_from_module(self, name, module_name):
3.207 +
3.208 + "Import 'name' from the module having the given 'module_name'."
3.209 +
3.210 + if module_name != self.name:
3.211 + self.importer.queue_module(module_name, self)
3.212 + return Reference("<depends>", "%s.%s" % (module_name, name))
3.213 +
3.214 # Special names.
3.215
3.216 def get_special(self, name):
3.217 @@ -385,160 +296,6 @@
3.218 value = ResolvedNameRef(literal_name, ref)
3.219 self.set_special(literal_name, value)
3.220
3.221 - # Revealing modules by tracking name imports across modules.
3.222 -
3.223 - def set_imported_name(self, name, module_name, alias=None, path=None):
3.224 -
3.225 - """
3.226 - Record 'name' as being imported from the given 'module_name', employing
3.227 - the given 'alias' in the local namespace if specified.
3.228 - """
3.229 -
3.230 - path = path or self.get_namespace_path()
3.231 - init_item(self.imported_names, path, dict)
3.232 - self.imported_names[path][alias or name] = (name, module_name)
3.233 -
3.234 - def get_imported_name(self, name, path):
3.235 -
3.236 - "Get details of any imported 'name' within the namespace at 'path'."
3.237 -
3.238 - if self.imported_names.has_key(path):
3.239 - return self.imported_names[path].get(name)
3.240 - else:
3.241 - return None
3.242 -
3.243 - def find_imported_name(self, name, path, module=None):
3.244 -
3.245 - """
3.246 - Find details of the imported 'name' within the namespace at 'path',
3.247 - starting within the given 'module' if indicated, or within this module
3.248 - otherwise.
3.249 - """
3.250 -
3.251 - module = module or self
3.252 -
3.253 - # Obtain any module required by the name.
3.254 -
3.255 - name_modname = module.get_imported_name(name, path)
3.256 -
3.257 - if name_modname:
3.258 - name, modname = name_modname
3.259 - module = self._find_imported_name(name, modname, module)
3.260 -
3.261 - # Obtain the name from the final module, revealing it if appropriate.
3.262 -
3.263 - if module:
3.264 - init_item(self.importer.revealing, module.name, set)
3.265 - self.importer.set_revealing(module, name, self)
3.266 -
3.267 - def _find_imported_name(self, name, modname, module):
3.268 -
3.269 - """
3.270 - Traverse details for 'name' via 'modname' to the module providing the
3.271 - name, tentatively revealing the module even if the module is not yet
3.272 - loaded and cannot provide the details of the object recorded for the
3.273 - name.
3.274 - """
3.275 -
3.276 - _name = name
3.277 -
3.278 - # Obtain any modules referenced by each required module.
3.279 -
3.280 - while True:
3.281 -
3.282 - # Get the module directly or traverse possibly-aliased names.
3.283 -
3.284 - module = self.get_module_direct(modname, True)
3.285 - if not module:
3.286 - top, module = self.get_module(modname, True)
3.287 - name_modname = module.get_imported_name(_name, module.name)
3.288 - if not name_modname:
3.289 - break
3.290 - else:
3.291 - _name, modname = name_modname
3.292 -
3.293 - return module
3.294 -
3.295 - def reveal_referenced(self):
3.296 -
3.297 - """
3.298 - Reveal modules referenced by this module.
3.299 - """
3.300 -
3.301 - for path, names in self.imported_names.items():
3.302 - for alias, (name, modname) in names.items():
3.303 - module = self._find_imported_name(name, modname, self)
3.304 - self.reveal_module(module)
3.305 -
3.306 - def reveal_module(self, module):
3.307 -
3.308 - """
3.309 - Reveal the given 'module', recording the revealed modules on this
3.310 - module.
3.311 - """
3.312 -
3.313 - if module is not self:
3.314 - self.importer.reveal_module(module)
3.315 - self.revealed.add(module)
3.316 - module.accessing_modules.add(self.name)
3.317 -
3.318 - # Module loading.
3.319 -
3.320 - def get_module_direct(self, modname, hidden=False):
3.321 -
3.322 - """
3.323 - Return 'modname' without traversing parent modules, keeping the module
3.324 - 'hidden' if set to a true value, loading the module if not already
3.325 - loaded.
3.326 - """
3.327 -
3.328 - return self.importer.get_module(modname, True) or self.importer.load(modname, hidden=hidden)
3.329 -
3.330 - def get_module(self, name, hidden=False):
3.331 -
3.332 - """
3.333 - Use the given 'name' to obtain the identity of a module. Return module
3.334 - objects or None if the module cannot be found. This method is required
3.335 - when aliases are used to refer to modules and where a module "path" does
3.336 - not correspond to the actual module path.
3.337 -
3.338 - A tuple is returned containing the top or base module and the deepest or
3.339 - leaf module involved.
3.340 - """
3.341 -
3.342 - path_so_far = []
3.343 - top = module = None
3.344 - parts = name.split(".")
3.345 -
3.346 - for i, part in enumerate(parts):
3.347 - path_so_far.append(part)
3.348 - module_name = ".".join(path_so_far)
3.349 - ref = self.get_object(module_name)
3.350 -
3.351 - # If no known object exists, attempt to load it.
3.352 -
3.353 - if not ref:
3.354 - module = self.importer.load(module_name, True, hidden)
3.355 - if not module:
3.356 - return None
3.357 -
3.358 - # Rewrite the path to use the actual module details.
3.359 -
3.360 - path_so_far = module.name.split(".")
3.361 -
3.362 - # If the object exists and is not a module, stop.
3.363 -
3.364 - elif ref.has_kind(["<class>", "<function>", "<var>"]):
3.365 - return None
3.366 -
3.367 - else:
3.368 - module = self.importer.get_module(ref.get_origin(), hidden)
3.369 -
3.370 - if not top:
3.371 - top = module
3.372 -
3.373 - return top, module
3.374 -
3.375 class CachedModule(BasicModule):
3.376
3.377 "A cached module."
3.378 @@ -546,9 +303,6 @@
3.379 def __repr__(self):
3.380 return "CachedModule(%r, %r)" % (self.name, self.importer)
3.381
3.382 - def resolve(self):
3.383 - pass
3.384 -
3.385 def to_cache(self, filename):
3.386
3.387 "Not actually writing the module back to 'filename'."
3.388 @@ -566,27 +320,8 @@
3.389 try:
3.390 self.filename = f.readline().rstrip()
3.391
3.392 - accessing_modules = f.readline().split(": ", 1)[-1].rstrip()
3.393 -
3.394 - module_names = f.readline().split(": ", 1)[-1].rstrip()
3.395 - if module_names:
3.396 - for module_name in module_names.split(", "):
3.397 - self.imported.add(self.importer.load(module_name))
3.398 -
3.399 - module_names = f.readline().split(": ", 1)[-1].rstrip()
3.400 - if module_names:
3.401 - for module_name in module_names.split(", "):
3.402 - self.imported_hidden.add(self.importer.load(module_name, hidden=True))
3.403 -
3.404 - module_names = f.readline().split(": ", 1)[-1].rstrip()
3.405 - if module_names:
3.406 - for module_name in module_names.split(", "):
3.407 - module = self.importer.load(module_name, True, True)
3.408 - self.reveal_module(module)
3.409 -
3.410 f.readline() # (empty line)
3.411
3.412 - self._get_imported_names(f)
3.413 self._get_members(f)
3.414 self._get_class_relationships(f)
3.415 self._get_instance_attrs(f)
3.416 @@ -613,20 +348,9 @@
3.417 finally:
3.418 f.close()
3.419
3.420 - self.loaded = True
3.421 -
3.422 - def resolve(self):
3.423 + def complete(self):
3.424 self.propagate()
3.425
3.426 - def _get_imported_names(self, f):
3.427 - f.readline() # "imported names:"
3.428 - line = f.readline().rstrip()
3.429 - while line:
3.430 - path, alias_or_name, name, module_name = self._get_fields(line, 4)
3.431 - init_item(self.imported_names, path, dict)
3.432 - self.imported_names[path][alias_or_name] = (name, module_name)
3.433 - line = f.readline().rstrip()
3.434 -
3.435 def _get_members(self, f):
3.436 f.readline() # "members:"
3.437 line = f.readline().rstrip()
3.438 @@ -885,13 +609,6 @@
3.439 format to the file having the given 'filename':
3.440
3.441 filename
3.442 - "accessed by:" accessing module names during this module's import
3.443 - "imports:" imported module names
3.444 - "hidden imports:" imported module names
3.445 - "reveals:" revealed module names
3.446 - (empty line)
3.447 - "imported names:"
3.448 - zero or more: qualified name " " name " " module name
3.449 (empty line)
3.450 "members:"
3.451 zero or more: qualified name " " reference
3.452 @@ -988,33 +705,6 @@
3.453 try:
3.454 print >>f, self.filename
3.455
3.456 - accessing_modules = list(self.accessing_modules)
3.457 - accessing_modules.sort()
3.458 - print >>f, "accessed by:", ", ".join(accessing_modules)
3.459 -
3.460 - module_names = [m.name for m in self.imported]
3.461 - module_names.sort()
3.462 - print >>f, "imports:", ", ".join(module_names)
3.463 -
3.464 - module_names = [m.name for m in self.imported_hidden]
3.465 - module_names.sort()
3.466 - print >>f, "hidden imports:", ", ".join(module_names)
3.467 -
3.468 - module_names = [m.name for m in self.revealed]
3.469 - module_names.sort()
3.470 - print >>f, "reveals:", ", ".join(module_names)
3.471 -
3.472 - print >>f
3.473 - print >>f, "imported names:"
3.474 - paths = self.imported_names.keys()
3.475 - paths.sort()
3.476 - for path in paths:
3.477 - names = self.imported_names[path].keys()
3.478 - names.sort()
3.479 - for alias_or_name in names:
3.480 - name, modname = self.imported_names[path][alias_or_name]
3.481 - print >>f, path, alias_or_name, name, modname
3.482 -
3.483 print >>f
3.484 print >>f, "members:"
3.485 objects = self.objects.keys()
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/resolving.py Sat Sep 03 22:43:44 2016 +0200
4.3 @@ -0,0 +1,409 @@
4.4 +#!/usr/bin/env python
4.5 +
4.6 +"""
4.7 +Name resolution.
4.8 +
4.9 +Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>
4.10 +
4.11 +This program is free software; you can redistribute it and/or modify it under
4.12 +the terms of the GNU General Public License as published by the Free Software
4.13 +Foundation; either version 3 of the License, or (at your option) any later
4.14 +version.
4.15 +
4.16 +This program is distributed in the hope that it will be useful, but WITHOUT
4.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
4.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
4.19 +details.
4.20 +
4.21 +You should have received a copy of the GNU General Public License along with
4.22 +this program. If not, see <http://www.gnu.org/licenses/>.
4.23 +"""
4.24 +
4.25 +from common import init_item, predefined_constants
4.26 +from results import AccessRef, InstanceRef, InvocationRef, LocalNameRef, \
4.27 + NameRef, ResolvedNameRef
4.28 +from referencing import Reference
4.29 +import sys
4.30 +
4.31 +class NameResolving:
4.32 +
4.33 + "Resolving names mix-in for inspected modules."
4.34 +
4.35 + def __init__(self):
4.36 + self.name_references = {} # references to globals
4.37 +
4.38 + # Initialisation-related details.
4.39 +
4.40 + self.initialised_names = {}
4.41 + self.aliased_names = {}
4.42 +
4.43 + # Object resolution.
4.44 +
4.45 + def resolve_object(self, ref):
4.46 +
4.47 + """
4.48 + Return the given 'ref' in resolved form, given knowledge of the entire
4.49 + program.
4.50 + """
4.51 +
4.52 + if ref.has_kind("<depends>"):
4.53 + return self.importer.get_object(ref.get_origin())
4.54 + else:
4.55 + return ref
4.56 +
4.57 + def get_resolved_object(self, path):
4.58 +
4.59 + """
4.60 + Get the details of an object with the given 'path'. Where the object
4.61 + has not been resolved, None is returned. This differs from the
4.62 + get_object method used elsewhere in that it does not return an
4.63 + unresolved object reference.
4.64 + """
4.65 +
4.66 + if self.objects.has_key(path):
4.67 + ref = self.objects[path]
4.68 + if ref.has_kind("<depends>"):
4.69 + return None
4.70 + else:
4.71 + return ref
4.72 + else:
4.73 + return None
4.74 +
4.75 + # Post-inspection resolution activities.
4.76 +
4.77 + def resolve(self):
4.78 +
4.79 + "Resolve dependencies and complete definitions."
4.80 +
4.81 + self.resolve_members()
4.82 + self.resolve_class_bases()
4.83 + #self.check_special()
4.84 + self.check_names_used()
4.85 + self.resolve_initialisers()
4.86 + self.resolve_literals()
4.87 + self.remove_redundant_accessors()
4.88 +
4.89 + def resolve_members(self):
4.90 +
4.91 + "Resolve any members referring to deferred references."
4.92 +
4.93 + for name, ref in self.objects.items():
4.94 + if ref.has_kind("<depends>"):
4.95 + ref = self.importer.get_object(name)
4.96 + ref = ref.alias(name)
4.97 + self.importer.objects[name] = self.objects[name] = ref
4.98 +
4.99 + def resolve_class_bases(self):
4.100 +
4.101 + "Resolve all class bases since some of them may have been deferred."
4.102 +
4.103 + for name, bases in self.classes.items():
4.104 + resolved = []
4.105 + bad = []
4.106 +
4.107 + for base in bases:
4.108 + ref = self.resolve_object(base)
4.109 +
4.110 + # Obtain the origin of the base class reference.
4.111 +
4.112 + if not ref or not ref.has_kind("<class>"):
4.113 + bad.append(base)
4.114 + break
4.115 +
4.116 + resolved.append(ref)
4.117 +
4.118 + if bad:
4.119 + print >>sys.stderr, "Bases of class %s were not classes." % (name, ", ".join(map(str, bad)))
4.120 + else:
4.121 + self.importer.classes[name] = self.classes[name] = resolved
4.122 +
4.123 + def check_special(self):
4.124 +
4.125 + "Check special names."
4.126 +
4.127 + for name, value in self.special.items():
4.128 + # NOTE: Needs to handle Ref classes.
4.129 + self.special[name] = self.get_resolved_object(value.get_origin())
4.130 +
4.131 + def check_names_used(self):
4.132 +
4.133 + "Check the names used by each function."
4.134 +
4.135 + for path in self.names_used.keys():
4.136 + self.check_names_used_for_path(path)
4.137 +
4.138 + def check_names_used_for_path(self, path):
4.139 +
4.140 + "Check the names used by the given 'path'."
4.141 +
4.142 + names = self.names_used.get(path)
4.143 + if not names:
4.144 + return
4.145 +
4.146 + in_function = self.function_locals.has_key(path)
4.147 +
4.148 + for name in names:
4.149 + if name in predefined_constants or in_function and name in self.function_locals[path]:
4.150 + continue
4.151 +
4.152 + # Find local definitions (within static namespaces).
4.153 +
4.154 + key = "%s.%s" % (path, name)
4.155 + ref = self.get_resolved_object(key)
4.156 + if ref:
4.157 + self.name_references[key] = ref.final() or key
4.158 + self.resolve_accesses(path, name, ref)
4.159 + continue
4.160 +
4.161 + # Find global or built-in definitions.
4.162 +
4.163 + ref = self.get_global_or_builtin(name)
4.164 + objpath = ref and (ref.final() or ref.get_name())
4.165 + if objpath:
4.166 + self.name_references[key] = objpath
4.167 + self.resolve_accesses(path, name, ref)
4.168 + continue
4.169 +
4.170 + print >>sys.stderr, "Name not recognised: %s in %s" % (name, path)
4.171 + init_item(self.names_missing, path, set)
4.172 + self.names_missing[path].add(name)
4.173 +
4.174 + def resolve_accesses(self, path, name, ref):
4.175 +
4.176 + """
4.177 + Resolve any unresolved accesses in the function at the given 'path'
4.178 + for the given 'name' corresponding to the indicated 'ref'. Note that
4.179 + this mechanism cannot resolve things like inherited methods because
4.180 + they are not recorded as program objects in their inherited locations.
4.181 + """
4.182 +
4.183 + attr_accesses = self.global_attr_accesses.get(path)
4.184 + all_attrnames = attr_accesses and attr_accesses.get(name)
4.185 +
4.186 + if not all_attrnames:
4.187 + return
4.188 +
4.189 + # Insist on constant accessors.
4.190 +
4.191 + if not ref.has_kind(["<class>", "<module>"]):
4.192 + return
4.193 +
4.194 + found_attrnames = set()
4.195 +
4.196 + for attrnames in all_attrnames:
4.197 +
4.198 + # Start with the resolved name, adding attributes.
4.199 +
4.200 + attrs = ref.get_path()
4.201 + remaining = attrnames.split(".")
4.202 + last_ref = ref
4.203 +
4.204 + # Add each component, testing for a constant object.
4.205 +
4.206 + while remaining:
4.207 + attrname = remaining[0]
4.208 + attrs.append(attrname)
4.209 + del remaining[0]
4.210 +
4.211 + # Find any constant object reference.
4.212 +
4.213 + attr_ref = self.get_resolved_object(".".join(attrs))
4.214 +
4.215 + # Non-constant accessors terminate the traversal.
4.216 +
4.217 + if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]):
4.218 +
4.219 + # Provide the furthest constant accessor unless the final
4.220 + # access can be resolved.
4.221 +
4.222 + if remaining:
4.223 + remaining.insert(0, attrs.pop())
4.224 + else:
4.225 + last_ref = attr_ref
4.226 + break
4.227 +
4.228 + # Follow any reference to a constant object.
4.229 + # Where the given name refers to an object in another location,
4.230 + # switch to the other location in order to be able to test its
4.231 + # attributes.
4.232 +
4.233 + last_ref = attr_ref
4.234 + attrs = attr_ref.get_path()
4.235 +
4.236 + # Record a constant accessor only if an object was found
4.237 + # that is different from the namespace involved.
4.238 +
4.239 + if last_ref:
4.240 + objpath = ".".join(attrs)
4.241 + if objpath != path:
4.242 +
4.243 + # Establish a constant access.
4.244 +
4.245 + init_item(self.const_accesses, path, dict)
4.246 + self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))
4.247 +
4.248 + if len(attrs) > 1:
4.249 + found_attrnames.add(attrs[1])
4.250 +
4.251 + # Remove any usage records for the name.
4.252 +
4.253 + if found_attrnames:
4.254 +
4.255 + # NOTE: Should be only one name version.
4.256 +
4.257 + versions = []
4.258 + for version in self.attr_usage[path][name]:
4.259 + new_usage = set()
4.260 + for usage in version:
4.261 + if found_attrnames.intersection(usage):
4.262 + new_usage.add(tuple(set(usage).difference(found_attrnames)))
4.263 + else:
4.264 + new_usage.add(usage)
4.265 + versions.append(new_usage)
4.266 +
4.267 + self.attr_usage[path][name] = versions
4.268 +
4.269 + def resolve_initialisers(self):
4.270 +
4.271 + "Resolve initialiser values for names."
4.272 +
4.273 + # Get the initialisers in each namespace.
4.274 +
4.275 + for path, name_initialisers in self.name_initialisers.items():
4.276 + const_accesses = self.const_accesses.get(path)
4.277 +
4.278 + # Resolve values for each name in a scope.
4.279 +
4.280 + for name, values in name_initialisers.items():
4.281 + if path == self.name:
4.282 + assigned_path = name
4.283 + else:
4.284 + assigned_path = "%s.%s" % (path, name)
4.285 +
4.286 + initialised_names = {}
4.287 + aliased_names = {}
4.288 +
4.289 + for i, name_ref in enumerate(values):
4.290 +
4.291 + # Unwrap invocations.
4.292 +
4.293 + if isinstance(name_ref, InvocationRef):
4.294 + invocation = True
4.295 + name_ref = name_ref.name_ref
4.296 + else:
4.297 + invocation = False
4.298 +
4.299 + # Obtain a usable reference from names or constants.
4.300 +
4.301 + if isinstance(name_ref, ResolvedNameRef):
4.302 + if not name_ref.reference():
4.303 + continue
4.304 + ref = name_ref.reference()
4.305 +
4.306 + # Obtain a reference from instances.
4.307 +
4.308 + elif isinstance(name_ref, InstanceRef):
4.309 + if not name_ref.reference():
4.310 + continue
4.311 + ref = name_ref.reference()
4.312 +
4.313 + # Resolve accesses that employ constants.
4.314 +
4.315 + elif isinstance(name_ref, AccessRef):
4.316 + ref = None
4.317 +
4.318 + if const_accesses:
4.319 + resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
4.320 + if resolved_access:
4.321 + objpath, ref, remaining_attrnames = resolved_access
4.322 + if remaining_attrnames:
4.323 + ref = None
4.324 +
4.325 + # Accesses that do not employ constants cannot be resolved,
4.326 + # but they may be resolvable later.
4.327 +
4.328 + if not ref:
4.329 + if not invocation:
4.330 + aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number
4.331 + continue
4.332 +
4.333 + # Attempt to resolve a plain name reference.
4.334 +
4.335 + elif isinstance(name_ref, LocalNameRef):
4.336 + key = "%s.%s" % (path, name_ref.name)
4.337 + origin = self.name_references.get(key)
4.338 +
4.339 + # Accesses that do not refer to known static objects
4.340 + # cannot be resolved, but they may be resolvable later.
4.341 +
4.342 + if not origin:
4.343 + if not invocation:
4.344 + aliased_names[i] = name_ref.name, None, name_ref.number
4.345 + continue
4.346 +
4.347 + ref = self.get_resolved_object(origin)
4.348 + if not ref:
4.349 + continue
4.350 +
4.351 + elif isinstance(name_ref, NameRef):
4.352 + key = "%s.%s" % (path, name_ref.name)
4.353 + origin = self.name_references.get(key)
4.354 + if not origin:
4.355 + continue
4.356 +
4.357 + ref = self.get_resolved_object(origin)
4.358 + if not ref:
4.359 + continue
4.360 +
4.361 + else:
4.362 + continue
4.363 +
4.364 + # Convert class invocations to instances.
4.365 +
4.366 + if invocation:
4.367 + ref = ref.has_kind("<class>") and ref.instance_of() or None
4.368 +
4.369 + if ref:
4.370 + initialised_names[i] = ref
4.371 +
4.372 + if initialised_names:
4.373 + self.initialised_names[assigned_path] = initialised_names
4.374 + if aliased_names:
4.375 + self.aliased_names[assigned_path] = aliased_names
4.376 +
4.377 + def resolve_literals(self):
4.378 +
4.379 + "Resolve constant value types."
4.380 +
4.381 + # Get the constants defined in each namespace.
4.382 +
4.383 + for path, constants in self.constants.items():
4.384 + for constant, n in constants.items():
4.385 + objpath = "%s.$c%d" % (path, n)
4.386 + _constant, value_type = self.constant_values[objpath]
4.387 + self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
4.388 +
4.389 + # Get the literals defined in each namespace.
4.390 +
4.391 + for path, literals in self.literals.items():
4.392 + for n in range(0, literals):
4.393 + objpath = "%s.$C%d" % (path, n)
4.394 + value_type = self.literal_types[objpath]
4.395 + self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
4.396 +
4.397 + def remove_redundant_accessors(self):
4.398 +
4.399 + "Remove now-redundant modifier and accessor information."
4.400 +
4.401 + for path, const_accesses in self.const_accesses.items():
4.402 + accesses = self.attr_accessors.get(path)
4.403 + modifiers = self.attr_access_modifiers.get(path)
4.404 + if not accesses:
4.405 + continue
4.406 + for access in const_accesses.keys():
4.407 + if accesses.has_key(access):
4.408 + del accesses[access]
4.409 + if modifiers and modifiers.has_key(access):
4.410 + del modifiers[access]
4.411 +
4.412 +# vim: tabstop=4 expandtab shiftwidth=4