# HG changeset patch # User Paul Boddie # Date 1480095405 -3600 # Node ID b2748d715f7c1680d463ee2eaf07b0f1fd0fed68 # Parent 379ee212f343039386b8a5f31617202f92ffa10d Added guard generation for function parameters. diff -r 379ee212f343 -r b2748d715f7c deducer.py --- a/deducer.py Fri Nov 25 18:36:10 2016 +0100 +++ b/deducer.py Fri Nov 25 18:36:45 2016 +0100 @@ -25,7 +25,7 @@ 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 + get_kinds, test_label_for_kind, test_label_for_type from errors import DeduceError from os.path import join from referencing import combine_types, is_single_class_type, separate_types, \ @@ -260,23 +260,25 @@ print >>f_warnings, encode_location(location), "; ".join(map(encode_usage, attrnames)) guard_test = self.accessor_guard_tests.get(location) + if guard_test: + guard_test_type, guard_test_arg = guard_test # Write specific type guard details. - if guard_test and guard_test.startswith("specific"): - print >>f_guards, encode_location(location), guard_test, \ - get_kinds(all_types)[0], \ + if guard_test and guard_test_type == "specific": + print >>f_guards, encode_location(location), "-".join(guard_test), \ + first(get_kinds(all_types)), \ sorted_output(all_types) # Write common type guard details. - elif guard_test and guard_test.startswith("common"): - print >>f_guards, encode_location(location), guard_test, \ - get_kinds(all_general_types)[0], \ + elif guard_test and guard_test_type == "common": + print >>f_guards, encode_location(location), "-".join(guard_test), \ + first(get_kinds(all_general_types)), \ sorted_output(all_general_types) print >>f_type_summary, encode_location(location), encode_constrained(constrained), \ - guard_test or "unguarded", sorted_output(all_general_types), len(all_types) + guard_test and "-".join(guard_test) or "unguarded", sorted_output(all_general_types), len(all_types) finally: f_type_summary.close() @@ -362,18 +364,18 @@ # Write the need to test at run time. - if test_type == "validate": - print >>f_tests, encode_access_location(location), test_type + if test_type[0] == "validate": + print >>f_tests, encode_access_location(location), "-".join(test_type) # Write any type checks for anonymous accesses. elif test_type and self.reference_test_accessor_type.get(location): - print >>f_tests, encode_access_location(location), test_type, \ + print >>f_tests, encode_access_location(location), "-".join(test_type[1:]), \ sorted_output(all_accessed_attrs), \ self.reference_test_accessor_type[location] print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \ - test_type or "untested", sorted_output(all_accessed_attrs) + test_type and "-".join(test_type) or "untested", sorted_output(all_accessed_attrs) else: print >>f_warnings, encode_access_location(location) @@ -409,7 +411,8 @@ print >>f_attrs, encode_access_location(location), \ name or "{}", \ - test, test_type or "{}", \ + test and "-".join(test) or "{}", \ + test_type or "{}", \ base or "{}", \ ".".join(traversed) or "{}", \ ".".join(traversal_modes) or "{}", \ @@ -473,16 +476,16 @@ # Record specific type guard details. if len(all_types) == 1: - self.accessor_guard_tests[location] = test_for_type("specific", first(all_types)) + self.accessor_guard_tests[location] = ("specific", test_label_for_type(first(all_types))) elif is_single_class_type(all_types): - self.accessor_guard_tests[location] = "specific-object" + self.accessor_guard_tests[location] = ("specific", "object") # Record common type guard details. elif len(all_general_types) == 1: - self.accessor_guard_tests[location] = test_for_type("common", first(all_types)) + self.accessor_guard_tests[location] = ("common", test_label_for_type(first(all_types))) elif is_single_class_type(all_general_types): - self.accessor_guard_tests[location] = "common-object" + self.accessor_guard_tests[location] = ("common", "object") # Otherwise, no convenient guard can be defined. @@ -581,15 +584,15 @@ if constrained: if single_accessor_type: - self.reference_test_types[location] = test_for_type("constrained-specific", first(all_accessor_types)) + self.reference_test_types[location] = ("constrained", "specific", test_label_for_type(first(all_accessor_types))) elif single_accessor_class_type: - self.reference_test_types[location] = "constrained-specific-object" + self.reference_test_types[location] = ("constrained", "specific", "object") elif single_accessor_general_type: - self.reference_test_types[location] = test_for_type("constrained-common", first(all_accessor_general_types)) + self.reference_test_types[location] = ("constrained", "common", test_label_for_type(first(all_accessor_general_types))) elif single_accessor_general_class_type: - self.reference_test_types[location] = "constrained-common-object" + self.reference_test_types[location] = ("constrained", "common", "object") else: - self.reference_test_types[location] = "constrained-many" + self.reference_test_types[location] = ("constrained", "many") # Suitably guarded accesses, where the nature of the # accessor can be guaranteed, do not require the attribute @@ -598,13 +601,13 @@ elif guarded and all_accessed_attrs.issubset(guard_attrs): if single_accessor_type: - self.reference_test_types[location] = test_for_type("guarded-specific", first(all_accessor_types)) + self.reference_test_types[location] = ("guarded", "specific", test_label_for_type(first(all_accessor_types))) elif single_accessor_class_type: - self.reference_test_types[location] = "guarded-specific-object" + self.reference_test_types[location] = ("guarded", "specific", "object") elif single_accessor_general_type: - self.reference_test_types[location] = test_for_type("guarded-common", first(all_accessor_general_types)) + self.reference_test_types[location] = ("guarded", "common", test_label_for_type(first(all_accessor_general_types))) elif single_accessor_general_class_type: - self.reference_test_types[location] = "guarded-common-object" + self.reference_test_types[location] = ("guarded", "common", "object") # Record the need to test the type of anonymous and # unconstrained accessors. @@ -614,9 +617,9 @@ if provider != '__builtins__.object': all_accessor_kinds = set(get_kinds(all_accessor_types)) if len(all_accessor_kinds) == 1: - test_type = test_for_kinds("specific", all_accessor_kinds) + test_type = ("test", "specific", first(all_accessor_kinds)) else: - test_type = "specific-object" + test_type = ("test", "specific", "object") self.reference_test_types[location] = test_type self.reference_test_accessor_type[location] = provider @@ -625,16 +628,16 @@ if provider != '__builtins__.object': all_accessor_kinds = set(get_kinds(all_accessor_general_types)) if len(all_accessor_kinds) == 1: - test_type = test_for_kinds("common", all_accessor_kinds) + test_type = ("test", "common", first(all_accessor_kinds)) else: - test_type = "common-object" + test_type = ("test", "common", "object") self.reference_test_types[location] = test_type self.reference_test_accessor_type[location] = provider # Record the need to test the identity of the attribute. else: - self.reference_test_types[location] = "validate" + self.reference_test_types[location] = ("validate",) def initialise_access_plans(self): @@ -1749,54 +1752,11 @@ return attrs - constrained_specific_tests = ( - "constrained-specific-instance", - "constrained-specific-type", - "constrained-specific-object", - ) - - constrained_common_tests = ( - "constrained-common-instance", - "constrained-common-type", - "constrained-common-object", - ) - - guarded_specific_tests = ( - "guarded-specific-instance", - "guarded-specific-type", - "guarded-specific-object", - ) - - guarded_common_tests = ( - "guarded-common-instance", - "guarded-common-type", - "guarded-common-object", - ) - - specific_tests = ( - "specific-instance", - "specific-type", - "specific-object", - ) - - common_tests = ( - "common-instance", - "common-type", - "common-object", - ) - class_tests = ( - "guarded-specific-type", - "guarded-common-type", - "specific-type", - "common-type", - ) - - class_or_instance_tests = ( - "guarded-specific-object", - "guarded-common-object", - "specific-object", - "common-object", + ("guarded", "specific", "type"), + ("guarded", "common", "type"), + ("test", "specific", "type"), + ("test", "common", "type"), ) def get_access_plan(self, location): @@ -1883,22 +1843,22 @@ # Usage of previously-generated guard and test details. - elif test in self.constrained_specific_tests: + elif test[:2] == ("constrained", "specific"): ref = first(accessor_types) - elif test in self.constrained_common_tests: + elif test[:2] == ("constrained", "common"): ref = first(accessor_general_types) - elif test in self.guarded_specific_tests: + elif test[:2] == ("guarded", "specific"): ref = first(accessor_types) - elif test in self.guarded_common_tests: + elif test[:2] == ("guarded", "common"): ref = first(accessor_general_types) # For attribute-based tests, tentatively identify a dynamic base. # Such tests allow single or multiple kinds of a type. - elif test in self.common_tests or test in self.specific_tests: + elif test[0] == "test" and test[1] in ("common", "specific"): dynamic_base = test_type # Static accessors. diff -r 379ee212f343 -r b2748d715f7c encoders.py --- a/encoders.py Fri Nov 25 18:36:10 2016 +0100 +++ b/encoders.py Fri Nov 25 18:36:45 2016 +0100 @@ -111,30 +111,17 @@ return map(lambda ref: ref.get_kind(), all_types) -def test_for_kind(prefix, kind): - - "Return a test condition identifier featuring 'prefix' and 'kind'." +def test_label_for_kind(kind): - return "%s-%s" % (prefix, kind == "" and "instance" or "type") + "Return the label used for 'kind' in test details." -def test_for_kinds(prefix, all_kinds): + return kind == "" and "instance" or "type" - """ - Return an identifier describing test conditions incorporating the given - 'prefix' and involving 'all_kinds', being a collection of object kinds. - """ - - return test_for_kind(prefix, first(all_kinds)) +def test_label_for_type(ref): -def test_for_type(prefix, ref): + "Return the label used for 'ref' in test details." - """ - Return an identifier describing a test condition incorporating the given - 'prefix' and involving 'ref', being a program type reference. The kind of - the reference is employed in the identifier. - """ - - return test_for_kind(prefix, ref.get_kind()) + return test_label_for_kind(ref.get_kind()) diff -r 379ee212f343 -r b2748d715f7c templates/ops.c --- a/templates/ops.c Fri Nov 25 18:36:10 2016 +0100 +++ b/templates/ops.c Fri Nov 25 18:36:45 2016 +0100 @@ -65,6 +65,16 @@ return __get_class(obj) == type ? obj : 0; } +__ref __test_specific_object(__ref obj, __ref type) +{ + return __test_specific_type(obj, type) || __test_specific_instance(obj, type) ? obj : 0; +} + +__ref __test_specific_type(__ref obj, __ref type) +{ + return obj == type ? obj : 0; +} + __ref __test_common_instance(__ref obj, int pos, int code) { return __HASATTR(__get_class(obj), pos, code) ? obj : 0; diff -r 379ee212f343 -r b2748d715f7c templates/ops.h --- a/templates/ops.h Fri Nov 25 18:36:10 2016 +0100 +++ b/templates/ops.h Fri Nov 25 18:36:45 2016 +0100 @@ -28,10 +28,12 @@ /* Attribute testing operations. */ +__ref __test_specific_instance(__ref obj, __ref type); +__ref __test_specific_object(__ref obj, __ref type); +__ref __test_specific_type(__ref obj, __ref type); __ref __test_common_instance(__ref obj, int pos, int code); __ref __test_common_object(__ref obj, int pos, int code); __ref __test_common_type(__ref obj, int pos, int code); -__ref __test_specific_instance(__ref obj, __ref type); /* Attribute testing and retrieval operations. */ diff -r 379ee212f343 -r b2748d715f7c tests/methods_unbound.py --- a/tests/methods_unbound.py Fri Nov 25 18:36:10 2016 +0100 +++ b/tests/methods_unbound.py Fri Nov 25 18:36:45 2016 +0100 @@ -34,6 +34,10 @@ 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 +try: + print g(C, 1) # should fail with an error caused by a guard +except TypeError: + print "g(C, 1): C is not a suitable argument." + print g(c, 1) # 1 print g(c, 0)(3) # 3 diff -r 379ee212f343 -r b2748d715f7c translator.py --- a/translator.py Fri Nov 25 18:36:10 2016 +0100 +++ b/translator.py Fri Nov 25 18:36:45 2016 +0100 @@ -245,9 +245,10 @@ self.in_try_finally = False self.in_try_except = False - # Attribute access counting. + # Attribute access and accessor counting. self.attr_accesses = {} + self.attr_accessors = {} def __repr__(self): return "TranslatedModule(%r, %r)" % (self.name, self.importer) @@ -710,6 +711,34 @@ init_item(self.attr_accesses[path], access, lambda: 0) self.attr_accesses[path][access] += 1 + def get_accessor_location(self, name): + + """ + Using the current namespace and the given 'name', return the accessor + location. + """ + + path = self.get_path_for_access() + + # Get the location used by the deducer and optimiser and find any + # recorded accessor. + + access_number = self.get_accessor_number(path, name) + self.update_accessor_number(path, name) + return (path, name, None, access_number) + + def get_accessor_number(self, path, name): + if self.attr_accessors.has_key(path) and self.attr_accessors[path].has_key(name): + return self.attr_accessors[path][name] + else: + return 0 + + def update_accessor_number(self, path, name): + if name: + init_item(self.attr_accessors, path, dict) + init_item(self.attr_accessors[path], name, lambda: 0) + self.attr_accessors[path][name] += 1 + def process_class_node(self, n): "Process the given class node 'n'." @@ -740,6 +769,34 @@ self.in_conditional = False self.function_target = 0 + # Process any guards defined for the parameters. + + for name in self.importer.function_parameters.get(function_name): + + # Get the accessor details and any guards defined for it. + + location = self.get_accessor_location(name) + test = self.deducer.accessor_guard_tests.get(location) + if test: + guard, guard_type = test + + if guard == "specific": + ref = first(self.deducer.accessor_all_types[location]) + argstr = "&%s" % encode_path(ref.get_origin()) + elif guard == "common": + ref = first(self.deducer.accessor_all_general_types[location]) + typeattr = encode_type_attribute(ref.get_origin()) + argstr = "%s, %s" % (encode_symbol("pos", typeattr), encode_symbol("code", typeattr)) + else: + continue + + # Write a test that raises a TypeError upon failure. + + self.writestmt("if (!__test_%s_%s(%s->value, %s)) __raise_type_error();" % ( + guard, guard_type, name, argstr)) + + # Produce the body and any additional return statement. + expr = self.process_structure_node(n.code) or PredefinedConstantRef("None") if not isinstance(expr, ReturnRef): self.writestmt("return %s;" % expr)