# HG changeset patch # User Paul Boddie # Date 1489424353 -3600 # Node ID 881299824661ae4960d38f5c99bc97d95b0d47ca # Parent 267be996f667393d4bcaa0c5b1aa65ddd04aeb74# Parent c83f896f063a5e5aef2d92a836247a1271ab83db Merged the return-value-definition branch into the default branch. diff -r 267be996f667 -r 881299824661 branching.py --- a/branching.py Mon Mar 13 17:09:50 2017 +0100 +++ b/branching.py Mon Mar 13 17:59:13 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 267be996f667 -r 881299824661 deducer.py --- a/deducer.py Mon Mar 13 17:09:50 2017 +0100 +++ b/deducer.py Mon Mar 13 17:59:13 2017 +0100 @@ -1860,8 +1860,13 @@ "Convert 'ref' to a provider appropriate to its invocation result." - if ref and ref.has_kind(""): - return ref + if ref: + if ref.has_kind(""): + return ref + elif ref.has_kind(""): + refs = self.importer.all_return_values.get(ref.get_origin()) + if refs and len(refs) == 1: + return first(refs) return Reference("") @@ -1878,8 +1883,13 @@ "Convert 'ref' to its invocation result." - if ref and ref.has_kind(""): - return ref.instance_of() + if ref: + if ref.has_kind(""): + return ref.instance_of() + elif ref.has_kind(""): + refs = self.importer.all_return_values.get(ref.get_origin()) + if refs and len(refs) == 1: + return first(refs) return Reference("") diff -r 267be996f667 -r 881299824661 importer.py --- a/importer.py Mon Mar 13 17:09:50 2017 +0100 +++ b/importer.py Mon Mar 13 17:59:13 2017 +0100 @@ -116,6 +116,10 @@ self.all_constants = {} self.all_constant_values = {} + # Return values. + + self.all_return_values = {} + self.make_cache() def give_warning(self, name): diff -r 267be996f667 -r 881299824661 inspector.py --- a/inspector.py Mon Mar 13 17:09:50 2017 +0100 +++ b/inspector.py Mon Mar 13 17:59:13 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. @@ -1076,6 +1083,7 @@ """ Stop tracking attribute usage, recording computed usage for the current + namespace. Indicate whether a value is always returned from the namespace. """ @@ -1086,6 +1094,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." @@ -1470,4 +1480,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 267be996f667 -r 881299824661 modules.py --- a/modules.py Mon Mar 13 17:09:50 2017 +0100 +++ b/modules.py Mon Mar 13 17:59:13 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 = {} @@ -111,6 +115,7 @@ self.propagate_name_references() self.propagate_attr_accesses() self.propagate_constants() + self.propagate_return_values() def unpropagate(self): @@ -147,6 +152,7 @@ remove_items(self.importer.all_attr_access_modifiers, self.attr_access_modifiers) remove_items(self.importer.all_constants, self.constants) remove_items(self.importer.all_constant_values, self.constant_values) + remove_items(self.importer.all_return_values, self.return_values) # Remove this module's objects from the importer. Objects are # automatically propagated when defined. @@ -216,6 +222,12 @@ self.importer.all_instance_attrs[name] = self.instance_attrs.get(name) or {} self.importer.all_instance_attr_constants[name] = self.instance_attr_constants.get(name) or {} + def propagate_return_values(self): + + "Propagate return values for the module." + + self.importer.all_return_values.update(self.return_values) + def set_object(self, name, value=None): "Set an object with the given 'name' and the given 'value'." @@ -398,6 +410,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 +631,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 +897,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 267be996f667 -r 881299824661 resolving.py --- a/resolving.py Mon Mar 13 17:09:50 2017 +0100 +++ b/resolving.py Mon Mar 13 17:59:13 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,107 +244,152 @@ 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: - - # 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: - - # 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 or ref.has_kind(""): - 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: + + # 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 None, aliased_name + + # 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: + + # 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 None, aliased_name + + 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 or ref.has_kind(""): + 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."