From ddb0c9e7b8104332382346d8a0c163db353fa501 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 11 Mar 2013 16:58:48 +0100 Subject: [PATCH] optimise calls on variables that reference builtin methods (e.g. pre-assigned unbound methods) --HG-- extra : rebase_source : 4aeefab40694c611bfdaa4ecf56a3f4f6cdf3d04 --- Cython/Compiler/Optimize.py | 128 +++++++++++++++++---------------- Cython/Compiler/Tests/TestVisitor.py | 6 +- Cython/Compiler/Visitor.py | 44 +++++++++--- tests/run/assigned_builtin_methods.pyx | 85 ++++++++++++++++++++++ 4 files changed, 190 insertions(+), 73 deletions(-) create mode 100644 tests/run/assigned_builtin_methods.pyx diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py index 01be0c0..2e97c7e 100644 --- a/Cython/Compiler/Optimize.py +++ b/Cython/Compiler/Optimize.py @@ -1856,7 +1856,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("dict", Builtin.dict_type, None) ]) - def _handle_simple_function_dict(self, node, pos_args): + def _handle_simple_function_dict(self, node, function, pos_args): """Replace dict(some_dict) by PyDict_Copy(some_dict). """ if len(pos_args) != 1: @@ -1876,7 +1876,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("list", Builtin.list_type, None) ]) - def _handle_simple_function_tuple(self, node, pos_args): + def _handle_simple_function_tuple(self, node, function, pos_args): """Replace tuple([...]) by a call to PyList_AsTuple. """ if len(pos_args) != 1: @@ -1902,7 +1902,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): exception_value = "((double)-1)", exception_check = True) - def _handle_simple_function_set(self, node, pos_args): + def _handle_simple_function_set(self, node, function, pos_args): if len(pos_args) == 1 and isinstance(pos_args[0], (ExprNodes.ListNode, ExprNodes.TupleNode)): # We can optimise set([x,y,z]) safely into a set literal, @@ -1923,7 +1923,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): return result return node - def _handle_simple_function_float(self, node, pos_args): + def _handle_simple_function_float(self, node, function, pos_args): """Transform float() into either a C type cast or a faster C function call. """ @@ -1952,7 +1952,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): utility_code = load_c_utility('pyobject_as_double'), py_name = "float") - def _handle_simple_function_bool(self, node, pos_args): + def _handle_simple_function_bool(self, node, function, pos_args): """Transform bool(x) into a type coercion to a boolean. """ if len(pos_args) == 0: @@ -2000,7 +2000,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): _ext_types_with_pysize = set(["cpython.array.array"]) - def _handle_simple_function_len(self, node, pos_args): + def _handle_simple_function_len(self, node, function, pos_args): """Replace len(char*) by the equivalent call to strlen(), len(Py_UNICODE) by the equivalent Py_UNICODE_strlen() and len(known_builtin_type) by an equivalent C-API call. @@ -2051,7 +2051,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("object", PyrexTypes.py_object_type, None) ]) - def _handle_simple_function_type(self, node, pos_args): + def _handle_simple_function_type(self, node, function, pos_args): """Replace type(o) by a macro call to Py_TYPE(o). """ if len(pos_args) != 1: @@ -2067,7 +2067,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("arg", PyrexTypes.py_object_type, None) ]) - def _handle_simple_function_isinstance(self, node, pos_args): + def _handle_simple_function_isinstance(self, node, function, pos_args): """Replace isinstance() checks against builtin types by the corresponding C-API call. """ @@ -2122,7 +2122,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): test_node = UtilNodes.EvalWithTempExprNode(temp, test_node) return test_node - def _handle_simple_function_ord(self, node, pos_args): + def _handle_simple_function_ord(self, node, function, pos_args): """Unpack ord(Py_UNICODE) and ord('X'). """ if len(pos_args) != 1: @@ -2165,10 +2165,11 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("kwargs", Builtin.dict_type, None), ]) - def _handle_any_slot__new__(self, node, args, is_unbound_method, kwargs=None): + def _handle_any_slot__new__(self, node, function, args, + is_unbound_method, kwargs=None): """Replace 'exttype.__new__(exttype, ...)' by a call to exttype->tp_new() """ - obj = node.function.obj + obj = function.obj if not is_unbound_method or len(args) < 1: return node type_arg = args[0] @@ -2244,7 +2245,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("item", PyrexTypes.py_object_type, None), ]) - def _handle_simple_method_object_append(self, node, args, is_unbound_method): + def _handle_simple_method_object_append(self, node, function, args, is_unbound_method): """Optimistic optimisation as X.append() is almost always referring to a list. """ @@ -2270,7 +2271,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("index", PyrexTypes.c_long_type, None), ]) - def _handle_simple_method_object_pop(self, node, args, is_unbound_method): + def _handle_simple_method_object_pop(self, node, function, args, is_unbound_method): """Optimistic optimisation as X.pop([n]) is almost always referring to a list. """ @@ -2312,13 +2313,13 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): ], exception_value = "-1") - def _handle_simple_method_list_sort(self, node, args, is_unbound_method): + def _handle_simple_method_list_sort(self, node, function, args, is_unbound_method): """Call PyList_Sort() instead of the 0-argument l.sort(). """ if len(args) != 1: return node return self._substitute_method_call( - node, "PyList_Sort", self.single_param_func_type, + node, function, "PyList_Sort", self.single_param_func_type, 'sort', is_unbound_method, args).coerce_to(node.type, self.current_env) Pyx_PyDict_GetItem_func_type = PyrexTypes.CFuncType( @@ -2328,7 +2329,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("default", PyrexTypes.py_object_type, None), ]) - def _handle_simple_method_dict_get(self, node, args, is_unbound_method): + def _handle_simple_method_dict_get(self, node, function, args, is_unbound_method): """Replace dict.get() by a call to PyDict_GetItem(). """ if len(args) == 2: @@ -2338,7 +2339,8 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): return node return self._substitute_method_call( - node, "__Pyx_PyDict_GetItemDefault", self.Pyx_PyDict_GetItem_func_type, + node, function, + "__Pyx_PyDict_GetItemDefault", self.Pyx_PyDict_GetItem_func_type, 'get', is_unbound_method, args, may_return_none = True, utility_code = load_c_utility("dict_getitem_default")) @@ -2351,7 +2353,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("is_safe_type", PyrexTypes.c_int_type, None), ]) - def _handle_simple_method_dict_setdefault(self, node, args, is_unbound_method): + def _handle_simple_method_dict_setdefault(self, node, function, args, is_unbound_method): """Replace dict.setdefault() by calls to PyDict_GetItem() and PyDict_SetItem(). """ if len(args) == 2: @@ -2371,7 +2373,8 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): node.pos, value=is_safe_type, constant_result=is_safe_type)) return self._substitute_method_call( - node, "__Pyx_PyDict_SetDefault", self.Pyx_PyDict_SetDefault_func_type, + node, function, + "__Pyx_PyDict_SetDefault", self.Pyx_PyDict_SetDefault_func_type, 'setdefault', is_unbound_method, args, may_return_none=True, utility_code=load_c_utility('dict_setdefault')) @@ -2384,7 +2387,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("uchar", PyrexTypes.c_py_ucs4_type, None), ]) - def _inject_unicode_predicate(self, node, args, is_unbound_method): + def _inject_unicode_predicate(self, node, function, args, is_unbound_method): if is_unbound_method or len(args) != 1: return node ustring = args[0] @@ -2392,7 +2395,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): not ustring.arg.type.is_unicode_char: return node uchar = ustring.arg - method_name = node.function.attribute + method_name = function.attribute if method_name == 'istitle': # istitle() doesn't directly map to Py_UNICODE_ISTITLE() utility_code = UtilityCode.load_cached( @@ -2402,7 +2405,8 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): utility_code = None function_name = 'Py_UNICODE_%s' % method_name.upper() func_call = self._substitute_method_call( - node, function_name, self.PyUnicode_uchar_predicate_func_type, + node, function, + function_name, self.PyUnicode_uchar_predicate_func_type, method_name, is_unbound_method, [uchar], utility_code = utility_code) if node.type.is_pyobject: @@ -2424,7 +2428,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("uchar", PyrexTypes.c_py_ucs4_type, None), ]) - def _inject_unicode_character_conversion(self, node, args, is_unbound_method): + def _inject_unicode_character_conversion(self, function, node, args, is_unbound_method): if is_unbound_method or len(args) != 1: return node ustring = args[0] @@ -2432,10 +2436,11 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): not ustring.arg.type.is_unicode_char: return node uchar = ustring.arg - method_name = node.function.attribute + method_name = function.attribute function_name = 'Py_UNICODE_TO%s' % method_name.upper() func_call = self._substitute_method_call( - node, function_name, self.PyUnicode_uchar_conversion_func_type, + node, function, + function_name, self.PyUnicode_uchar_conversion_func_type, method_name, is_unbound_method, [uchar]) if node.type.is_pyobject: func_call = func_call.coerce_to_pyobject(self.current_env) @@ -2451,7 +2456,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("keepends", PyrexTypes.c_bint_type, None), ]) - def _handle_simple_method_unicode_splitlines(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_splitlines(self, function, node, args, is_unbound_method): """Replace unicode.splitlines(...) by a direct call to the corresponding C-API function. """ @@ -2461,7 +2466,8 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): self._inject_bint_default_argument(node, args, 1, False) return self._substitute_method_call( - node, "PyUnicode_Splitlines", self.PyUnicode_Splitlines_func_type, + node, function, + "PyUnicode_Splitlines", self.PyUnicode_Splitlines_func_type, 'splitlines', is_unbound_method, args) PyUnicode_Split_func_type = PyrexTypes.CFuncType( @@ -2472,7 +2478,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): ] ) - def _handle_simple_method_unicode_split(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_split(self, node, function, args, is_unbound_method): """Replace unicode.split(...) by a direct call to the corresponding C-API function. """ @@ -2485,7 +2491,8 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): node, args, 2, PyrexTypes.c_py_ssize_t_type, "-1") return self._substitute_method_call( - node, "PyUnicode_Split", self.PyUnicode_Split_func_type, + node, function, + "PyUnicode_Split", self.PyUnicode_Split_func_type, 'split', is_unbound_method, args) PyString_Tailmatch_func_type = PyrexTypes.CFuncType( @@ -2498,17 +2505,17 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): ], exception_value = '-1') - def _handle_simple_method_unicode_endswith(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_endswith(self, node, function, args, is_unbound_method): return self._inject_tailmatch( - node, args, is_unbound_method, 'unicode', 'endswith', + node, function, args, is_unbound_method, 'unicode', 'endswith', unicode_tailmatch_utility_code, +1) - def _handle_simple_method_unicode_startswith(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_startswith(self, node, function, args, is_unbound_method): return self._inject_tailmatch( - node, args, is_unbound_method, 'unicode', 'startswith', + node, function, args, is_unbound_method, 'unicode', 'startswith', unicode_tailmatch_utility_code, -1) - def _inject_tailmatch(self, node, args, is_unbound_method, type_name, + def _inject_tailmatch(self, node, function, args, is_unbound_method, type_name, method_name, utility_code, direction): """Replace unicode.startswith(...) and unicode.endswith(...) by a direct call to the corresponding C-API function. @@ -2524,7 +2531,8 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): node.pos, value=str(direction), type=PyrexTypes.c_int_type)) method_call = self._substitute_method_call( - node, "__Pyx_Py%s_Tailmatch" % type_name.capitalize(), + node, function, + "__Pyx_Py%s_Tailmatch" % type_name.capitalize(), self.PyString_Tailmatch_func_type, method_name, is_unbound_method, args, utility_code = utility_code) @@ -2540,15 +2548,15 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): ], exception_value = '-2') - def _handle_simple_method_unicode_find(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_find(self, node, function, args, is_unbound_method): return self._inject_unicode_find( - node, args, is_unbound_method, 'find', +1) + node, function, args, is_unbound_method, 'find', +1) - def _handle_simple_method_unicode_rfind(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_rfind(self, node, function, args, is_unbound_method): return self._inject_unicode_find( - node, args, is_unbound_method, 'rfind', -1) + node, function, args, is_unbound_method, 'rfind', -1) - def _inject_unicode_find(self, node, args, is_unbound_method, + def _inject_unicode_find(self, node, function, args, is_unbound_method, method_name, direction): """Replace unicode.find(...) and unicode.rfind(...) by a direct call to the corresponding C-API function. @@ -2564,7 +2572,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): node.pos, value=str(direction), type=PyrexTypes.c_int_type)) method_call = self._substitute_method_call( - node, "PyUnicode_Find", self.PyUnicode_Find_func_type, + node, function, "PyUnicode_Find", self.PyUnicode_Find_func_type, method_name, is_unbound_method, args) return method_call.coerce_to_pyobject(self.current_env()) @@ -2577,7 +2585,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): ], exception_value = '-1') - def _handle_simple_method_unicode_count(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_count(self, node, function, args, is_unbound_method): """Replace unicode.count(...) by a direct call to the corresponding C-API function. """ @@ -2590,7 +2598,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): node, args, 3, PyrexTypes.c_py_ssize_t_type, "PY_SSIZE_T_MAX") method_call = self._substitute_method_call( - node, "PyUnicode_Count", self.PyUnicode_Count_func_type, + node, function, "PyUnicode_Count", self.PyUnicode_Count_func_type, 'count', is_unbound_method, args) return method_call.coerce_to_pyobject(self.current_env()) @@ -2602,7 +2610,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): PyrexTypes.CFuncTypeArg("maxcount", PyrexTypes.c_py_ssize_t_type, None), ]) - def _handle_simple_method_unicode_replace(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_replace(self, node, function, args, is_unbound_method): """Replace unicode.replace(...) by a direct call to the corresponding C-API function. """ @@ -2613,7 +2621,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): node, args, 3, PyrexTypes.c_py_ssize_t_type, "-1") return self._substitute_method_call( - node, "PyUnicode_Replace", self.PyUnicode_Replace_func_type, + node, function, "PyUnicode_Replace", self.PyUnicode_Replace_func_type, 'replace', is_unbound_method, args) PyUnicode_AsEncodedString_func_type = PyrexTypes.CFuncType( @@ -2634,7 +2642,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): _special_codecs = [ (name, codecs.getencoder(name)) for name in _special_encodings ] - def _handle_simple_method_unicode_encode(self, node, args, is_unbound_method): + def _handle_simple_method_unicode_encode(self, node, function, args, is_unbound_method): """Replace unicode.encode(...) by a direct C-API call to the corresponding codec. """ @@ -2647,7 +2655,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): if len(args) == 1: null_node = ExprNodes.NullNode(node.pos) return self._substitute_method_call( - node, "PyUnicode_AsEncodedString", + node, function, "PyUnicode_AsEncodedString", self.PyUnicode_AsEncodedString_func_type, 'encode', is_unbound_method, [string_node, null_node, null_node]) @@ -2675,12 +2683,12 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): if codec_name is not None: encode_function = "PyUnicode_As%sString" % codec_name return self._substitute_method_call( - node, encode_function, + node, function, encode_function, self.PyUnicode_AsXyzString_func_type, 'encode', is_unbound_method, [string_node]) return self._substitute_method_call( - node, "PyUnicode_AsEncodedString", + node, function, "PyUnicode_AsEncodedString", self.PyUnicode_AsEncodedString_func_type, 'encode', is_unbound_method, [string_node, encoding_node, error_handling_node]) @@ -2714,7 +2722,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): _decode_cpp_string_func_type = None # lazy init - def _handle_simple_method_bytes_decode(self, node, args, is_unbound_method): + def _handle_simple_method_bytes_decode(self, node, function, args, is_unbound_method): """Replace char*.decode() by a direct C-API call to the corresponding codec, possibly resolving a slice on the char*. """ @@ -2884,29 +2892,29 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): encoding = node = None return encoding, node - def _handle_simple_method_str_endswith(self, node, args, is_unbound_method): + def _handle_simple_method_str_endswith(self, node, function, args, is_unbound_method): return self._inject_tailmatch( - node, args, is_unbound_method, 'str', 'endswith', + node, function, args, is_unbound_method, 'str', 'endswith', str_tailmatch_utility_code, +1) - def _handle_simple_method_str_startswith(self, node, args, is_unbound_method): + def _handle_simple_method_str_startswith(self, node, function, args, is_unbound_method): return self._inject_tailmatch( - node, args, is_unbound_method, 'str', 'startswith', + node, function, args, is_unbound_method, 'str', 'startswith', str_tailmatch_utility_code, -1) - def _handle_simple_method_bytes_endswith(self, node, args, is_unbound_method): + def _handle_simple_method_bytes_endswith(self, node, function, args, is_unbound_method): return self._inject_tailmatch( - node, args, is_unbound_method, 'bytes', 'endswith', + node, function, args, is_unbound_method, 'bytes', 'endswith', bytes_tailmatch_utility_code, +1) - def _handle_simple_method_bytes_startswith(self, node, args, is_unbound_method): + def _handle_simple_method_bytes_startswith(self, node, function, args, is_unbound_method): return self._inject_tailmatch( - node, args, is_unbound_method, 'bytes', 'startswith', + node, function, args, is_unbound_method, 'bytes', 'startswith', bytes_tailmatch_utility_code, -1) ### helpers - def _substitute_method_call(self, node, name, func_type, + def _substitute_method_call(self, node, function, name, func_type, attr_name, is_unbound_method, args=(), utility_code=None, is_temp=None, may_return_none=ExprNodes.PythonCapiCallNode.may_return_none): @@ -2916,7 +2924,7 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform): if is_unbound_method: self_arg = self_arg.as_none_safe_node( "descriptor '%s' requires a '%s' object but received a 'NoneType'", - format_args = [attr_name, node.function.obj.name]) + format_args=[attr_name, function.obj.name]) else: self_arg = self_arg.as_none_safe_node( "'NoneType' object has no attribute '%s'", diff --git a/Cython/Compiler/Tests/TestVisitor.py b/Cython/Compiler/Tests/TestVisitor.py index fcc5d3f..dbc8e0c 100644 --- a/Cython/Compiler/Tests/TestVisitor.py +++ b/Cython/Compiler/Tests/TestVisitor.py @@ -37,7 +37,7 @@ class TestMethodDispatcherTransform(TransformTest): def test_builtin_method(self): calls = [0] class Test(MethodDispatcherTransform): - def _handle_simple_method_dict_get(self, node, args, unbound): + def _handle_simple_method_dict_get(self, node, func, args, unbound): calls[0] += 1 return node @@ -48,10 +48,10 @@ class TestMethodDispatcherTransform(TransformTest): def test_binop_method(self): calls = {'bytes': 0, 'object': 0} class Test(MethodDispatcherTransform): - def _handle_simple_method_bytes___mul__(self, node, args, unbound): + def _handle_simple_method_bytes___mul__(self, node, func, args, unbound): calls['bytes'] += 1 return node - def _handle_simple_method_object___mul__(self, node, args, unbound): + def _handle_simple_method_object___mul__(self, node, func, args, unbound): calls['object'] += 1 return node diff --git a/Cython/Compiler/Visitor.py b/Cython/Compiler/Visitor.py index 527c410..35f8206 100644 --- a/Cython/Compiler/Visitor.py +++ b/Cython/Compiler/Visitor.py @@ -517,7 +517,7 @@ class MethodDispatcherTransform(EnvTransform): type_name = "object" # safety measure node = self._dispatch_to_method_handler( special_method_name, None, False, type_name, - node, [operand1, operand2], None) + node, None, [operand1, operand2], None) return node def visit_UnopNode(self, node): @@ -532,7 +532,7 @@ class MethodDispatcherTransform(EnvTransform): type_name = "object" # safety measure node = self._dispatch_to_method_handler( special_method_name, None, False, type_name, - node, [operand], None) + node, None, [operand], None) return node ### dispatch to specific handlers @@ -544,6 +544,22 @@ class MethodDispatcherTransform(EnvTransform): handler = getattr(self, '_handle_any_%s' % match_name, None) return handler + def _delegate_to_assigned_value(self, node, function, arg_list, kwargs): + assignment = function.cf_state[0] + value = assignment.rhs + if value.is_name: + if not value.entry or len(value.entry.cf_assignments) > 1: + # the variable might have been reassigned => play safe + return node + elif value.is_attribute: + if not value.obj.entry or len(value.obj.entry.cf_assignments) > 1: + # the underlying variable might have been reassigned => play safe + return node + else: + return node + return self._dispatch_to_handler( + node, value, arg_list, kwargs) + def _dispatch_to_handler(self, node, function, arg_list, kwargs): if function.is_name: # we only consider functions that are either builtin @@ -551,18 +567,24 @@ class MethodDispatcherTransform(EnvTransform): # into a C function call (defined in the builtin scope) if not function.entry: return node - is_builtin = function.entry.is_builtin or\ - function.entry is self.current_env().builtin_scope().lookup_here(function.name) + is_builtin = ( + function.entry.is_builtin or + function.entry is self.current_env().builtin_scope().lookup_here(function.name)) if not is_builtin: + if function.cf_state and function.cf_state.is_single: + # we know the value of the variable + # => see if it's usable instead + return self._delegate_to_assigned_value( + node, function, arg_list, kwargs) return node function_handler = self._find_handler( "function_%s" % function.name, kwargs) if function_handler is None: return node if kwargs: - return function_handler(node, arg_list, kwargs) + return function_handler(node, function, arg_list, kwargs) else: - return function_handler(node, arg_list) + return function_handler(node, function, arg_list) elif function.is_attribute and function.type.is_pyobject: attr_name = function.attribute self_arg = function.obj @@ -582,13 +604,13 @@ class MethodDispatcherTransform(EnvTransform): type_name = "object" # safety measure return self._dispatch_to_method_handler( attr_name, self_arg, is_unbound_method, type_name, - node, arg_list, kwargs) + node, function, arg_list, kwargs) else: return node def _dispatch_to_method_handler(self, attr_name, self_arg, is_unbound_method, type_name, - node, arg_list, kwargs): + node, function, arg_list, kwargs): method_handler = self._find_handler( "method_%s_%s" % (type_name, attr_name), kwargs) if method_handler is None: @@ -601,9 +623,11 @@ class MethodDispatcherTransform(EnvTransform): if self_arg is not None: arg_list = [self_arg] + list(arg_list) if kwargs: - return method_handler(node, arg_list, is_unbound_method, kwargs) + return method_handler( + node, function, arg_list, is_unbound_method, kwargs) else: - return method_handler(node, arg_list, is_unbound_method) + return method_handler( + node, function, arg_list, is_unbound_method) class RecursiveNodeReplacer(VisitorTransform): diff --git a/tests/run/assigned_builtin_methods.pyx b/tests/run/assigned_builtin_methods.pyx new file mode 100644 index 0000000..e3904a7 --- /dev/null +++ b/tests/run/assigned_builtin_methods.pyx @@ -0,0 +1,85 @@ +# mode: run +# tag: builtins + +cimport cython + +@cython.test_assert_path_exists( + '//ReturnStatNode//PythonCapiCallNode') +def unbound_dict_get(d): + """ + >>> unbound_dict_get({}) + >>> unbound_dict_get({1:2}) + 2 + """ + get = dict.get + return get(d, 1) + + +@cython.test_assert_path_exists( + '//ReturnStatNode//PythonCapiCallNode') +def bound_dict_get(dict d): + """ + >>> bound_dict_get({}) + >>> bound_dict_get({1:2}) + 2 + """ + get = d.get + return get(1) + + +@cython.test_fail_if_path_exists( + '//ReturnStatNode//PythonCapiCallNode') +@cython.test_assert_path_exists( + '//ReturnStatNode//SimpleCallNode') +def bound_dict_get_reassign(dict d): + """ + >>> bound_dict_get_reassign({}) + >>> bound_dict_get_reassign({1:2}) + 2 + """ + get = d.get + d = {1: 3} + return get(1) + + +@cython.test_assert_path_exists( + '//PythonCapiCallNode//NameNode[@name="l"]') +def unbound_list_sort(list l): + """ + >>> unbound_list_sort([1, 3, 2]) + [1, 2, 3] + >>> unbound_list_sort([1, 3, 2]) + [1, 2, 3] + """ + sort = list.sort + sort(l) + return l + + +@cython.test_assert_path_exists( + '//PythonCapiCallNode//NameNode[@name="l"]') +def bound_list_sort(list l): + """ + >>> bound_list_sort([1, 3, 2]) + [1, 2, 3] + >>> bound_list_sort([1, 3, 2]) + [1, 2, 3] + """ + sort = l.sort + sort() + return l + + +@cython.test_fail_if_path_exists( + '//PythonCapiCallNode') +def bound_list_sort_reassign(list l): + """ + >>> bound_list_sort_reassign([1, 3, 2]) + [3, 2, 1] + >>> bound_list_sort_reassign([1, 3, 2]) + [3, 2, 1] + """ + sort = l.sort + l = [3, 2, 1] + sort() + return l -- 2.7.4