# HG changeset patch # User Paul Boddie # Date 1489267665 -3600 # Node ID 09d2060356136d91f468cbbc8ef8c9ab3cc188ad # Parent 2e22fd27e941265efe8ba5df3d60040ffff3f240 Record and resolve return value references. diff -r 2e22fd27e941 -r 09d206035613 branching.py --- a/branching.py Sat Mar 11 16:46:36 2017 +0100 +++ b/branching.py Sat Mar 11 22:27:45 2017 +0100 @@ -607,6 +607,12 @@ d[name] = l return d + def returns_value(self): + + "Indicate whether a value is always being returned." + + return isinstance(self.attribute_branches[-1], AbandonedDict) + # Special objects. class AbandonedDict(dict): diff -r 2e22fd27e941 -r 09d206035613 inspector.py --- a/inspector.py Sat Mar 11 16:46:36 2017 +0100 +++ b/inspector.py Sat Mar 11 22:27:45 2017 +0100 @@ -271,7 +271,7 @@ self.trackers[-1].abandon_branch() elif isinstance(n, compiler.ast.Return): - self.process_structure(n) + self.record_return_value(self.process_structure_node(n.value)) self.trackers[-1].abandon_returning_branch() # Print statements. @@ -654,7 +654,14 @@ self.start_tracking(locals) self.process_structure_node(n.code) - self.stop_tracking() + returns_value = self.stop_tracking() + + # Record any null result. + + is_initialiser = is_method and name == "__init__" + + if not returns_value and not is_initialiser: + self.record_return_value(ResolvedNameRef("None", self.get_builtin("None"))) # Exit to the parent. @@ -1075,6 +1082,7 @@ """ Stop tracking attribute usage, recording computed usage for the current + namespace. Indicate whether a value is always returned from the namespace. """ @@ -1085,6 +1093,8 @@ self.attr_usage[path] = tracker.get_all_usage() self.name_initialisers[path] = tracker.get_all_values() + return tracker.returns_value() + def start_tracking_in_module(self): "Start tracking attribute usage in the module." @@ -1469,4 +1479,14 @@ self.exception_namespaces.add(self.get_namespace_path()) + # Return values. + + def record_return_value(self, expr): + + "Record the given return 'expr'." + + path = self.get_namespace_path() + init_item(self.return_values, path, set) + self.return_values[path].add(expr) + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 2e22fd27e941 -r 09d206035613 modules.py --- a/modules.py Sat Mar 11 16:46:36 2017 +0100 +++ b/modules.py Sat Mar 11 22:27:45 2017 +0100 @@ -71,6 +71,10 @@ self.exception_namespaces = set() + # Return value details. + + self.return_values = {} + # Attribute usage at module and function levels. self.attr_usage = {} @@ -398,6 +402,7 @@ self._get_constant_literals(f) self._get_constant_values(f) self._get_exception_namespaces(f) + self._get_return_values(f) finally: f.close() @@ -618,6 +623,15 @@ self.exception_namespaces = value and set(value.split(", ")) or set() f.readline() + def _get_return_values(self, f): + f.readline() # "return values:" + line = f.readline().rstrip() + while line: + path, values = self._get_fields(line) + values = values.split(", ") + self.return_values[path] = map(decode_reference, values) + line = f.readline().rstrip() + # Generic parsing methods. def from_lines(self, f, d): @@ -875,6 +889,13 @@ paths.sort() print >>f, ", ".join(paths) + print >>f + print >>f, "return values:" + paths = self.return_values.keys() + paths.sort() + for path in paths: + print >>f, path, ", ".join(map(str, self.return_values[path])) + finally: f.close() diff -r 2e22fd27e941 -r 09d206035613 resolving.py --- a/resolving.py Sat Mar 11 16:46:36 2017 +0100 +++ b/resolving.py Sat Mar 11 22:27:45 2017 +0100 @@ -40,6 +40,7 @@ self.check_names_used() self.check_invocations() self.resolve_initialisers() + self.resolve_return_values() self.resolve_literals() def resolve_class_bases(self): @@ -235,7 +236,6 @@ # Get the initialisers in each namespace. for path, name_initialisers in self.name_initialisers.items(): - const_accesses = self.const_accesses.get(path) # Resolve values for each name in a scope. @@ -244,109 +244,154 @@ aliased_names = {} for i, name_ref in enumerate(values): - - # Unwrap invocations. - - if isinstance(name_ref, InvocationRef): - invocation = True - name_ref = name_ref.name_ref - else: - invocation = False - - # Obtain a usable reference from names or constants. - - if isinstance(name_ref, ResolvedNameRef): - if not name_ref.reference(): - continue - ref = name_ref.reference() - - # Obtain a reference from instances. - - elif isinstance(name_ref, InstanceRef): - if not name_ref.reference(): - continue - ref = name_ref.reference() - - # Resolve accesses that employ constants. - - elif isinstance(name_ref, AccessRef): - ref = None - - if const_accesses: - resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames)) - if resolved_access: - objpath, ref, remaining_attrnames = resolved_access - if remaining_attrnames: - ref = None - - # Accesses that do not employ constants cannot be resolved, - # but they may be resolvable later. - - if not ref: - if not invocation: - - # Record the path used for tracking purposes - # alongside original name, attribute and access - # number details. - - aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number - - continue - - # Attempt to resolve a plain name reference. - - elif isinstance(name_ref, LocalNameRef): - key = "%s.%s" % (path, name_ref.name) - ref = self.name_references.get(key) - - # Accesses that do not refer to known static objects - # cannot be resolved, but they may be resolvable later. - - if not ref: - if not invocation: - - # Record the path used for tracking purposes - # alongside original name, attribute and access - # number details. - - aliased_names[i] = path, name_ref.name, None, name_ref.number - - continue - - ref = self.get_resolved_object(ref.get_origin()) - if not ref: - continue - - elif isinstance(name_ref, NameRef): - key = "%s.%s" % (path, name_ref.name) - ref = self.name_references.get(key) - - ref = ref and self.get_resolved_object(ref.get_origin()) - if not ref: - continue - - else: - continue - - # Resolve any hidden dependencies involving external objects - # or unresolved names referring to globals or built-ins. - - if ref.has_kind(""): - ref = self.importer.identify(ref.get_origin()) - - # Convert class invocations to instances. - - if ref and invocation: - ref = self.convert_invocation(ref) - - if ref and not ref.has_kind(""): - initialised_names[i] = ref + initialised_ref, aliased_name = self.resolve_reference(path, name_ref) + if initialised_ref: + initialised_names[i] = initialised_ref + if aliased_name: + aliased_names[i] = aliased_name if initialised_names: self.initialised_names[(path, name)] = initialised_names if aliased_names: self.aliased_names[(path, name)] = aliased_names + def resolve_return_values(self): + + "Resolve return values using name references." + + return_values = {} + + # Get the return values from each namespace. + + for path, values in self.return_values.items(): + l = set() + + for value in values: + if not value: + ref = None + else: + ref, aliased_name = self.resolve_reference(path, value) + + l.add(ref or Reference("")) + + return_values[path] = l + + # Replace the original values. + + self.return_values = return_values + + def resolve_reference(self, path, name_ref): + + """ + Within the namespace 'path', resolve the given 'name_ref', returning any + initialised reference, along with any aliased name information. + """ + + const_accesses = self.const_accesses.get(path) + + initialised_ref = None + aliased_name = None + no_reference = None, None + + # Unwrap invocations. + + if isinstance(name_ref, InvocationRef): + invocation = True + name_ref = name_ref.name_ref + else: + invocation = False + + # Obtain a usable reference from names or constants. + + if isinstance(name_ref, ResolvedNameRef): + if not name_ref.reference(): + return no_reference + ref = name_ref.reference() + + # Obtain a reference from instances. + + elif isinstance(name_ref, InstanceRef): + if not name_ref.reference(): + return no_reference + ref = name_ref.reference() + + # Resolve accesses that employ constants. + + elif isinstance(name_ref, AccessRef): + ref = None + + if const_accesses: + resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames)) + if resolved_access: + objpath, ref, remaining_attrnames = resolved_access + if remaining_attrnames: + ref = None + + # Accesses that do not employ constants cannot be resolved, + # but they may be resolvable later. + + if not ref: + if not invocation: + + # Record the path used for tracking purposes + # alongside original name, attribute and access + # number details. + + aliased_name = path, name_ref.original_name, name_ref.attrnames, name_ref.number + + return no_reference + + # Attempt to resolve a plain name reference. + + elif isinstance(name_ref, LocalNameRef): + key = "%s.%s" % (path, name_ref.name) + ref = self.name_references.get(key) + + # Accesses that do not refer to known static objects + # cannot be resolved, but they may be resolvable later. + + if not ref: + if not invocation: + + # Record the path used for tracking purposes + # alongside original name, attribute and access + # number details. + + aliased_name = path, name_ref.name, None, name_ref.number + + return no_reference + + ref = self.get_resolved_object(ref.get_origin()) + if not ref: + return no_reference + + elif isinstance(name_ref, NameRef): + key = "%s.%s" % (path, name_ref.name) + ref = self.name_references.get(key) + + ref = ref and self.get_resolved_object(ref.get_origin()) + if not ref: + return no_reference + + else: + return no_reference + + # Resolve any hidden dependencies involving external objects + # or unresolved names referring to globals or built-ins. + + if ref.has_kind(""): + ref = self.importer.identify(ref.get_origin()) + + # Convert class invocations to instances. + + if ref and invocation: + ref = self.convert_invocation(ref) + + if ref and not ref.has_kind(""): + initialised_ref = ref + + return initialised_ref, aliased_name + def resolve_literals(self): "Resolve constant value types."