1.1 --- a/common.py Mon Feb 06 22:30:47 2017 +0100
1.2 +++ b/common.py Tue Feb 07 23:37:40 2017 +0100
1.3 @@ -114,7 +114,7 @@
1.4 # Retain the assignment value expression and track invocations.
1.5
1.6 self.in_assignment = None
1.7 - self.in_invocation = False
1.8 + self.in_invocation = None
1.9
1.10 # Attribute chain state management.
1.11
1.12 @@ -725,7 +725,7 @@
1.13 self.chain_assignment.append(self.in_assignment)
1.14 self.chain_invocation.append(self.in_invocation)
1.15 self.in_assignment = None
1.16 - self.in_invocation = False
1.17 + self.in_invocation = None
1.18
1.19 def restore_attribute_chain(self, attrs):
1.20
2.1 --- a/deducer.py Mon Feb 06 22:30:47 2017 +0100
2.2 +++ b/deducer.py Tue Feb 07 23:37:40 2017 +0100
2.3 @@ -90,7 +90,8 @@
2.4 # Accesses that are assignments or invocations.
2.5
2.6 self.reference_assignments = set()
2.7 - self.reference_invocations = set()
2.8 + self.reference_invocations = {}
2.9 + self.reference_invocations_unsuitable = {}
2.10
2.11 # Map locations to types, constrained indicators and attributes.
2.12
2.13 @@ -104,6 +105,7 @@
2.14 self.access_constrained = set()
2.15 self.referenced_attrs = {}
2.16 self.referenced_objects = {}
2.17 + self.accessor_invocation_types = {}
2.18
2.19 # Details of access operations.
2.20
2.21 @@ -139,6 +141,7 @@
2.22 self.init_aliases()
2.23 self.modify_mutated_attributes()
2.24 self.identify_references()
2.25 + self.classify_invocations()
2.26 self.classify_accessors()
2.27 self.classify_accesses()
2.28 self.initialise_access_plans()
2.29 @@ -346,6 +349,7 @@
2.30 f_attrs = open(join(self.output, "attributes"), "w")
2.31 f_tests = open(join(self.output, "tests"), "w")
2.32 f_warnings = open(join(self.output, "attribute_warnings"), "w")
2.33 + f_unsuitable = open(join(self.output, "invocation_warnings"), "w")
2.34
2.35 try:
2.36 locations = self.referenced_attrs.keys()
2.37 @@ -383,6 +387,15 @@
2.38 print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \
2.39 test_type and "-".join(test_type) or "untested", sorted_output(all_accessed_attrs)
2.40
2.41 + # Write details of potentially unsuitable invocation
2.42 + # occurrences.
2.43 +
2.44 + unsuitable = self.reference_invocations_unsuitable.get(location)
2.45 + if unsuitable:
2.46 + unsuitable = map(str, unsuitable)
2.47 + unsuitable.sort()
2.48 + print >>f_unsuitable, encode_access_location(location), ", ".join(unsuitable)
2.49 +
2.50 else:
2.51 print >>f_warnings, encode_access_location(location)
2.52
2.53 @@ -391,6 +404,7 @@
2.54 f_attrs.close()
2.55 f_tests.close()
2.56 f_warnings.close()
2.57 + f_unsuitable.close()
2.58
2.59 def write_access_plans(self):
2.60
2.61 @@ -430,6 +444,55 @@
2.62 finally:
2.63 f_attrs.close()
2.64
2.65 + def classify_invocations(self):
2.66 +
2.67 + """
2.68 + Classify invocations, suggesting guard opportunities where accessors
2.69 + providing attributes involved in invocations can be sufficiently
2.70 + determined, recording invocation candidates that would not be suitable.
2.71 + """
2.72 +
2.73 + for access_location, arguments in self.reference_invocations.items():
2.74 +
2.75 + # Obtain the possible attributes.
2.76 +
2.77 + referenced_attrs = self.referenced_attrs[access_location]
2.78 +
2.79 + if referenced_attrs:
2.80 + object_types = set()
2.81 + unsuitable = set()
2.82 +
2.83 + for attrtype, object_type, attr in referenced_attrs:
2.84 +
2.85 + # Obtain identifiable callables.
2.86 +
2.87 + objpath = attr.get_origin()
2.88 + if objpath:
2.89 + parameters = self.importer.function_parameters.get(objpath)
2.90 + if parameters:
2.91 + defaults = self.importer.function_defaults.get(objpath)
2.92 +
2.93 + # Determine whether the specified arguments are
2.94 + # compatible with the callable signature.
2.95 +
2.96 + if arguments >= len(parameters) - len(defaults) and \
2.97 + arguments <= len(parameters):
2.98 +
2.99 + object_types.add(object_type)
2.100 + else:
2.101 + unsuitable.add(attr)
2.102 +
2.103 + # Record the unsuitable candidates for the invocation.
2.104 +
2.105 + if unsuitable:
2.106 + self.reference_invocations_unsuitable[access_location] = unsuitable
2.107 +
2.108 + # Record the possible accessor types for the invocation.
2.109 +
2.110 + for accessor_location in self.get_accessors_for_access(access_location):
2.111 + init_item(self.accessor_invocation_types, accessor_location, set)
2.112 + self.accessor_invocation_types[accessor_location].update(object_types)
2.113 +
2.114 def classify_accessors(self):
2.115
2.116 "For each program location, classify accessors."
2.117 @@ -866,7 +929,7 @@
2.118 # Now only process assignments and invocations.
2.119
2.120 if invocation:
2.121 - self.reference_invocations.add(access_location)
2.122 + self.reference_invocations[access_location] = invocation
2.123 continue
2.124 elif not assignment:
2.125 continue
2.126 @@ -1576,7 +1639,9 @@
2.127
2.128 # Obtain attribute references for the access.
2.129
2.130 - attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]]
2.131 + attrs = []
2.132 + for _attrtype, object_type, attr in self.referenced_attrs[access_location]:
2.133 + attrs.append(attr)
2.134
2.135 # Separate the different attribute types.
2.136
2.137 @@ -1635,7 +1700,9 @@
2.138 # Alias references an attribute access.
2.139
2.140 if attrnames:
2.141 - attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]]
2.142 + attrs = []
2.143 + for attrtype, object_type, attr in self.referenced_attrs[access_location]:
2.144 + attrs.append(attr)
2.145 refs.update(attrs)
2.146
2.147 # Alias references a name, not an access.
2.148 @@ -1739,15 +1806,26 @@
2.149 From the given 'class_types', identify methods for the given
2.150 'attrnames' that are being invoked, returning a filtered collection of
2.151 class types.
2.152 +
2.153 + This method may be used to remove class types from consideration where
2.154 + their attributes are methods that are directly invoked: method
2.155 + invocations must involve instance accessors.
2.156 """
2.157
2.158 to_filter = set()
2.159
2.160 for class_type in class_types:
2.161 for attrname in attrnames:
2.162 +
2.163 + # Attempt to obtain a class attribute of the given name. This
2.164 + # may return an attribute provided by an ancestor class.
2.165 +
2.166 ref = self.importer.get_class_attribute(class_type, attrname)
2.167 parent_class = ref and ref.parent()
2.168
2.169 + # If such an attribute is a method and would be available on
2.170 + # the given class, record the class for filtering.
2.171 +
2.172 if ref and ref.has_kind("<function>") and (
2.173 parent_class == class_type or
2.174 class_type in self.descendants[parent_class]):
2.175 @@ -1824,6 +1902,8 @@
2.176
2.177 return attrs
2.178
2.179 + # Attribute access plan formulation.
2.180 +
2.181 class_tests = (
2.182 ("guarded", "specific", "type"),
2.183 ("guarded", "common", "type"),
3.1 --- a/encoders.py Mon Feb 06 22:30:47 2017 +0100
3.2 +++ b/encoders.py Tue Feb 07 23:37:40 2017 +0100
3.3 @@ -78,7 +78,7 @@
3.4
3.5 def encode_modifiers(modifiers):
3.6
3.7 - "Encode assignment details from 'modifiers'."
3.8 + "Encode assignment and invocation details from 'modifiers'."
3.9
3.10 all_modifiers = []
3.11 for t in modifiers:
3.12 @@ -87,16 +87,38 @@
3.13
3.14 def encode_modifier_term(t):
3.15
3.16 - "Encode modifier 't' representing assignment status."
3.17 + "Encode modifier 't' representing an assignment or an invocation."
3.18
3.19 assignment, invocation = t
3.20 - return assignment and "=" or invocation and "!" or "_"
3.21 + if assignment:
3.22 + return "="
3.23 + elif invocation is not None:
3.24 + return "(%d)" % invocation
3.25 + else:
3.26 + return "_"
3.27
3.28 -def decode_modifier_term(s):
3.29 +def decode_modifiers(s):
3.30 +
3.31 + "Decode 's' containing modifiers."
3.32 +
3.33 + i = 0
3.34 + end = len(s)
3.35
3.36 - "Decode modifier term 's' representing assignment status."
3.37 + modifiers = []
3.38
3.39 - return (s == "=", s == "!")
3.40 + while i < end:
3.41 + if s[i] == "=":
3.42 + modifiers.append((True, None))
3.43 + i += 1
3.44 + elif s[i] == "(":
3.45 + j = s.index(")", i)
3.46 + modifiers.append((False, int(s[i+1:j])))
3.47 + i = j + 1
3.48 + else:
3.49 + modifiers.append((False, None))
3.50 + i += 1
3.51 +
3.52 + return modifiers
3.53
3.54
3.55
4.1 --- a/inspector.py Mon Feb 06 22:30:47 2017 +0100
4.2 +++ b/inspector.py Tue Feb 07 23:37:40 2017 +0100
4.3 @@ -438,7 +438,7 @@
4.4 # Record attribute usage in the tracker, and record the branch
4.5 # information for the access.
4.6
4.7 - branches = tracker.use_attribute(name, attrname, self.in_invocation, assignment)
4.8 + branches = tracker.use_attribute(name, attrname, self.in_invocation is not None, assignment)
4.9
4.10 if not branches:
4.11 raise InspectError("Name %s is accessed using %s before an assignment." % (
4.12 @@ -746,12 +746,12 @@
4.13 # target of an invocation, potentially affecting attribute accesses.
4.14
4.15 in_invocation = self.in_invocation
4.16 - self.in_invocation = True
4.17 + self.in_invocation = len(n.args)
4.18
4.19 # Process the expression, obtaining any identified reference.
4.20
4.21 name_ref = self.process_structure_node(n.node)
4.22 - self.in_invocation = False
4.23 + self.in_invocation = None
4.24
4.25 # Process the arguments.
4.26
4.27 @@ -885,7 +885,7 @@
4.28
4.29 if branches:
4.30 self.record_branches_for_access(branches, n.name, None)
4.31 - access_number = self.record_access_details(n.name, None, False, False)
4.32 + access_number = self.record_access_details(n.name, None, None, None)
4.33 return LocalNameRef(n.name, access_number)
4.34
4.35 # Possible global or built-in name.
4.36 @@ -1147,7 +1147,7 @@
4.37
4.38 """
4.39 For the given 'name' and 'attrnames', record an access indicating
4.40 - whether 'assignment' is occurring.
4.41 + whether an 'assignment' or an 'invocation' is occurring.
4.42
4.43 These details correspond to accesses otherwise recorded by the attribute
4.44 accessor and attribute access dictionaries.
5.1 --- a/modules.py Mon Feb 06 22:30:47 2017 +0100
5.2 +++ b/modules.py Tue Feb 07 23:37:40 2017 +0100
5.3 @@ -22,7 +22,7 @@
5.4
5.5 from common import get_builtin_class, get_builtin_module, init_item, \
5.6 remove_items, CommonModule
5.7 -from encoders import decode_modifier_term, decode_usage, encode_modifiers, encode_usage
5.8 +from encoders import decode_modifiers, decode_usage, encode_modifiers, encode_usage
5.9 from referencing import decode_reference, Reference
5.10 from results import ResolvedNameRef
5.11 import sys
5.12 @@ -605,7 +605,7 @@
5.13 access = name, attrnames
5.14 init_item(self.attr_access_modifiers, objpath, dict)
5.15 init_item(self.attr_access_modifiers[objpath], access, list)
5.16 - modifiers = [decode_modifier_term(s) for s in value]
5.17 + modifiers = decode_modifiers(value)
5.18 self.attr_access_modifiers[objpath][access] = modifiers
5.19 line = f.readline().rstrip()
5.20
6.1 --- a/results.py Mon Feb 06 22:30:47 2017 +0100
6.2 +++ b/results.py Tue Feb 07 23:37:40 2017 +0100
6.3 @@ -36,6 +36,9 @@
6.4 def references(self):
6.5 return None
6.6
6.7 + def unsuitable_invocations(self):
6.8 + return None
6.9 +
6.10 def get_name(self):
6.11 return None
6.12
7.1 --- a/translator.py Mon Feb 06 22:30:47 2017 +0100
7.2 +++ b/translator.py Tue Feb 07 23:37:40 2017 +0100
7.3 @@ -91,9 +91,13 @@
7.4
7.5 "A reference to a name in the translation."
7.6
7.7 - def __init__(self, name, ref, expr=None, parameter=None):
7.8 + def __init__(self, name, ref, expr=None, parameter=None, unsuitable=None):
7.9 results.ResolvedNameRef.__init__(self, name, ref, expr)
7.10 self.parameter = parameter
7.11 + self.unsuitable = unsuitable
7.12 +
7.13 + def unsuitable_invocations(self):
7.14 + return self.unsuitable
7.15
7.16 def __str__(self):
7.17
7.18 @@ -189,14 +193,18 @@
7.19
7.20 "A translation result for an attribute access."
7.21
7.22 - def __init__(self, instructions, refs, accessor_kinds):
7.23 + def __init__(self, instructions, refs, accessor_kinds, unsuitable):
7.24 InstructionSequence.__init__(self, instructions)
7.25 self.refs = refs
7.26 self.accessor_kinds = accessor_kinds
7.27 + self.unsuitable = unsuitable
7.28
7.29 def references(self):
7.30 return self.refs
7.31
7.32 + def unsuitable_invocations(self):
7.33 + return self.unsuitable
7.34 +
7.35 def get_origin(self):
7.36 return self.refs and len(self.refs) == 1 and first(self.refs).get_origin()
7.37
7.38 @@ -724,8 +732,9 @@
7.39 name_ref = attr_expr and attr_expr.is_name() and attr_expr
7.40 name = name_ref and self.get_name_for_tracking(name_ref.name, name_ref and name_ref.final()) or None
7.41
7.42 - location = self.get_access_location(name)
7.43 + location = self.get_access_location(name, self.attrs)
7.44 refs = self.get_referenced_attributes(location)
7.45 + unsuitable = self.get_referenced_attribute_invocations(location)
7.46
7.47 # Generate access instructions.
7.48
7.49 @@ -768,13 +777,13 @@
7.50 self.record_temp(temp_subs[sub])
7.51
7.52 del self.attrs[0]
7.53 - return AttrResult(output, refs, self.get_accessor_kinds(location))
7.54 + return AttrResult(output, refs, self.get_accessor_kinds(location), unsuitable)
7.55
7.56 def get_referenced_attributes(self, location):
7.57
7.58 """
7.59 Convert 'location' to the form used by the deducer and retrieve any
7.60 - identified attribute.
7.61 + identified attributes.
7.62 """
7.63
7.64 access_location = self.deducer.const_accesses.get(location)
7.65 @@ -783,17 +792,27 @@
7.66 refs.append(attr)
7.67 return refs
7.68
7.69 + def get_referenced_attribute_invocations(self, location):
7.70 +
7.71 + """
7.72 + Convert 'location' to the form used by the deducer and retrieve any
7.73 + identified attribute invocation details.
7.74 + """
7.75 +
7.76 + access_location = self.deducer.const_accesses.get(location)
7.77 + return self.deducer.reference_invocations_unsuitable.get(access_location or location)
7.78 +
7.79 def get_accessor_kinds(self, location):
7.80
7.81 "Return the accessor kinds for 'location'."
7.82
7.83 return self.optimiser.accessor_kinds[location]
7.84
7.85 - def get_access_location(self, name):
7.86 + def get_access_location(self, name, attrnames=None):
7.87
7.88 """
7.89 - Using the current namespace and the given 'name', return the access
7.90 - location.
7.91 + Using the current namespace, the given 'name', and the 'attrnames'
7.92 + employed in an access, return the access location.
7.93 """
7.94
7.95 path = self.get_path_for_access()
7.96 @@ -801,7 +820,7 @@
7.97 # Get the location used by the deducer and optimiser and find any
7.98 # recorded access.
7.99
7.100 - attrnames = ".".join(self.attrs)
7.101 + attrnames = attrnames and ".".join(self.attrs)
7.102 access_number = self.get_access_number(path, name, attrnames)
7.103 self.update_access_number(path, name, attrnames)
7.104 return (path, name, attrnames, access_number)
7.105 @@ -1203,30 +1222,16 @@
7.106 # Other targets are retrieved at run-time.
7.107
7.108 else:
7.109 - refs = expr.references()
7.110 -
7.111 - # Attempt to test the number of arguments and warn about possible
7.112 - # invocation problems.
7.113 -
7.114 - if refs:
7.115 - for ref in refs:
7.116 + unsuitable = expr.unsuitable_invocations()
7.117 +
7.118 + if unsuitable:
7.119 + for ref in unsuitable:
7.120 _objpath = ref.get_origin()
7.121 - _parameters = self.importer.function_parameters.get(_objpath)
7.122 -
7.123 - if _parameters is None:
7.124 - continue
7.125 -
7.126 - # Determine whether the possible target has a different
7.127 - # number of parameters to the number of arguments given.
7.128 -
7.129 - num_parameters = len(_parameters)
7.130 - _defaults = len(self.importer.function_defaults.get(_objpath, []))
7.131 -
7.132 - if len(n.args) < num_parameters - _defaults or len(n.args) > num_parameters:
7.133 - print "In %s, at line %d, inappropriate number of " \
7.134 - "arguments given. Need %d arguments to call %s." % (
7.135 - self.get_namespace_path(), n.lineno, num_parameters,
7.136 - _objpath)
7.137 + num_parameters = len(self.importer.function_parameters[_objpath])
7.138 + print "In %s, at line %d, inappropriate number of " \
7.139 + "arguments given. Need %d arguments to call %s." % (
7.140 + self.get_namespace_path(), n.lineno, num_parameters,
7.141 + _objpath)
7.142
7.143 # Arguments are presented in a temporary frame array with any context
7.144 # always being the first argument. Where it would be unused, it may be
7.145 @@ -1503,11 +1508,16 @@
7.146 parameter = n.name == "self" and self.in_method() or \
7.147 parameters and n.name in parameters
7.148
7.149 + # Find any invocation details.
7.150 +
7.151 + location = self.get_access_location(n.name)
7.152 + unsuitable = self.get_referenced_attribute_invocations(location)
7.153 +
7.154 # Qualified names are used for resolved static references or for
7.155 # static namespace members. The reference should be configured to return
7.156 # such names.
7.157
7.158 - return TrResolvedNameRef(n.name, ref, expr=expr, parameter=parameter)
7.159 + return TrResolvedNameRef(n.name, ref, expr=expr, parameter=parameter, unsuitable=unsuitable)
7.160
7.161 def process_not_node(self, n):
7.162