# HG changeset patch # User Paul Boddie # Date 1475880193 -7200 # Node ID c7ddfc4525dae5f2da99e41803955d72631494d1 # Parent c7d3a8bf2cdc2923a60b27d16da56a74431903a0 Added some support for eliminating accessor class types where the provided attributes are invoked and are unbound methods. This uses a more sophisticated method involving usage observations that incorporate invocation information, permitting classes as accessors if paths through the code support them, even if other paths require instances as accessors to invoke methods. diff -r c7d3a8bf2cdc -r c7ddfc4525da common.py --- a/common.py Sat Oct 08 00:34:34 2016 +0200 +++ b/common.py Sat Oct 08 00:43:13 2016 +0200 @@ -811,7 +811,7 @@ else: return "%s.%s" % (path, name) -# Type deduction for usage. +# Usage-related functions. def get_types_for_usage(attrnames, objects): @@ -826,6 +826,17 @@ types.append(name) return types +def get_invoked_attributes(usage): + + "Obtain invoked attribute from the given 'usage'." + + invoked = [] + if usage: + for attrname, invocation in usage: + if invocation: + invoked.append(attrname) + return invoked + # Useful data. predefined_constants = "False", "None", "NotImplemented", "True" diff -r c7d3a8bf2cdc -r c7ddfc4525da deducer.py --- a/deducer.py Sat Oct 08 00:34:34 2016 +0200 +++ b/deducer.py Sat Oct 08 00:43:13 2016 +0200 @@ -20,8 +20,8 @@ """ from common import first, get_attrname_from_location, get_attrnames, \ - get_name_path, init_item, sorted_output, \ - CommonOutput + get_invoked_attributes, get_name_path, init_item, \ + sorted_output, CommonOutput from encoders import encode_attrnames, encode_access_location, \ encode_constrained, encode_location, encode_usage, \ get_kinds, test_for_kinds, test_for_type @@ -1394,7 +1394,9 @@ constrained, constrained_specific) = self.get_target_types(accessor_location, usage) - self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific) + invocations = get_invoked_attributes(usage) + + self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific, invocations) def record_types_for_attribute(self, access_location, attrname): @@ -1543,7 +1545,7 @@ return None def record_reference_types(self, location, class_types, instance_types, - module_types, constrained, constrained_specific=False): + module_types, constrained, constrained_specific=False, invocations=None): """ Associate attribute provider types with the given 'location', consisting @@ -1572,12 +1574,18 @@ # Instance-only and module types support only their own kinds as # accessors. + path, name, version, attrnames = location + + if invocations: + class_only_types = self.filter_for_invocations(class_types, invocations) + else: + class_only_types = class_types + # However, the nature of accessors can be further determined. # Any self variable may only refer to an instance. - path, name, version, attrnames = location if name != "self" or not self.in_method(path): - self.accessor_class_types[location].update(class_types) + self.accessor_class_types[location].update(class_only_types) if not constrained_specific: self.accessor_instance_types[location].update(class_types) @@ -1590,6 +1598,30 @@ if constrained: self.accessor_constrained.add(location) + def filter_for_invocations(self, class_types, attrnames): + + """ + From the given 'class_types', identify methods for the given + 'attrnames' that are being invoked, returning a filtered collection of + class types. + """ + + to_filter = set() + + for class_type in class_types: + for attrname in attrnames: + ref = self.importer.get_class_attribute(class_type, attrname) + parent_class = ref and ref.parent() + + if ref and ref.has_kind("") and ( + parent_class == class_type or + class_type in self.descendants[parent_class]): + + to_filter.add(class_type) + break + + return set(class_types).difference(to_filter) + def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained): """ diff -r c7d3a8bf2cdc -r c7ddfc4525da tests/methods_bound.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/methods_bound.py Sat Oct 08 00:43:13 2016 +0200 @@ -0,0 +1,14 @@ +class C: + def m(self, x): + return x + +def f(obj, i): + if i: + return obj.m(i) + else: + return obj.m + +c = C() +result1 = f(c, 1) +fn = f(c, 0) +result2 = fn(2)