1.1 --- a/branching.py Mon Mar 13 17:09:50 2017 +0100
1.2 +++ b/branching.py Mon Mar 13 17:59:13 2017 +0100
1.3 @@ -607,6 +607,12 @@
1.4 d[name] = l
1.5 return d
1.6
1.7 + def returns_value(self):
1.8 +
1.9 + "Indicate whether a value is always being returned."
1.10 +
1.11 + return isinstance(self.attribute_branches[-1], AbandonedDict)
1.12 +
1.13 # Special objects.
1.14
1.15 class AbandonedDict(dict):
2.1 --- a/deducer.py Mon Mar 13 17:09:50 2017 +0100
2.2 +++ b/deducer.py Mon Mar 13 17:59:13 2017 +0100
2.3 @@ -1860,8 +1860,13 @@
2.4
2.5 "Convert 'ref' to a provider appropriate to its invocation result."
2.6
2.7 - if ref and ref.has_kind("<class>"):
2.8 - return ref
2.9 + if ref:
2.10 + if ref.has_kind("<class>"):
2.11 + return ref
2.12 + elif ref.has_kind("<function>"):
2.13 + refs = self.importer.all_return_values.get(ref.get_origin())
2.14 + if refs and len(refs) == 1:
2.15 + return first(refs)
2.16
2.17 return Reference("<var>")
2.18
2.19 @@ -1878,8 +1883,13 @@
2.20
2.21 "Convert 'ref' to its invocation result."
2.22
2.23 - if ref and ref.has_kind("<class>"):
2.24 - return ref.instance_of()
2.25 + if ref:
2.26 + if ref.has_kind("<class>"):
2.27 + return ref.instance_of()
2.28 + elif ref.has_kind("<function>"):
2.29 + refs = self.importer.all_return_values.get(ref.get_origin())
2.30 + if refs and len(refs) == 1:
2.31 + return first(refs)
2.32
2.33 return Reference("<var>")
2.34
3.1 --- a/importer.py Mon Mar 13 17:09:50 2017 +0100
3.2 +++ b/importer.py Mon Mar 13 17:59:13 2017 +0100
3.3 @@ -116,6 +116,10 @@
3.4 self.all_constants = {}
3.5 self.all_constant_values = {}
3.6
3.7 + # Return values.
3.8 +
3.9 + self.all_return_values = {}
3.10 +
3.11 self.make_cache()
3.12
3.13 def give_warning(self, name):
4.1 --- a/inspector.py Mon Mar 13 17:09:50 2017 +0100
4.2 +++ b/inspector.py Mon Mar 13 17:59:13 2017 +0100
4.3 @@ -271,7 +271,7 @@
4.4 self.trackers[-1].abandon_branch()
4.5
4.6 elif isinstance(n, compiler.ast.Return):
4.7 - self.process_structure(n)
4.8 + self.record_return_value(self.process_structure_node(n.value))
4.9 self.trackers[-1].abandon_returning_branch()
4.10
4.11 # Print statements.
4.12 @@ -654,7 +654,14 @@
4.13
4.14 self.start_tracking(locals)
4.15 self.process_structure_node(n.code)
4.16 - self.stop_tracking()
4.17 + returns_value = self.stop_tracking()
4.18 +
4.19 + # Record any null result.
4.20 +
4.21 + is_initialiser = is_method and name == "__init__"
4.22 +
4.23 + if not returns_value and not is_initialiser:
4.24 + self.record_return_value(ResolvedNameRef("None", self.get_builtin("None")))
4.25
4.26 # Exit to the parent.
4.27
4.28 @@ -1076,6 +1083,7 @@
4.29
4.30 """
4.31 Stop tracking attribute usage, recording computed usage for the current
4.32 + namespace. Indicate whether a value is always returned from the
4.33 namespace.
4.34 """
4.35
4.36 @@ -1086,6 +1094,8 @@
4.37 self.attr_usage[path] = tracker.get_all_usage()
4.38 self.name_initialisers[path] = tracker.get_all_values()
4.39
4.40 + return tracker.returns_value()
4.41 +
4.42 def start_tracking_in_module(self):
4.43
4.44 "Start tracking attribute usage in the module."
4.45 @@ -1470,4 +1480,14 @@
4.46
4.47 self.exception_namespaces.add(self.get_namespace_path())
4.48
4.49 + # Return values.
4.50 +
4.51 + def record_return_value(self, expr):
4.52 +
4.53 + "Record the given return 'expr'."
4.54 +
4.55 + path = self.get_namespace_path()
4.56 + init_item(self.return_values, path, set)
4.57 + self.return_values[path].add(expr)
4.58 +
4.59 # vim: tabstop=4 expandtab shiftwidth=4
5.1 --- a/modules.py Mon Mar 13 17:09:50 2017 +0100
5.2 +++ b/modules.py Mon Mar 13 17:59:13 2017 +0100
5.3 @@ -71,6 +71,10 @@
5.4
5.5 self.exception_namespaces = set()
5.6
5.7 + # Return value details.
5.8 +
5.9 + self.return_values = {}
5.10 +
5.11 # Attribute usage at module and function levels.
5.12
5.13 self.attr_usage = {}
5.14 @@ -111,6 +115,7 @@
5.15 self.propagate_name_references()
5.16 self.propagate_attr_accesses()
5.17 self.propagate_constants()
5.18 + self.propagate_return_values()
5.19
5.20 def unpropagate(self):
5.21
5.22 @@ -147,6 +152,7 @@
5.23 remove_items(self.importer.all_attr_access_modifiers, self.attr_access_modifiers)
5.24 remove_items(self.importer.all_constants, self.constants)
5.25 remove_items(self.importer.all_constant_values, self.constant_values)
5.26 + remove_items(self.importer.all_return_values, self.return_values)
5.27
5.28 # Remove this module's objects from the importer. Objects are
5.29 # automatically propagated when defined.
5.30 @@ -216,6 +222,12 @@
5.31 self.importer.all_instance_attrs[name] = self.instance_attrs.get(name) or {}
5.32 self.importer.all_instance_attr_constants[name] = self.instance_attr_constants.get(name) or {}
5.33
5.34 + def propagate_return_values(self):
5.35 +
5.36 + "Propagate return values for the module."
5.37 +
5.38 + self.importer.all_return_values.update(self.return_values)
5.39 +
5.40 def set_object(self, name, value=None):
5.41
5.42 "Set an object with the given 'name' and the given 'value'."
5.43 @@ -398,6 +410,7 @@
5.44 self._get_constant_literals(f)
5.45 self._get_constant_values(f)
5.46 self._get_exception_namespaces(f)
5.47 + self._get_return_values(f)
5.48
5.49 finally:
5.50 f.close()
5.51 @@ -618,6 +631,15 @@
5.52 self.exception_namespaces = value and set(value.split(", ")) or set()
5.53 f.readline()
5.54
5.55 + def _get_return_values(self, f):
5.56 + f.readline() # "return values:"
5.57 + line = f.readline().rstrip()
5.58 + while line:
5.59 + path, values = self._get_fields(line)
5.60 + values = values.split(", ")
5.61 + self.return_values[path] = map(decode_reference, values)
5.62 + line = f.readline().rstrip()
5.63 +
5.64 # Generic parsing methods.
5.65
5.66 def from_lines(self, f, d):
5.67 @@ -875,6 +897,13 @@
5.68 paths.sort()
5.69 print >>f, ", ".join(paths)
5.70
5.71 + print >>f
5.72 + print >>f, "return values:"
5.73 + paths = self.return_values.keys()
5.74 + paths.sort()
5.75 + for path in paths:
5.76 + print >>f, path, ", ".join(map(str, self.return_values[path]))
5.77 +
5.78 finally:
5.79 f.close()
5.80
6.1 --- a/resolving.py Mon Mar 13 17:09:50 2017 +0100
6.2 +++ b/resolving.py Mon Mar 13 17:59:13 2017 +0100
6.3 @@ -40,6 +40,7 @@
6.4 self.check_names_used()
6.5 self.check_invocations()
6.6 self.resolve_initialisers()
6.7 + self.resolve_return_values()
6.8 self.resolve_literals()
6.9
6.10 def resolve_class_bases(self):
6.11 @@ -235,7 +236,6 @@
6.12 # Get the initialisers in each namespace.
6.13
6.14 for path, name_initialisers in self.name_initialisers.items():
6.15 - const_accesses = self.const_accesses.get(path)
6.16
6.17 # Resolve values for each name in a scope.
6.18
6.19 @@ -244,107 +244,152 @@
6.20 aliased_names = {}
6.21
6.22 for i, name_ref in enumerate(values):
6.23 -
6.24 - # Unwrap invocations.
6.25 -
6.26 - if isinstance(name_ref, InvocationRef):
6.27 - invocation = True
6.28 - name_ref = name_ref.name_ref
6.29 - else:
6.30 - invocation = False
6.31 -
6.32 - # Obtain a usable reference from names or constants.
6.33 -
6.34 - if isinstance(name_ref, ResolvedNameRef):
6.35 - if not name_ref.reference():
6.36 - continue
6.37 - ref = name_ref.reference()
6.38 -
6.39 - # Obtain a reference from instances.
6.40 -
6.41 - elif isinstance(name_ref, InstanceRef):
6.42 - if not name_ref.reference():
6.43 - continue
6.44 - ref = name_ref.reference()
6.45 -
6.46 - # Resolve accesses that employ constants.
6.47 -
6.48 - elif isinstance(name_ref, AccessRef):
6.49 - ref = None
6.50 -
6.51 - if const_accesses:
6.52 - resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
6.53 - if resolved_access:
6.54 - objpath, ref, remaining_attrnames = resolved_access
6.55 - if remaining_attrnames:
6.56 - ref = None
6.57 -
6.58 - # Accesses that do not employ constants cannot be resolved,
6.59 - # but they may be resolvable later.
6.60 -
6.61 - if not ref:
6.62 -
6.63 - # Record the path used for tracking purposes
6.64 - # alongside original name, attribute and access
6.65 - # number details.
6.66 -
6.67 - aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number
6.68 -
6.69 - continue
6.70 -
6.71 - # Attempt to resolve a plain name reference.
6.72 -
6.73 - elif isinstance(name_ref, LocalNameRef):
6.74 - key = "%s.%s" % (path, name_ref.name)
6.75 - ref = self.name_references.get(key)
6.76 -
6.77 - # Accesses that do not refer to known static objects
6.78 - # cannot be resolved, but they may be resolvable later.
6.79 -
6.80 - if not ref:
6.81 -
6.82 - # Record the path used for tracking purposes
6.83 - # alongside original name, attribute and access
6.84 - # number details.
6.85 -
6.86 - aliased_names[i] = path, name_ref.name, None, name_ref.number
6.87 -
6.88 - continue
6.89 -
6.90 - ref = self.get_resolved_object(ref.get_origin())
6.91 - if not ref:
6.92 - continue
6.93 -
6.94 - elif isinstance(name_ref, NameRef):
6.95 - key = "%s.%s" % (path, name_ref.name)
6.96 - ref = self.name_references.get(key)
6.97 -
6.98 - ref = ref and self.get_resolved_object(ref.get_origin())
6.99 - if not ref:
6.100 - continue
6.101 -
6.102 - else:
6.103 - continue
6.104 -
6.105 - # Resolve any hidden dependencies involving external objects
6.106 - # or unresolved names referring to globals or built-ins.
6.107 -
6.108 - if ref.has_kind("<depends>"):
6.109 - ref = self.importer.identify(ref.get_origin())
6.110 -
6.111 - # Convert class invocations to instances.
6.112 -
6.113 - if ref and invocation or ref.has_kind("<invoke>"):
6.114 - ref = self.convert_invocation(ref)
6.115 -
6.116 - if ref and not ref.has_kind("<var>"):
6.117 - initialised_names[i] = ref
6.118 + initialised_ref, aliased_name = self.resolve_reference(path, name_ref)
6.119 + if initialised_ref:
6.120 + initialised_names[i] = initialised_ref
6.121 + if aliased_name:
6.122 + aliased_names[i] = aliased_name
6.123
6.124 if initialised_names:
6.125 self.initialised_names[(path, name)] = initialised_names
6.126 if aliased_names:
6.127 self.aliased_names[(path, name)] = aliased_names
6.128
6.129 + def resolve_return_values(self):
6.130 +
6.131 + "Resolve return values using name references."
6.132 +
6.133 + return_values = {}
6.134 +
6.135 + # Get the return values from each namespace.
6.136 +
6.137 + for path, values in self.return_values.items():
6.138 + l = set()
6.139 +
6.140 + for value in values:
6.141 + if not value:
6.142 + ref = None
6.143 + else:
6.144 + ref, aliased_name = self.resolve_reference(path, value)
6.145 +
6.146 + l.add(ref or Reference("<var>"))
6.147 +
6.148 + return_values[path] = l
6.149 +
6.150 + # Replace the original values.
6.151 +
6.152 + self.return_values = return_values
6.153 +
6.154 + def resolve_reference(self, path, name_ref):
6.155 +
6.156 + """
6.157 + Within the namespace 'path', resolve the given 'name_ref', returning any
6.158 + initialised reference, along with any aliased name information.
6.159 + """
6.160 +
6.161 + const_accesses = self.const_accesses.get(path)
6.162 +
6.163 + initialised_ref = None
6.164 + aliased_name = None
6.165 + no_reference = None, None
6.166 +
6.167 + # Unwrap invocations.
6.168 +
6.169 + if isinstance(name_ref, InvocationRef):
6.170 + invocation = True
6.171 + name_ref = name_ref.name_ref
6.172 + else:
6.173 + invocation = False
6.174 +
6.175 + # Obtain a usable reference from names or constants.
6.176 +
6.177 + if isinstance(name_ref, ResolvedNameRef):
6.178 + if not name_ref.reference():
6.179 + return no_reference
6.180 + ref = name_ref.reference()
6.181 +
6.182 + # Obtain a reference from instances.
6.183 +
6.184 + elif isinstance(name_ref, InstanceRef):
6.185 + if not name_ref.reference():
6.186 + return no_reference
6.187 + ref = name_ref.reference()
6.188 +
6.189 + # Resolve accesses that employ constants.
6.190 +
6.191 + elif isinstance(name_ref, AccessRef):
6.192 + ref = None
6.193 +
6.194 + if const_accesses:
6.195 + resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
6.196 + if resolved_access:
6.197 + objpath, ref, remaining_attrnames = resolved_access
6.198 + if remaining_attrnames:
6.199 + ref = None
6.200 +
6.201 + # Accesses that do not employ constants cannot be resolved,
6.202 + # but they may be resolvable later.
6.203 +
6.204 + if not ref:
6.205 +
6.206 + # Record the path used for tracking purposes
6.207 + # alongside original name, attribute and access
6.208 + # number details.
6.209 +
6.210 + aliased_name = path, name_ref.original_name, name_ref.attrnames, name_ref.number
6.211 +
6.212 + return None, aliased_name
6.213 +
6.214 + # Attempt to resolve a plain name reference.
6.215 +
6.216 + elif isinstance(name_ref, LocalNameRef):
6.217 + key = "%s.%s" % (path, name_ref.name)
6.218 + ref = self.name_references.get(key)
6.219 +
6.220 + # Accesses that do not refer to known static objects
6.221 + # cannot be resolved, but they may be resolvable later.
6.222 +
6.223 + if not ref:
6.224 +
6.225 + # Record the path used for tracking purposes
6.226 + # alongside original name, attribute and access
6.227 + # number details.
6.228 +
6.229 + aliased_name = path, name_ref.name, None, name_ref.number
6.230 +
6.231 + return None, aliased_name
6.232 +
6.233 + ref = self.get_resolved_object(ref.get_origin())
6.234 + if not ref:
6.235 + return no_reference
6.236 +
6.237 + elif isinstance(name_ref, NameRef):
6.238 + key = "%s.%s" % (path, name_ref.name)
6.239 + ref = self.name_references.get(key)
6.240 +
6.241 + ref = ref and self.get_resolved_object(ref.get_origin())
6.242 + if not ref:
6.243 + return no_reference
6.244 +
6.245 + else:
6.246 + return no_reference
6.247 +
6.248 + # Resolve any hidden dependencies involving external objects
6.249 + # or unresolved names referring to globals or built-ins.
6.250 +
6.251 + if ref.has_kind("<depends>"):
6.252 + ref = self.importer.identify(ref.get_origin())
6.253 +
6.254 + # Convert class invocations to instances.
6.255 +
6.256 + if ref and invocation or ref.has_kind("<invoke>"):
6.257 + ref = self.convert_invocation(ref)
6.258 +
6.259 + if ref and not ref.has_kind("<var>"):
6.260 + initialised_ref = ref
6.261 +
6.262 + return initialised_ref, aliased_name
6.263 +
6.264 def resolve_literals(self):
6.265
6.266 "Resolve constant value types."