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 ref = self.importer.identify(ref.get_origin()) 128 return ref and ref.has_kind("<class>") and ref.instance_of() or Reference("<var>") 129 130 def resolve_accesses(self, path, name, ref): 131 132 """ 133 Resolve any unresolved accesses in the function at the given 'path' 134 for the given 'name' corresponding to the indicated 'ref'. Note that 135 this mechanism cannot resolve things like inherited methods because 136 they are not recorded as program objects in their inherited locations. 137 """ 138 139 attr_accesses = self.global_attr_accesses.get(path) 140 all_attrnames = attr_accesses and attr_accesses.get(name) 141 142 if not all_attrnames: 143 return 144 145 # Insist on constant accessors. 146 147 if not ref.has_kind(["<class>", "<module>"]): 148 return 149 150 found_attrnames = set() 151 152 for attrnames in all_attrnames: 153 154 # Start with the resolved name, adding attributes. 155 156 attrs = ref.get_path() 157 remaining = attrnames.split(".") 158 last_ref = ref 159 160 # Add each component, testing for a constant object. 161 162 while remaining: 163 attrname = remaining[0] 164 attrs.append(attrname) 165 del remaining[0] 166 167 # Find any constant object reference. 168 169 attr_ref = self.get_resolved_object(".".join(attrs)) 170 171 # Non-constant accessors terminate the traversal. 172 173 if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]): 174 175 # Provide the furthest constant accessor unless the final 176 # access can be resolved. 177 178 if remaining: 179 remaining.insert(0, attrs.pop()) 180 else: 181 last_ref = attr_ref 182 break 183 184 # Follow any reference to a constant object. 185 # Where the given name refers to an object in another location, 186 # switch to the other location in order to be able to test its 187 # attributes. 188 189 last_ref = attr_ref 190 attrs = attr_ref.get_path() 191 192 # Record a constant accessor only if an object was found 193 # that is different from the namespace involved. 194 195 if last_ref: 196 objpath = ".".join(attrs) 197 if objpath != path: 198 199 if last_ref.has_kind("<invoke>"): 200 last_ref = self.convert_invocation(last_ref) 201 202 # Establish a constant access. 203 204 init_item(self.const_accesses, path, dict) 205 self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining)) 206 207 if len(attrs) > 1: 208 found_attrnames.add(attrs[1]) 209 210 # Remove any usage records for the name. 211 212 if found_attrnames: 213 214 # NOTE: Should be only one name version. 215 216 versions = [] 217 for version in self.attr_usage[path][name]: 218 new_usage = set() 219 for usage in version: 220 if found_attrnames.intersection(usage): 221 new_usage.add(tuple(set(usage).difference(found_attrnames))) 222 else: 223 new_usage.add(usage) 224 versions.append(new_usage) 225 226 self.attr_usage[path][name] = versions 227 228 def resolve_initialisers(self): 229 230 "Resolve initialiser values for names." 231 232 # Get the initialisers in each namespace. 233 234 for path, name_initialisers in self.name_initialisers.items(): 235 const_accesses = self.const_accesses.get(path) 236 237 # Resolve values for each name in a scope. 238 239 for name, values in name_initialisers.items(): 240 if path == self.name: 241 assigned_path = name 242 else: 243 assigned_path = "%s.%s" % (path, name) 244 245 initialised_names = {} 246 aliased_names = {} 247 248 for i, name_ref in enumerate(values): 249 250 # Unwrap invocations. 251 252 if isinstance(name_ref, InvocationRef): 253 invocation = True 254 name_ref = name_ref.name_ref 255 else: 256 invocation = False 257 258 # Obtain a usable reference from names or constants. 259 260 if isinstance(name_ref, ResolvedNameRef): 261 if not name_ref.reference(): 262 continue 263 ref = name_ref.reference() 264 265 # Obtain a reference from instances. 266 267 elif isinstance(name_ref, InstanceRef): 268 if not name_ref.reference(): 269 continue 270 ref = name_ref.reference() 271 272 # Resolve accesses that employ constants. 273 274 elif isinstance(name_ref, AccessRef): 275 ref = None 276 277 if const_accesses: 278 resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames)) 279 if resolved_access: 280 objpath, ref, remaining_attrnames = resolved_access 281 if remaining_attrnames: 282 ref = None 283 284 # Accesses that do not employ constants cannot be resolved, 285 # but they may be resolvable later. 286 287 if not ref: 288 if not invocation: 289 aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number 290 continue 291 292 # Attempt to resolve a plain name reference. 293 294 elif isinstance(name_ref, LocalNameRef): 295 key = "%s.%s" % (path, name_ref.name) 296 origin = self.name_references.get(key) 297 298 # Accesses that do not refer to known static objects 299 # cannot be resolved, but they may be resolvable later. 300 301 if not origin: 302 if not invocation: 303 aliased_names[i] = name_ref.name, None, name_ref.number 304 continue 305 306 ref = self.get_resolved_object(origin) 307 if not ref: 308 continue 309 310 elif isinstance(name_ref, NameRef): 311 key = "%s.%s" % (path, name_ref.name) 312 origin = self.name_references.get(key) 313 if not origin: 314 continue 315 316 ref = self.get_resolved_object(origin) 317 if not ref: 318 continue 319 320 else: 321 continue 322 323 # Resolve any hidden dependencies involving external objects 324 # or unresolved names referring to globals or built-ins. 325 326 if ref.has_kind("<depends>"): 327 ref = self.importer.identify(ref.get_origin()) 328 329 # Convert class invocations to instances. 330 331 if ref and invocation: 332 ref = self.convert_invocation(ref) 333 334 if ref and not ref.has_kind("<var>"): 335 initialised_names[i] = ref 336 337 if initialised_names: 338 self.initialised_names[assigned_path] = initialised_names 339 if aliased_names: 340 self.aliased_names[assigned_path] = aliased_names 341 342 def resolve_literals(self): 343 344 "Resolve constant value types." 345 346 # Get the constants defined in each namespace. 347 348 for path, constants in self.constants.items(): 349 for constant, n in constants.items(): 350 objpath = "%s.$c%d" % (path, n) 351 _constant, value_type = self.constant_values[objpath] 352 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 353 354 # Get the literals defined in each namespace. 355 356 for path, literals in self.literals.items(): 357 for n in range(0, literals): 358 objpath = "%s.$C%d" % (path, n) 359 value_type = self.literal_types[objpath] 360 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 361 362 # Object resolution. 363 364 def get_resolved_object(self, path): 365 366 """ 367 Get the details of an object with the given 'path' within this module. 368 Where the object has not been resolved, None is returned. This differs 369 from the get_object method used elsewhere in that it does not return an 370 unresolved object reference. 371 """ 372 373 if self.objects.has_key(path): 374 ref = self.objects[path] 375 if ref.has_kind("<depends>"): 376 return None 377 else: 378 return ref 379 else: 380 return None 381 382 # vim: tabstop=4 expandtab shiftwidth=4