# HG changeset patch # User Paul Boddie # Date 1480031602 -3600 # Node ID 2ddfe1bdf7b742b909c4d31733fb50c351a13100 # Parent c048070e6a62f1b4dfc2299093c9e9b45fb435fb Propagate accessor kinds for accesses so that only functions and bound methods are directly referenced in invocation operations, with unknown accessors causing a dynamic attribute lookup and yielding an unbound method if the accessor proves to be a class. diff -r c048070e6a62 -r 2ddfe1bdf7b7 deducer.py --- a/deducer.py Thu Nov 24 23:42:19 2016 +0100 +++ b/deducer.py Fri Nov 25 00:53:22 2016 +0100 @@ -391,7 +391,7 @@ location " " name " " test " " test type " " base " " traversed attributes " " attributes to traverse " " context " " access method - " " static attribute + " " static attribute " " accessor kinds """ f_attrs = open(join(self.output, "attribute_plans"), "w") @@ -401,9 +401,11 @@ locations.sort() for location in locations: - name, test, test_type, base, traversed, traversal_modes, attrnames, \ + name, test, test_type, base, \ + traversed, traversal_modes, attrnames, \ context, context_test, \ - first_method, final_method, attr = self.access_plans[location] + first_method, final_method, \ + attr, accessor_kinds = self.access_plans[location] print >>f_attrs, encode_access_location(location), \ name or "{}", \ @@ -413,7 +415,8 @@ ".".join(traversal_modes) or "{}", \ ".".join(attrnames) or "{}", \ context, context_test, \ - first_method, final_method, attr or "{}" + first_method, final_method, attr or "{}", \ + ",".join(accessor_kinds) finally: f_attrs.close() @@ -2099,6 +2102,10 @@ (base and "base" or "original-accessor") or \ "final-accessor" - return name, test, test_type, base, traversed, traversal_modes, remaining, context, context_test, first_method, final_method, origin + return name, test, test_type, base, \ + traversed, traversal_modes, remaining, \ + context, context_test, \ + first_method, final_method, \ + origin, accessor_kinds # vim: tabstop=4 expandtab shiftwidth=4 diff -r c048070e6a62 -r 2ddfe1bdf7b7 optimiser.py --- a/optimiser.py Thu Nov 24 23:42:19 2016 +0100 +++ b/optimiser.py Fri Nov 25 00:53:22 2016 +0100 @@ -57,6 +57,7 @@ # Specific attribute access information. self.access_instructions = {} + self.accessor_kinds = {} # Object structure information. @@ -343,8 +344,11 @@ # Obtain the access details. - name, test, test_type, base, traversed, traversal_modes, \ - attrnames, context, context_test, first_method, final_method, origin = access_plan + name, test, test_type, base, \ + traversed, traversal_modes, attrnames, \ + context, context_test, \ + first_method, final_method, \ + origin, accessor_kinds = access_plan instructions = [] emit = instructions.append @@ -539,6 +543,7 @@ emit(accessor) self.access_instructions[access_location] = instructions + self.accessor_kinds[access_location] = accessor_kinds def get_ambiguity_for_attributes(self, attrnames): diff -r c048070e6a62 -r 2ddfe1bdf7b7 tests/methods_unbound.py --- a/tests/methods_unbound.py Thu Nov 24 23:42:19 2016 +0100 +++ b/tests/methods_unbound.py Fri Nov 25 00:53:22 2016 +0100 @@ -1,15 +1,39 @@ class C: + def __init__(self): + self.a = 1 def m(self, x): return x def f(obj, i): if i: - return obj.m(i) + return obj.m(i) # should cause access to an unbound method + else: + return obj.m + +def g(obj, i): + obj.a # only provided by instances of C + if i: + return obj.m(i) # should use the method directly since obj is an instance else: return obj.m c = C() -#print f(C, 1) # NOTE: Need to raise and handle error. + +try: + print f(C, 1) # fails +except UnboundMethodInvocation: + print "f(C, 1): Unbound method is not callable." + fn = f(C, 0) -print get_using(fn, c)(2) # 2 -print get_using(f(C, 0), c)(2) # 2 + +try: + print fn(2) # fails +except UnboundMethodInvocation: + print "fn(2): Unbound method is not callable." + +print get_using(fn, c)(2) # 2 +print get_using(f(C, 0), c)(2) # 2 + +#print g(C, 1) # should fail with an error caused by a guard +print g(c, 1) # 1 +print g(c, 0)(3) # 3 diff -r c048070e6a62 -r 2ddfe1bdf7b7 translator.py --- a/translator.py Thu Nov 24 23:42:19 2016 +0100 +++ b/translator.py Fri Nov 25 00:53:22 2016 +0100 @@ -55,7 +55,8 @@ "An abstract translation result mix-in." - pass + def get_accessor_kinds(self): + return None class ReturnRef(TranslationResult): @@ -156,9 +157,10 @@ "A translation result for an attribute access." - def __init__(self, s, refs): + def __init__(self, s, refs, accessor_kinds): Expression.__init__(self, s) self.refs = refs + self.accessor_kinds = accessor_kinds def get_origin(self): return self.refs and len(self.refs) == 1 and first(self.refs).get_origin() @@ -171,6 +173,9 @@ return True return False + def get_accessor_kinds(self): + return self.accessor_kinds + def __repr__(self): return "AttrResult(%r, %r)" % (self.s, self.get_origin()) @@ -653,7 +658,7 @@ out = "(\n%s\n)" % ",\n".join(output) del self.attrs[0] - return AttrResult(out, refs) + return AttrResult(out, refs, self.get_accessor_kinds(location)) def get_referenced_attributes(self, location): @@ -668,6 +673,12 @@ refs.append(attr) return refs + def get_accessor_kinds(self, location): + + "Return the accessor kinds for 'location'." + + return self.optimiser.accessor_kinds[location] + def get_access_location(self, name): """ @@ -869,12 +880,24 @@ elif objpath: parameters = self.importer.function_parameters.get(objpath) + + # Class invocation involves instantiators. + if expr.has_kind(""): target = encode_instantiator_pointer(objpath) target_structure = encode_initialiser_pointer(objpath) + + # Only plain functions and bound methods employ function pointers. + elif expr.has_kind(""): - target = encode_function_pointer(objpath) - target_structure = encode_path(objpath) + + # Test for functions and methods. + + accessor_kinds = expr.get_accessor_kinds() + + if not self.is_method(objpath) or accessor_kinds and len(accessor_kinds) == 1 and first(accessor_kinds) == "": + target = encode_function_pointer(objpath) + target_structure = encode_path(objpath) # Other targets are retrieved at run-time.