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