paul@12 | 1 | #!/usr/bin/env python |
paul@12 | 2 | |
paul@12 | 3 | """ |
paul@12 | 4 | Name resolution. |
paul@12 | 5 | |
paul@597 | 6 | Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk> |
paul@12 | 7 | |
paul@12 | 8 | This program is free software; you can redistribute it and/or modify it under |
paul@12 | 9 | the terms of the GNU General Public License as published by the Free Software |
paul@12 | 10 | Foundation; either version 3 of the License, or (at your option) any later |
paul@12 | 11 | version. |
paul@12 | 12 | |
paul@12 | 13 | This program is distributed in the hope that it will be useful, but WITHOUT |
paul@12 | 14 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
paul@12 | 15 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
paul@12 | 16 | details. |
paul@12 | 17 | |
paul@12 | 18 | You should have received a copy of the GNU General Public License along with |
paul@12 | 19 | this program. If not, see <http://www.gnu.org/licenses/>. |
paul@12 | 20 | """ |
paul@12 | 21 | |
paul@135 | 22 | from common import init_item |
paul@12 | 23 | from results import AccessRef, InstanceRef, InvocationRef, LocalNameRef, \ |
paul@794 | 24 | MultipleRef, NameRef, ResolvedNameRef |
paul@12 | 25 | from referencing import Reference |
paul@12 | 26 | import sys |
paul@12 | 27 | |
paul@12 | 28 | class NameResolving: |
paul@12 | 29 | |
paul@12 | 30 | "Resolving names mix-in for inspected modules." |
paul@12 | 31 | |
paul@12 | 32 | # Post-inspection resolution activities. |
paul@12 | 33 | |
paul@12 | 34 | def resolve(self): |
paul@12 | 35 | |
paul@12 | 36 | "Resolve dependencies and complete definitions." |
paul@12 | 37 | |
paul@12 | 38 | self.resolve_class_bases() |
paul@21 | 39 | self.check_special() |
paul@12 | 40 | self.check_names_used() |
paul@27 | 41 | self.check_invocations() |
paul@12 | 42 | self.resolve_initialisers() |
paul@703 | 43 | self.resolve_return_values() |
paul@12 | 44 | self.resolve_literals() |
paul@12 | 45 | |
paul@12 | 46 | def resolve_class_bases(self): |
paul@12 | 47 | |
paul@12 | 48 | "Resolve all class bases since some of them may have been deferred." |
paul@12 | 49 | |
paul@12 | 50 | for name, bases in self.classes.items(): |
paul@12 | 51 | resolved = [] |
paul@12 | 52 | bad = [] |
paul@12 | 53 | |
paul@12 | 54 | for base in bases: |
paul@27 | 55 | ref = self.importer.identify(base.get_origin()) |
paul@12 | 56 | |
paul@12 | 57 | # Obtain the origin of the base class reference. |
paul@12 | 58 | |
paul@12 | 59 | if not ref or not ref.has_kind("<class>"): |
paul@12 | 60 | bad.append(base) |
paul@12 | 61 | break |
paul@12 | 62 | |
paul@12 | 63 | resolved.append(ref) |
paul@12 | 64 | |
paul@12 | 65 | if bad: |
paul@72 | 66 | print >>sys.stderr, "Bases of class %s were not classes: %s" % (name, ", ".join(map(str, bad))) |
paul@12 | 67 | else: |
paul@12 | 68 | self.importer.classes[name] = self.classes[name] = resolved |
paul@12 | 69 | |
paul@12 | 70 | def check_special(self): |
paul@12 | 71 | |
paul@12 | 72 | "Check special names." |
paul@12 | 73 | |
paul@12 | 74 | for name, value in self.special.items(): |
paul@423 | 75 | ref, paths = value |
paul@423 | 76 | self.special[name] = self.importer.identify(ref.get_origin()), paths |
paul@12 | 77 | |
paul@12 | 78 | def check_names_used(self): |
paul@12 | 79 | |
paul@27 | 80 | "Check the external names used by each scope." |
paul@12 | 81 | |
paul@27 | 82 | for key, ref in self.name_references.items(): |
paul@27 | 83 | path, name = key.rsplit(".", 1) |
paul@27 | 84 | self.resolve_accesses(path, name, ref) |
paul@12 | 85 | |
paul@27 | 86 | def check_invocations(self): |
paul@12 | 87 | |
paul@27 | 88 | "Find invocations amongst module data and replace their results." |
paul@12 | 89 | |
paul@27 | 90 | # Find members and replace invocation results with values. This is |
paul@27 | 91 | # effectively the same as is done for initialised names, but refers to |
paul@27 | 92 | # any unchanging value after initialisation. |
paul@12 | 93 | |
paul@27 | 94 | for key, ref in self.objects.items(): |
paul@27 | 95 | if ref.has_kind("<invoke>"): |
paul@27 | 96 | ref = self.convert_invocation(ref) |
paul@27 | 97 | self.importer.objects[key] = self.objects[key] = ref |
paul@12 | 98 | |
paul@169 | 99 | # Convert name references. |
paul@169 | 100 | |
paul@169 | 101 | for key, ref in self.name_references.items(): |
paul@169 | 102 | if ref.has_kind("<invoke>"): |
paul@169 | 103 | ref = self.convert_invocation(ref) |
paul@169 | 104 | self.importer.all_name_references[key] = self.name_references[key] = ref |
paul@169 | 105 | |
paul@31 | 106 | # Convert function defaults, which are effectively extra members of the |
paul@31 | 107 | # module, and function locals. |
paul@12 | 108 | |
paul@31 | 109 | for fname, parameters in self.function_defaults.items(): |
paul@27 | 110 | l = [] |
paul@27 | 111 | for pname, ref in parameters: |
paul@27 | 112 | if ref.has_kind("<invoke>"): |
paul@27 | 113 | ref = self.convert_invocation(ref) |
paul@27 | 114 | l.append((pname, ref)) |
paul@129 | 115 | self.importer.function_defaults[fname] = self.function_defaults[fname] = l |
paul@12 | 116 | |
paul@31 | 117 | # Convert function locals referencing invocations. |
paul@31 | 118 | |
paul@31 | 119 | for fname, names in self.function_locals.items(): |
paul@31 | 120 | for name, ref in names.items(): |
paul@31 | 121 | if ref.has_kind("<invoke>"): |
paul@31 | 122 | ref = self.convert_invocation(ref) |
paul@31 | 123 | names[name] = ref |
paul@31 | 124 | |
paul@27 | 125 | def convert_invocation(self, ref): |
paul@27 | 126 | |
paul@27 | 127 | "Convert the given invocation 'ref', handling instantiation." |
paul@27 | 128 | |
paul@175 | 129 | alias = ref.get_name() |
paul@27 | 130 | ref = self.importer.identify(ref.get_origin()) |
paul@175 | 131 | ref = ref and ref.has_kind("<class>") and ref.instance_of() or Reference("<var>") |
paul@175 | 132 | return ref and ref.alias(alias) or None |
paul@12 | 133 | |
paul@12 | 134 | def resolve_accesses(self, path, name, ref): |
paul@12 | 135 | |
paul@12 | 136 | """ |
paul@12 | 137 | Resolve any unresolved accesses in the function at the given 'path' |
paul@12 | 138 | for the given 'name' corresponding to the indicated 'ref'. Note that |
paul@12 | 139 | this mechanism cannot resolve things like inherited methods because |
paul@12 | 140 | they are not recorded as program objects in their inherited locations. |
paul@12 | 141 | """ |
paul@12 | 142 | |
paul@12 | 143 | attr_accesses = self.global_attr_accesses.get(path) |
paul@12 | 144 | all_attrnames = attr_accesses and attr_accesses.get(name) |
paul@12 | 145 | |
paul@12 | 146 | if not all_attrnames: |
paul@12 | 147 | return |
paul@12 | 148 | |
paul@12 | 149 | # Insist on constant accessors. |
paul@12 | 150 | |
paul@12 | 151 | if not ref.has_kind(["<class>", "<module>"]): |
paul@12 | 152 | return |
paul@12 | 153 | |
paul@12 | 154 | found_attrnames = set() |
paul@12 | 155 | |
paul@12 | 156 | for attrnames in all_attrnames: |
paul@12 | 157 | |
paul@12 | 158 | # Start with the resolved name, adding attributes. |
paul@12 | 159 | |
paul@12 | 160 | attrs = ref.get_path() |
paul@12 | 161 | remaining = attrnames.split(".") |
paul@12 | 162 | last_ref = ref |
paul@12 | 163 | |
paul@12 | 164 | # Add each component, testing for a constant object. |
paul@12 | 165 | |
paul@12 | 166 | while remaining: |
paul@12 | 167 | attrname = remaining[0] |
paul@12 | 168 | attrs.append(attrname) |
paul@12 | 169 | del remaining[0] |
paul@12 | 170 | |
paul@12 | 171 | # Find any constant object reference. |
paul@12 | 172 | |
paul@12 | 173 | attr_ref = self.get_resolved_object(".".join(attrs)) |
paul@12 | 174 | |
paul@12 | 175 | # Non-constant accessors terminate the traversal. |
paul@12 | 176 | |
paul@12 | 177 | if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]): |
paul@12 | 178 | |
paul@12 | 179 | # Provide the furthest constant accessor unless the final |
paul@12 | 180 | # access can be resolved. |
paul@12 | 181 | |
paul@12 | 182 | if remaining: |
paul@12 | 183 | remaining.insert(0, attrs.pop()) |
paul@12 | 184 | else: |
paul@12 | 185 | last_ref = attr_ref |
paul@12 | 186 | break |
paul@12 | 187 | |
paul@12 | 188 | # Follow any reference to a constant object. |
paul@12 | 189 | # Where the given name refers to an object in another location, |
paul@12 | 190 | # switch to the other location in order to be able to test its |
paul@12 | 191 | # attributes. |
paul@12 | 192 | |
paul@12 | 193 | last_ref = attr_ref |
paul@12 | 194 | attrs = attr_ref.get_path() |
paul@12 | 195 | |
paul@12 | 196 | # Record a constant accessor only if an object was found |
paul@12 | 197 | # that is different from the namespace involved. |
paul@12 | 198 | |
paul@12 | 199 | if last_ref: |
paul@12 | 200 | objpath = ".".join(attrs) |
paul@12 | 201 | if objpath != path: |
paul@12 | 202 | |
paul@27 | 203 | if last_ref.has_kind("<invoke>"): |
paul@27 | 204 | last_ref = self.convert_invocation(last_ref) |
paul@27 | 205 | |
paul@12 | 206 | # Establish a constant access. |
paul@12 | 207 | |
paul@12 | 208 | init_item(self.const_accesses, path, dict) |
paul@12 | 209 | self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining)) |
paul@12 | 210 | |
paul@12 | 211 | if len(attrs) > 1: |
paul@12 | 212 | found_attrnames.add(attrs[1]) |
paul@12 | 213 | |
paul@12 | 214 | # Remove any usage records for the name. |
paul@12 | 215 | |
paul@12 | 216 | if found_attrnames: |
paul@12 | 217 | |
paul@12 | 218 | # NOTE: Should be only one name version. |
paul@12 | 219 | |
paul@12 | 220 | versions = [] |
paul@12 | 221 | for version in self.attr_usage[path][name]: |
paul@12 | 222 | new_usage = set() |
paul@12 | 223 | for usage in version: |
paul@12 | 224 | if found_attrnames.intersection(usage): |
paul@12 | 225 | new_usage.add(tuple(set(usage).difference(found_attrnames))) |
paul@12 | 226 | else: |
paul@12 | 227 | new_usage.add(usage) |
paul@12 | 228 | versions.append(new_usage) |
paul@12 | 229 | |
paul@12 | 230 | self.attr_usage[path][name] = versions |
paul@12 | 231 | |
paul@12 | 232 | def resolve_initialisers(self): |
paul@12 | 233 | |
paul@12 | 234 | "Resolve initialiser values for names." |
paul@12 | 235 | |
paul@12 | 236 | # Get the initialisers in each namespace. |
paul@12 | 237 | |
paul@12 | 238 | for path, name_initialisers in self.name_initialisers.items(): |
paul@12 | 239 | |
paul@12 | 240 | # Resolve values for each name in a scope. |
paul@12 | 241 | |
paul@12 | 242 | for name, values in name_initialisers.items(): |
paul@793 | 243 | self._resolve_values(path, name, values) |
paul@12 | 244 | |
paul@703 | 245 | def resolve_return_values(self): |
paul@703 | 246 | |
paul@703 | 247 | "Resolve return values using name references." |
paul@703 | 248 | |
paul@703 | 249 | # Get the return values from each namespace. |
paul@703 | 250 | |
paul@703 | 251 | for path, values in self.return_values.items(): |
paul@731 | 252 | |
paul@731 | 253 | # Resolve each return value provided by the scope. |
paul@703 | 254 | |
paul@793 | 255 | self._resolve_values(path, "$return", values) |
paul@793 | 256 | |
paul@793 | 257 | def _resolve_values(self, path, name, values): |
paul@793 | 258 | |
paul@793 | 259 | """ |
paul@793 | 260 | Resolve in 'path' the references for 'name' involving the given |
paul@793 | 261 | 'values'. |
paul@793 | 262 | """ |
paul@793 | 263 | |
paul@793 | 264 | initialised_names = {} |
paul@793 | 265 | aliased_names = {} |
paul@703 | 266 | |
paul@793 | 267 | for i, name_ref in enumerate(values): |
paul@793 | 268 | initialised_ref, _aliased_names = self.resolve_reference(path, name_ref) |
paul@793 | 269 | if initialised_ref: |
paul@793 | 270 | initialised_names[i] = initialised_ref |
paul@793 | 271 | if _aliased_names: |
paul@793 | 272 | aliased_names[i] = _aliased_names |
paul@703 | 273 | |
paul@793 | 274 | if initialised_names: |
paul@793 | 275 | self.initialised_names[(path, name)] = initialised_names |
paul@793 | 276 | if aliased_names: |
paul@793 | 277 | self.aliased_names[(path, name)] = aliased_names |
paul@703 | 278 | |
paul@703 | 279 | def resolve_reference(self, path, name_ref): |
paul@703 | 280 | |
paul@703 | 281 | """ |
paul@703 | 282 | Within the namespace 'path', resolve the given 'name_ref', returning any |
paul@703 | 283 | initialised reference, along with any aliased name information. |
paul@703 | 284 | """ |
paul@703 | 285 | |
paul@703 | 286 | initialised_ref = None |
paul@742 | 287 | aliased_names = None |
paul@703 | 288 | no_reference = None, None |
paul@703 | 289 | |
paul@794 | 290 | # Attempt to obtain a coherent reference from multiple outcomes. |
paul@794 | 291 | |
paul@794 | 292 | if isinstance(name_ref, MultipleRef): |
paul@794 | 293 | refs = set() |
paul@794 | 294 | aliases = [] |
paul@794 | 295 | |
paul@794 | 296 | for result in name_ref.results: |
paul@794 | 297 | _initialised_ref, _aliased_names = self.resolve_reference(path, result) |
paul@794 | 298 | |
paul@794 | 299 | # Unsuitable references at any level cause the result to yield |
paul@794 | 300 | # no reference. |
paul@794 | 301 | |
paul@794 | 302 | if not _initialised_ref: |
paul@794 | 303 | refs = None |
paul@794 | 304 | elif refs is not None: |
paul@794 | 305 | refs.add(_initialised_ref) |
paul@794 | 306 | |
paul@794 | 307 | if not _aliased_names: |
paul@794 | 308 | aliases = None |
paul@794 | 309 | elif aliases is not None: |
paul@794 | 310 | aliases += _aliased_names |
paul@794 | 311 | |
paul@794 | 312 | # Only unambiguous references are returned as initialising |
paul@794 | 313 | # references. |
paul@794 | 314 | |
paul@794 | 315 | if refs and len(refs) == 1: |
paul@794 | 316 | return list(refs)[0], aliases |
paul@794 | 317 | else: |
paul@794 | 318 | return None, aliases |
paul@794 | 319 | |
paul@703 | 320 | # Unwrap invocations. |
paul@703 | 321 | |
paul@703 | 322 | if isinstance(name_ref, InvocationRef): |
paul@703 | 323 | invocation = True |
paul@703 | 324 | name_ref = name_ref.name_ref |
paul@703 | 325 | else: |
paul@703 | 326 | invocation = False |
paul@703 | 327 | |
paul@794 | 328 | const_accesses = self.const_accesses.get(path) |
paul@794 | 329 | |
paul@703 | 330 | # Obtain a usable reference from names or constants. |
paul@703 | 331 | |
paul@703 | 332 | if isinstance(name_ref, ResolvedNameRef): |
paul@703 | 333 | if not name_ref.reference(): |
paul@703 | 334 | return no_reference |
paul@703 | 335 | ref = name_ref.reference() |
paul@703 | 336 | |
paul@703 | 337 | # Obtain a reference from instances. |
paul@703 | 338 | |
paul@703 | 339 | elif isinstance(name_ref, InstanceRef): |
paul@703 | 340 | if not name_ref.reference(): |
paul@703 | 341 | return no_reference |
paul@703 | 342 | ref = name_ref.reference() |
paul@703 | 343 | |
paul@703 | 344 | # Resolve accesses that employ constants. |
paul@703 | 345 | |
paul@703 | 346 | elif isinstance(name_ref, AccessRef): |
paul@703 | 347 | ref = None |
paul@703 | 348 | |
paul@703 | 349 | if const_accesses: |
paul@703 | 350 | resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames)) |
paul@703 | 351 | if resolved_access: |
paul@703 | 352 | objpath, ref, remaining_attrnames = resolved_access |
paul@703 | 353 | if remaining_attrnames: |
paul@703 | 354 | ref = None |
paul@703 | 355 | |
paul@703 | 356 | # Accesses that do not employ constants cannot be resolved, |
paul@703 | 357 | # but they may be resolvable later. |
paul@703 | 358 | |
paul@703 | 359 | if not ref: |
paul@703 | 360 | |
paul@711 | 361 | # Record the path used for tracking purposes |
paul@711 | 362 | # alongside original name, attribute and access |
paul@711 | 363 | # number details. |
paul@703 | 364 | |
paul@742 | 365 | aliased_names = [(path, name_ref.original_name, name_ref.attrnames, name_ref.number)] |
paul@703 | 366 | |
paul@742 | 367 | return None, aliased_names |
paul@703 | 368 | |
paul@703 | 369 | # Attempt to resolve a plain name reference. |
paul@703 | 370 | |
paul@703 | 371 | elif isinstance(name_ref, LocalNameRef): |
paul@703 | 372 | key = "%s.%s" % (path, name_ref.name) |
paul@703 | 373 | ref = self.name_references.get(key) |
paul@703 | 374 | |
paul@703 | 375 | # Accesses that do not refer to known static objects |
paul@703 | 376 | # cannot be resolved, but they may be resolvable later. |
paul@703 | 377 | |
paul@703 | 378 | if not ref: |
paul@703 | 379 | |
paul@711 | 380 | # Record the path used for tracking purposes |
paul@711 | 381 | # alongside original name, attribute and access |
paul@711 | 382 | # number details. |
paul@703 | 383 | |
paul@742 | 384 | aliased_names = [(path, name_ref.name, None, name_ref.number)] |
paul@703 | 385 | |
paul@742 | 386 | return None, aliased_names |
paul@703 | 387 | |
paul@703 | 388 | ref = self.get_resolved_object(ref.get_origin()) |
paul@703 | 389 | if not ref: |
paul@703 | 390 | return no_reference |
paul@703 | 391 | |
paul@703 | 392 | elif isinstance(name_ref, NameRef): |
paul@703 | 393 | key = "%s.%s" % (path, name_ref.name) |
paul@703 | 394 | ref = self.name_references.get(key) |
paul@703 | 395 | |
paul@703 | 396 | ref = ref and self.get_resolved_object(ref.get_origin()) |
paul@703 | 397 | if not ref: |
paul@703 | 398 | return no_reference |
paul@703 | 399 | |
paul@703 | 400 | else: |
paul@703 | 401 | return no_reference |
paul@703 | 402 | |
paul@703 | 403 | # Resolve any hidden dependencies involving external objects |
paul@703 | 404 | # or unresolved names referring to globals or built-ins. |
paul@703 | 405 | |
paul@703 | 406 | if ref.has_kind("<depends>"): |
paul@703 | 407 | ref = self.importer.identify(ref.get_origin()) |
paul@703 | 408 | |
paul@703 | 409 | # Convert class invocations to instances. |
paul@703 | 410 | |
paul@742 | 411 | if ref and (invocation or ref.has_kind("<invoke>")): |
paul@742 | 412 | target_ref = ref |
paul@742 | 413 | ref = self.convert_invocation(target_ref) |
paul@742 | 414 | |
paul@742 | 415 | if not ref or ref.has_kind("<var>"): |
paul@742 | 416 | aliased_names = self.get_aliases_for_target(target_ref.get_origin()) |
paul@742 | 417 | else: |
paul@742 | 418 | initialised_ref = ref |
paul@703 | 419 | |
paul@742 | 420 | elif ref and not ref.has_kind("<var>"): |
paul@703 | 421 | initialised_ref = ref |
paul@742 | 422 | |
paul@742 | 423 | return initialised_ref, aliased_names |
paul@703 | 424 | |
paul@742 | 425 | def get_aliases_for_target(self, path): |
paul@742 | 426 | |
paul@742 | 427 | "Return a list of return value locations for the given 'path'." |
paul@742 | 428 | |
paul@742 | 429 | return_values = self.importer.all_return_values.get(path) |
paul@742 | 430 | locations = [] |
paul@742 | 431 | |
paul@742 | 432 | if return_values: |
paul@742 | 433 | for version in range(0, len(return_values)): |
paul@742 | 434 | locations.append((path, "$return", None, version)) |
paul@742 | 435 | |
paul@742 | 436 | return locations |
paul@703 | 437 | |
paul@12 | 438 | def resolve_literals(self): |
paul@12 | 439 | |
paul@12 | 440 | "Resolve constant value types." |
paul@12 | 441 | |
paul@12 | 442 | # Get the constants defined in each namespace. |
paul@12 | 443 | |
paul@12 | 444 | for path, constants in self.constants.items(): |
paul@12 | 445 | for constant, n in constants.items(): |
paul@12 | 446 | objpath = "%s.$c%d" % (path, n) |
paul@406 | 447 | _constant, value_type, encoding = self.constant_values[objpath] |
paul@681 | 448 | self.initialised_names[(path, objpath)] = {0 : Reference("<instance>", value_type)} |
paul@12 | 449 | |
paul@12 | 450 | # Get the literals defined in each namespace. |
paul@12 | 451 | |
paul@12 | 452 | for path, literals in self.literals.items(): |
paul@12 | 453 | for n in range(0, literals): |
paul@12 | 454 | objpath = "%s.$C%d" % (path, n) |
paul@12 | 455 | value_type = self.literal_types[objpath] |
paul@681 | 456 | self.initialised_names[(path, objpath)] = {0 : Reference("<instance>", value_type)} |
paul@12 | 457 | |
paul@29 | 458 | # Object resolution. |
paul@29 | 459 | |
paul@792 | 460 | def get_resolved_object(self, path, defer=False): |
paul@29 | 461 | |
paul@29 | 462 | """ |
paul@29 | 463 | Get the details of an object with the given 'path' within this module. |
paul@29 | 464 | Where the object has not been resolved, None is returned. This differs |
paul@29 | 465 | from the get_object method used elsewhere in that it does not return an |
paul@29 | 466 | unresolved object reference. |
paul@29 | 467 | """ |
paul@29 | 468 | |
paul@29 | 469 | if self.objects.has_key(path): |
paul@29 | 470 | ref = self.objects[path] |
paul@792 | 471 | if not defer and ref.has_kind("<depends>"): |
paul@29 | 472 | return None |
paul@29 | 473 | else: |
paul@29 | 474 | return ref |
paul@29 | 475 | else: |
paul@29 | 476 | return None |
paul@29 | 477 | |
paul@12 | 478 | # vim: tabstop=4 expandtab shiftwidth=4 |