1.1 --- a/deducer.py Mon Feb 06 22:30:47 2017 +0100
1.2 +++ b/deducer.py Tue Feb 07 23:37:40 2017 +0100
1.3 @@ -90,7 +90,8 @@
1.4 # Accesses that are assignments or invocations.
1.5
1.6 self.reference_assignments = set()
1.7 - self.reference_invocations = set()
1.8 + self.reference_invocations = {}
1.9 + self.reference_invocations_unsuitable = {}
1.10
1.11 # Map locations to types, constrained indicators and attributes.
1.12
1.13 @@ -104,6 +105,7 @@
1.14 self.access_constrained = set()
1.15 self.referenced_attrs = {}
1.16 self.referenced_objects = {}
1.17 + self.accessor_invocation_types = {}
1.18
1.19 # Details of access operations.
1.20
1.21 @@ -139,6 +141,7 @@
1.22 self.init_aliases()
1.23 self.modify_mutated_attributes()
1.24 self.identify_references()
1.25 + self.classify_invocations()
1.26 self.classify_accessors()
1.27 self.classify_accesses()
1.28 self.initialise_access_plans()
1.29 @@ -346,6 +349,7 @@
1.30 f_attrs = open(join(self.output, "attributes"), "w")
1.31 f_tests = open(join(self.output, "tests"), "w")
1.32 f_warnings = open(join(self.output, "attribute_warnings"), "w")
1.33 + f_unsuitable = open(join(self.output, "invocation_warnings"), "w")
1.34
1.35 try:
1.36 locations = self.referenced_attrs.keys()
1.37 @@ -383,6 +387,15 @@
1.38 print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \
1.39 test_type and "-".join(test_type) or "untested", sorted_output(all_accessed_attrs)
1.40
1.41 + # Write details of potentially unsuitable invocation
1.42 + # occurrences.
1.43 +
1.44 + unsuitable = self.reference_invocations_unsuitable.get(location)
1.45 + if unsuitable:
1.46 + unsuitable = map(str, unsuitable)
1.47 + unsuitable.sort()
1.48 + print >>f_unsuitable, encode_access_location(location), ", ".join(unsuitable)
1.49 +
1.50 else:
1.51 print >>f_warnings, encode_access_location(location)
1.52
1.53 @@ -391,6 +404,7 @@
1.54 f_attrs.close()
1.55 f_tests.close()
1.56 f_warnings.close()
1.57 + f_unsuitable.close()
1.58
1.59 def write_access_plans(self):
1.60
1.61 @@ -430,6 +444,55 @@
1.62 finally:
1.63 f_attrs.close()
1.64
1.65 + def classify_invocations(self):
1.66 +
1.67 + """
1.68 + Classify invocations, suggesting guard opportunities where accessors
1.69 + providing attributes involved in invocations can be sufficiently
1.70 + determined, recording invocation candidates that would not be suitable.
1.71 + """
1.72 +
1.73 + for access_location, arguments in self.reference_invocations.items():
1.74 +
1.75 + # Obtain the possible attributes.
1.76 +
1.77 + referenced_attrs = self.referenced_attrs[access_location]
1.78 +
1.79 + if referenced_attrs:
1.80 + object_types = set()
1.81 + unsuitable = set()
1.82 +
1.83 + for attrtype, object_type, attr in referenced_attrs:
1.84 +
1.85 + # Obtain identifiable callables.
1.86 +
1.87 + objpath = attr.get_origin()
1.88 + if objpath:
1.89 + parameters = self.importer.function_parameters.get(objpath)
1.90 + if parameters:
1.91 + defaults = self.importer.function_defaults.get(objpath)
1.92 +
1.93 + # Determine whether the specified arguments are
1.94 + # compatible with the callable signature.
1.95 +
1.96 + if arguments >= len(parameters) - len(defaults) and \
1.97 + arguments <= len(parameters):
1.98 +
1.99 + object_types.add(object_type)
1.100 + else:
1.101 + unsuitable.add(attr)
1.102 +
1.103 + # Record the unsuitable candidates for the invocation.
1.104 +
1.105 + if unsuitable:
1.106 + self.reference_invocations_unsuitable[access_location] = unsuitable
1.107 +
1.108 + # Record the possible accessor types for the invocation.
1.109 +
1.110 + for accessor_location in self.get_accessors_for_access(access_location):
1.111 + init_item(self.accessor_invocation_types, accessor_location, set)
1.112 + self.accessor_invocation_types[accessor_location].update(object_types)
1.113 +
1.114 def classify_accessors(self):
1.115
1.116 "For each program location, classify accessors."
1.117 @@ -866,7 +929,7 @@
1.118 # Now only process assignments and invocations.
1.119
1.120 if invocation:
1.121 - self.reference_invocations.add(access_location)
1.122 + self.reference_invocations[access_location] = invocation
1.123 continue
1.124 elif not assignment:
1.125 continue
1.126 @@ -1576,7 +1639,9 @@
1.127
1.128 # Obtain attribute references for the access.
1.129
1.130 - attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]]
1.131 + attrs = []
1.132 + for _attrtype, object_type, attr in self.referenced_attrs[access_location]:
1.133 + attrs.append(attr)
1.134
1.135 # Separate the different attribute types.
1.136
1.137 @@ -1635,7 +1700,9 @@
1.138 # Alias references an attribute access.
1.139
1.140 if attrnames:
1.141 - attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]]
1.142 + attrs = []
1.143 + for attrtype, object_type, attr in self.referenced_attrs[access_location]:
1.144 + attrs.append(attr)
1.145 refs.update(attrs)
1.146
1.147 # Alias references a name, not an access.
1.148 @@ -1739,15 +1806,26 @@
1.149 From the given 'class_types', identify methods for the given
1.150 'attrnames' that are being invoked, returning a filtered collection of
1.151 class types.
1.152 +
1.153 + This method may be used to remove class types from consideration where
1.154 + their attributes are methods that are directly invoked: method
1.155 + invocations must involve instance accessors.
1.156 """
1.157
1.158 to_filter = set()
1.159
1.160 for class_type in class_types:
1.161 for attrname in attrnames:
1.162 +
1.163 + # Attempt to obtain a class attribute of the given name. This
1.164 + # may return an attribute provided by an ancestor class.
1.165 +
1.166 ref = self.importer.get_class_attribute(class_type, attrname)
1.167 parent_class = ref and ref.parent()
1.168
1.169 + # If such an attribute is a method and would be available on
1.170 + # the given class, record the class for filtering.
1.171 +
1.172 if ref and ref.has_kind("<function>") and (
1.173 parent_class == class_type or
1.174 class_type in self.descendants[parent_class]):
1.175 @@ -1824,6 +1902,8 @@
1.176
1.177 return attrs
1.178
1.179 + # Attribute access plan formulation.
1.180 +
1.181 class_tests = (
1.182 ("guarded", "specific", "type"),
1.183 ("guarded", "common", "type"),