From ffa80043babffd5748be2420ea51eb36163cb7c1 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 21 Dec 2020 16:30:18 +0200 Subject: [PATCH 01/15] Add HPy_Call, HPy_CallObject and HPyCallable_Check. --- hpy/devel/include/common/autogen_impl.h | 15 +++++++++++++++ hpy/devel/include/universal/autogen_ctx.h | 5 ++++- hpy/devel/include/universal/autogen_trampolines.h | 12 ++++++++++++ hpy/tools/autogen/parse.py | 1 + hpy/tools/autogen/public_api.h | 8 +++++--- hpy/universal/src/autogen_ctx_def.h | 5 ++++- 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/hpy/devel/include/common/autogen_impl.h b/hpy/devel/include/common/autogen_impl.h index 8b1d4c900..49517aa3a 100644 --- a/hpy/devel/include/common/autogen_impl.h +++ b/hpy/devel/include/common/autogen_impl.h @@ -270,6 +270,21 @@ HPyAPI_STORAGE HPy _HPy_IMPL_NAME_NOPREFIX(InPlaceOr)(HPyContext ctx, HPy h1, HP return _py2h(PyNumber_InPlaceOr(_h2py(h1), _h2py(h2))); } +HPyAPI_STORAGE int _HPy_IMPL_NAME(Callable_Check)(HPyContext ctx, HPy h) +{ + return PyCallable_Check(_h2py(h)); +} + +HPyAPI_STORAGE HPy _HPy_IMPL_NAME_NOPREFIX(Call)(HPyContext ctx, HPy callable, HPy args, HPy kw) +{ + return _py2h(PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw))); +} + +HPyAPI_STORAGE HPy _HPy_IMPL_NAME_NOPREFIX(CallObject)(HPyContext ctx, HPy callable, HPy args) +{ + return _py2h(PyObject_CallObject(_h2py(callable), _h2py(args))); +} + HPyAPI_STORAGE void _HPy_IMPL_NAME(Err_SetString)(HPyContext ctx, HPy h_type, const char *message) { PyErr_SetString(_h2py(h_type), message); diff --git a/hpy/devel/include/universal/autogen_ctx.h b/hpy/devel/include/universal/autogen_ctx.h index fd374bc94..5069b7c5a 100644 --- a/hpy/devel/include/universal/autogen_ctx.h +++ b/hpy/devel/include/universal/autogen_ctx.h @@ -140,6 +140,10 @@ struct _HPyContext_s { HPy (*ctx_InPlaceAnd)(HPyContext ctx, HPy h1, HPy h2); HPy (*ctx_InPlaceXor)(HPyContext ctx, HPy h1, HPy h2); HPy (*ctx_InPlaceOr)(HPyContext ctx, HPy h1, HPy h2); + int (*ctx_Callable_Check)(HPyContext ctx, HPy h); + HPy (*ctx_Call)(HPyContext ctx, HPy callable, HPy args, HPy kw); + HPy (*ctx_CallObject)(HPyContext ctx, HPy callable, HPy args); + void (*ctx_FatalError)(HPyContext ctx, const char *message); void (*ctx_Err_SetString)(HPyContext ctx, HPy h_type, const char *message); void (*ctx_Err_SetObject)(HPyContext ctx, HPy h_type, HPy h_value); int (*ctx_Err_Occurred)(HPyContext ctx); @@ -185,7 +189,6 @@ struct _HPyContext_s { int (*ctx_List_Append)(HPyContext ctx, HPy h_list, HPy h_item); int (*ctx_Dict_Check)(HPyContext ctx, HPy h); HPy (*ctx_Dict_New)(HPyContext ctx); - void (*ctx_FatalError)(HPyContext ctx, const char *message); HPy (*ctx_Tuple_FromArray)(HPyContext ctx, HPy items[], HPy_ssize_t n); HPy (*ctx_FromPyObject)(HPyContext ctx, cpy_PyObject *obj); cpy_PyObject *(*ctx_AsPyObject)(HPyContext ctx, HPy h); diff --git a/hpy/devel/include/universal/autogen_trampolines.h b/hpy/devel/include/universal/autogen_trampolines.h index 5174dc287..3b617e5d5 100644 --- a/hpy/devel/include/universal/autogen_trampolines.h +++ b/hpy/devel/include/universal/autogen_trampolines.h @@ -230,6 +230,18 @@ static inline HPy HPy_InPlaceOr(HPyContext ctx, HPy h1, HPy h2) { return ctx->ctx_InPlaceOr ( ctx, h1, h2 ); } +static inline int HPyCallable_Check(HPyContext ctx, HPy h) { + return ctx->ctx_Callable_Check ( ctx, h ); +} + +static inline HPy HPy_Call(HPyContext ctx, HPy callable, HPy args, HPy kw) { + return ctx->ctx_Call ( ctx, callable, args, kw ); +} + +static inline HPy HPy_CallObject(HPyContext ctx, HPy callable, HPy args) { + return ctx->ctx_CallObject ( ctx, callable, args ); +} + static inline void HPyErr_SetString(HPyContext ctx, HPy h_type, const char *message) { ctx->ctx_Err_SetString ( ctx, h_type, message ); } diff --git a/hpy/tools/autogen/parse.py b/hpy/tools/autogen/parse.py index 388506963..5171d9f00 100644 --- a/hpy/tools/autogen/parse.py +++ b/hpy/tools/autogen/parse.py @@ -153,6 +153,7 @@ def _visit_hpyslot_slot(self, node): 'HPy_SetItem_s': None, 'HPy_Length': 'PyObject_Length', 'HPy_Call': 'PyObject_Call', + 'HPy_CallObject': 'PyObject_CallObject', '_HPy_Cast': None, 'HPy_FromPyObject': None, 'HPy_AsPyObject': None, diff --git a/hpy/tools/autogen/public_api.h b/hpy/tools/autogen/public_api.h index e3ff8e552..beba18bf4 100644 --- a/hpy/tools/autogen/public_api.h +++ b/hpy/tools/autogen/public_api.h @@ -164,7 +164,12 @@ HPy HPy_InPlaceAnd(HPyContext ctx, HPy h1, HPy h2); HPy HPy_InPlaceXor(HPyContext ctx, HPy h1, HPy h2); HPy HPy_InPlaceOr(HPyContext ctx, HPy h1, HPy h2); +int HPyCallable_Check(HPyContext ctx, HPy h); +HPy HPy_Call(HPyContext ctx, HPy callable, HPy args, HPy kw); +HPy HPy_CallObject(HPyContext ctx, HPy callable, HPy args); + /* pyerrors.h */ +void HPy_FatalError(HPyContext ctx, const char *message); void HPyErr_SetString(HPyContext ctx, HPy h_type, const char *message); void HPyErr_SetObject(HPyContext ctx, HPy h_type, HPy h_value); /* note: HPyErr_Occurred() returns a flag 0-or-1, instead of a 'PyObject *' */ @@ -232,9 +237,6 @@ int HPyList_Append(HPyContext ctx, HPy h_list, HPy h_item); int HPyDict_Check(HPyContext ctx, HPy h); HPy HPyDict_New(HPyContext ctx); -/* pyerrors.h */ -void HPy_FatalError(HPyContext ctx, const char *message); - /* tupleobject.h */ HPy HPyTuple_FromArray(HPyContext ctx, HPy items[], HPy_ssize_t n); // note: HPyTuple_Pack is implemented as a macro in common/macros.h diff --git a/hpy/universal/src/autogen_ctx_def.h b/hpy/universal/src/autogen_ctx_def.h index 624094539..48dba3ad7 100644 --- a/hpy/universal/src/autogen_ctx_def.h +++ b/hpy/universal/src/autogen_ctx_def.h @@ -140,6 +140,10 @@ struct _HPyContext_s global_ctx = { .ctx_InPlaceAnd = &ctx_InPlaceAnd, .ctx_InPlaceXor = &ctx_InPlaceXor, .ctx_InPlaceOr = &ctx_InPlaceOr, + .ctx_Callable_Check = &ctx_Callable_Check, + .ctx_Call = &ctx_Call, + .ctx_CallObject = &ctx_CallObject, + .ctx_FatalError = &ctx_FatalError, .ctx_Err_SetString = &ctx_Err_SetString, .ctx_Err_SetObject = &ctx_Err_SetObject, .ctx_Err_Occurred = &ctx_Err_Occurred, @@ -185,7 +189,6 @@ struct _HPyContext_s global_ctx = { .ctx_List_Append = &ctx_List_Append, .ctx_Dict_Check = &ctx_Dict_Check, .ctx_Dict_New = &ctx_Dict_New, - .ctx_FatalError = &ctx_FatalError, .ctx_Tuple_FromArray = &ctx_Tuple_FromArray, .ctx_FromPyObject = &ctx_FromPyObject, .ctx_AsPyObject = &ctx_AsPyObject, From 6a38dfa82bc7beecce346db4a24e9a3b6be90393 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 21 Dec 2020 16:30:33 +0200 Subject: [PATCH 02/15] Reactivate unicode exceptions test. --- test/test_hpyerr.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/test_hpyerr.py b/test/test_hpyerr.py index f00273c1a..c592f8d43 100644 --- a/test/test_hpyerr.py +++ b/test/test_hpyerr.py @@ -248,10 +248,6 @@ def check_exception(cls): check_exception(EnvironmentError) check_exception(IOError) - @pytest_collecting.mark.xfail(True, reason=( - "Creating the unicode exceptions requires something like HPyCall" - " and that isn't implemented yet." - )) def test_h_unicode_exceptions(self): import pytest mod = self.make_module(""" From 5c2fc691af6cb7aea10698efb57e45d26cbd4257 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 21 Dec 2020 17:14:11 +0200 Subject: [PATCH 03/15] Add tests for HPy_Call, HPy_CallObject and HPyCallable_Check. --- test/test_call.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/test_call.py diff --git a/test/test_call.py b/test/test_call.py new file mode 100644 index 000000000..bbd31e062 --- /dev/null +++ b/test/test_call.py @@ -0,0 +1,93 @@ +from .support import HPyTest + + +class TestCall(HPyTest): + def test_hpy_call(self): + import pytest + mod = self.make_module(""" + HPyDef_METH(call, "call", call_impl, HPyFunc_VARARGS) + static HPy call_impl(HPyContext ctx, HPy self, + HPy *args, HPy_ssize_t nargs) + { + + HPy f, f_args; + HPy f_kw = HPy_NULL; + if (!HPyArg_Parse(ctx, NULL, args, nargs, "OO|O", &f, &f_args, + &f_kw)) + return HPy_NULL; + return HPy_Call(ctx, f, f_args, f_kw); + } + @EXPORT(call) + @INIT + """) + + def f(a, b): + return a + b + + assert mod.call(f, (1, 2)) == 3 + assert mod.call(f, (), {"a": 2, "b": 3}) == 5 + assert mod.call(str, (2,)) == "2" + assert mod.call(str, (2,), {}) == "2" + with pytest.raises(TypeError): + mod.call(f, (1,)) + with pytest.raises(TypeError): + mod.call(f, (), {"b": 2}) + with pytest.raises(TypeError): + mod.call(f, (), {}) + with pytest.raises(TypeError): + mod.call("not callable", (2,)) + with pytest.raises(TypeError): + mod.call("not callable", (2,), {}) + + def test_hpy_callobject(self): + import pytest + mod = self.make_module(""" + HPyDef_METH(call, "call", call_impl, HPyFunc_VARARGS) + static HPy call_impl(HPyContext ctx, HPy self, + HPy *args, HPy_ssize_t nargs) + { + + HPy f; + HPy f_args = HPy_NULL; + if (!HPyArg_Parse(ctx, NULL, args, nargs, "O|O", &f, &f_args)) + return HPy_NULL; + return HPy_CallObject(ctx, f, f_args); + } + @EXPORT(call) + @INIT + """) + + def f(a, b): + return a + b + + def g(): + return "this is g" + + assert mod.call(f, (1, 2)) == 3 + assert mod.call(g) == "this is g" + assert mod.call(str, (2,)) == "2" + with pytest.raises(TypeError): + mod.call(f, (1,)) + with pytest.raises(TypeError): + mod.call("not callable", (2,)) + + def test_hpycallable_check(self): + mod = self.make_module(""" + HPyDef_METH(f, "f", f_impl, HPyFunc_O) + static HPy f_impl(HPyContext ctx, HPy self, HPy arg) + { + if (HPyCallable_Check(ctx, arg)) + return HPy_Dup(ctx, ctx->h_True); + return HPy_Dup(ctx, ctx->h_False); + } + @EXPORT(f) + @INIT + """) + + def f(): + return "this is f" + + assert mod.f(f) is True + assert mod.f(str) is True + assert mod.f("a") is False + assert mod.f(3) is False From def721e50073e2aee217928e4b2a4ece3d1492db Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 4 Jan 2021 15:27:17 +0200 Subject: [PATCH 04/15] Replace HPy_Call with HPy_CallTupleDict. --- hpy/devel/include/common/autogen_impl.h | 10 ---------- hpy/devel/include/universal/autogen_ctx.h | 3 +-- hpy/devel/include/universal/autogen_trampolines.h | 8 ++------ hpy/tools/autogen/parse.py | 3 +-- hpy/tools/autogen/public_api.h | 3 +-- hpy/universal/src/autogen_ctx_def.h | 3 +-- 6 files changed, 6 insertions(+), 24 deletions(-) diff --git a/hpy/devel/include/common/autogen_impl.h b/hpy/devel/include/common/autogen_impl.h index 49517aa3a..767e350ac 100644 --- a/hpy/devel/include/common/autogen_impl.h +++ b/hpy/devel/include/common/autogen_impl.h @@ -275,16 +275,6 @@ HPyAPI_STORAGE int _HPy_IMPL_NAME(Callable_Check)(HPyContext ctx, HPy h) return PyCallable_Check(_h2py(h)); } -HPyAPI_STORAGE HPy _HPy_IMPL_NAME_NOPREFIX(Call)(HPyContext ctx, HPy callable, HPy args, HPy kw) -{ - return _py2h(PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw))); -} - -HPyAPI_STORAGE HPy _HPy_IMPL_NAME_NOPREFIX(CallObject)(HPyContext ctx, HPy callable, HPy args) -{ - return _py2h(PyObject_CallObject(_h2py(callable), _h2py(args))); -} - HPyAPI_STORAGE void _HPy_IMPL_NAME(Err_SetString)(HPyContext ctx, HPy h_type, const char *message) { PyErr_SetString(_h2py(h_type), message); diff --git a/hpy/devel/include/universal/autogen_ctx.h b/hpy/devel/include/universal/autogen_ctx.h index 5069b7c5a..0c2f027c3 100644 --- a/hpy/devel/include/universal/autogen_ctx.h +++ b/hpy/devel/include/universal/autogen_ctx.h @@ -141,8 +141,7 @@ struct _HPyContext_s { HPy (*ctx_InPlaceXor)(HPyContext ctx, HPy h1, HPy h2); HPy (*ctx_InPlaceOr)(HPyContext ctx, HPy h1, HPy h2); int (*ctx_Callable_Check)(HPyContext ctx, HPy h); - HPy (*ctx_Call)(HPyContext ctx, HPy callable, HPy args, HPy kw); - HPy (*ctx_CallObject)(HPyContext ctx, HPy callable, HPy args); + HPy (*ctx_CallTupleDict)(HPyContext ctx, HPy callable, HPy args, HPy kw); void (*ctx_FatalError)(HPyContext ctx, const char *message); void (*ctx_Err_SetString)(HPyContext ctx, HPy h_type, const char *message); void (*ctx_Err_SetObject)(HPyContext ctx, HPy h_type, HPy h_value); diff --git a/hpy/devel/include/universal/autogen_trampolines.h b/hpy/devel/include/universal/autogen_trampolines.h index 3b617e5d5..3a96080c6 100644 --- a/hpy/devel/include/universal/autogen_trampolines.h +++ b/hpy/devel/include/universal/autogen_trampolines.h @@ -234,12 +234,8 @@ static inline int HPyCallable_Check(HPyContext ctx, HPy h) { return ctx->ctx_Callable_Check ( ctx, h ); } -static inline HPy HPy_Call(HPyContext ctx, HPy callable, HPy args, HPy kw) { - return ctx->ctx_Call ( ctx, callable, args, kw ); -} - -static inline HPy HPy_CallObject(HPyContext ctx, HPy callable, HPy args) { - return ctx->ctx_CallObject ( ctx, callable, args ); +static inline HPy HPy_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw) { + return ctx->ctx_CallTupleDict ( ctx, callable, args, kw ); } static inline void HPyErr_SetString(HPyContext ctx, HPy h_type, const char *message) { diff --git a/hpy/tools/autogen/parse.py b/hpy/tools/autogen/parse.py index 5171d9f00..560303a01 100644 --- a/hpy/tools/autogen/parse.py +++ b/hpy/tools/autogen/parse.py @@ -152,8 +152,7 @@ def _visit_hpyslot_slot(self, node): 'HPy_SetItem_i': None, 'HPy_SetItem_s': None, 'HPy_Length': 'PyObject_Length', - 'HPy_Call': 'PyObject_Call', - 'HPy_CallObject': 'PyObject_CallObject', + 'HPy_CallTupleDict': None, '_HPy_Cast': None, 'HPy_FromPyObject': None, 'HPy_AsPyObject': None, diff --git a/hpy/tools/autogen/public_api.h b/hpy/tools/autogen/public_api.h index beba18bf4..9fb877a05 100644 --- a/hpy/tools/autogen/public_api.h +++ b/hpy/tools/autogen/public_api.h @@ -165,8 +165,7 @@ HPy HPy_InPlaceXor(HPyContext ctx, HPy h1, HPy h2); HPy HPy_InPlaceOr(HPyContext ctx, HPy h1, HPy h2); int HPyCallable_Check(HPyContext ctx, HPy h); -HPy HPy_Call(HPyContext ctx, HPy callable, HPy args, HPy kw); -HPy HPy_CallObject(HPyContext ctx, HPy callable, HPy args); +HPy HPy_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw); /* pyerrors.h */ void HPy_FatalError(HPyContext ctx, const char *message); diff --git a/hpy/universal/src/autogen_ctx_def.h b/hpy/universal/src/autogen_ctx_def.h index 48dba3ad7..a0bfe5219 100644 --- a/hpy/universal/src/autogen_ctx_def.h +++ b/hpy/universal/src/autogen_ctx_def.h @@ -141,8 +141,7 @@ struct _HPyContext_s global_ctx = { .ctx_InPlaceXor = &ctx_InPlaceXor, .ctx_InPlaceOr = &ctx_InPlaceOr, .ctx_Callable_Check = &ctx_Callable_Check, - .ctx_Call = &ctx_Call, - .ctx_CallObject = &ctx_CallObject, + .ctx_CallTupleDict = &ctx_CallTupleDict, .ctx_FatalError = &ctx_FatalError, .ctx_Err_SetString = &ctx_Err_SetString, .ctx_Err_SetObject = &ctx_Err_SetObject, From cc4cdfec293e2fe618206f3b35edcc1ce7430c3e Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 4 Jan 2021 16:38:59 +0200 Subject: [PATCH 05/15] Implement initial HPy_CallTupleDict. --- hpy/devel/include/common/runtime/ctx_call.h | 10 ++++++++++ hpy/devel/include/cpython/hpy.h | 7 +++++++ hpy/devel/src/runtime/ctx_call.c | 14 ++++++++++++++ hpy/universal/src/ctx.c | 1 + setup.py | 3 ++- 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 hpy/devel/include/common/runtime/ctx_call.h create mode 100644 hpy/devel/src/runtime/ctx_call.c diff --git a/hpy/devel/include/common/runtime/ctx_call.h b/hpy/devel/include/common/runtime/ctx_call.h new file mode 100644 index 000000000..0f64ea9ee --- /dev/null +++ b/hpy/devel/include/common/runtime/ctx_call.h @@ -0,0 +1,10 @@ +#ifndef HPY_COMMON_RUNTIME_CALL_H +#define HPY_COMMON_RUNTIME_CALL_H + +#include +#include "hpy.h" + +_HPy_HIDDEN HPy +ctx_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw); + +#endif /* HPY_COMMON_RUNTIME_CALL_H */ diff --git a/hpy/devel/include/cpython/hpy.h b/hpy/devel/include/cpython/hpy.h index 8f97c4be1..750684340 100644 --- a/hpy/devel/include/cpython/hpy.h +++ b/hpy/devel/include/cpython/hpy.h @@ -272,6 +272,7 @@ HPy_AsPyObject(HPyContext ctx, HPy h) #include "../common/hpydef.h" #include "../common/hpytype.h" #include "../common/hpymodule.h" +#include "../common/runtime/ctx_call.h" #include "../common/runtime/ctx_module.h" #include "../common/runtime/ctx_type.h" #include "../common/runtime/ctx_listbuilder.h" @@ -315,6 +316,12 @@ _HPy_Cast(HPyContext ctx, HPy h) return ctx_Cast(ctx, h); } +HPyAPI_FUNC(HPy) +HPy_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw) +{ + return ctx_CallTupleDict(ctx, callable, args, kw); +} + HPyAPI_FUNC(HPyListBuilder) HPyListBuilder_New(HPyContext ctx, HPy_ssize_t initial_size) { diff --git a/hpy/devel/src/runtime/ctx_call.c b/hpy/devel/src/runtime/ctx_call.c new file mode 100644 index 000000000..d55a42d97 --- /dev/null +++ b/hpy/devel/src/runtime/ctx_call.c @@ -0,0 +1,14 @@ +#include +#include "hpy.h" + +#ifdef HPY_UNIVERSAL_ABI + // for _h2py and _py2h +# include "handles.h" +#endif + +_HPy_HIDDEN HPy +ctx_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw) +{ + PyObject *obj = PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw)); + return _py2h(obj); +} diff --git a/hpy/universal/src/ctx.c b/hpy/universal/src/ctx.c index bd61cd446..f96ad0509 100644 --- a/hpy/universal/src/ctx.c +++ b/hpy/universal/src/ctx.c @@ -1,6 +1,7 @@ #include "hpy.h" #include "handles.h" +#include "common/runtime/ctx_call.h" #include "common/runtime/ctx_type.h" #include "common/runtime/ctx_module.h" #include "common/runtime/ctx_listbuilder.h" diff --git a/setup.py b/setup.py index 37fbe2797..af8c6ee64 100644 --- a/setup.py +++ b/setup.py @@ -49,9 +49,10 @@ def get_scm_config(): 'hpy/universal/src/ctx.c', 'hpy/universal/src/ctx_meth.c', 'hpy/universal/src/ctx_misc.c', + 'hpy/devel/src/runtime/argparse.c', + 'hpy/devel/src/runtime/ctx_call.c', 'hpy/devel/src/runtime/ctx_module.c', 'hpy/devel/src/runtime/ctx_type.c', - 'hpy/devel/src/runtime/argparse.c', 'hpy/devel/src/runtime/ctx_tracker.c', 'hpy/devel/src/runtime/ctx_listbuilder.c', 'hpy/devel/src/runtime/ctx_tuple.c', From 3ef7267a78eb0406d8db9a9d7b68b73564ed687c Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 4 Jan 2021 16:39:30 +0200 Subject: [PATCH 06/15] Update UnicodeError tests to HPy_CallTupleDict. --- test/test_hpyerr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_hpyerr.py b/test/test_hpyerr.py index c592f8d43..c1152d157 100644 --- a/test/test_hpyerr.py +++ b/test/test_hpyerr.py @@ -275,7 +275,7 @@ def test_h_unicode_exceptions(self): HPy_Close(ctx, h_dict); return HPy_NULL; } - h_err_value = HPy_Call(ctx, h_err, h_args, h_kw); + h_err_value = HPy_CallTupleDict(ctx, h_err, h_args, h_kw); if (HPy_IsNull(h_err_value)) { HPy_Close(ctx, h_dict); HPy_Close(ctx, h_err); From c14af6fbc9a2f9de19eeb1325052f0e269a6382f Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 4 Jan 2021 18:17:13 +0200 Subject: [PATCH 07/15] Implement HPy_CallTupleDict cases where args or kw are NULL. --- hpy/devel/src/runtime/ctx_call.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/hpy/devel/src/runtime/ctx_call.c b/hpy/devel/src/runtime/ctx_call.c index d55a42d97..c7f46d8c9 100644 --- a/hpy/devel/src/runtime/ctx_call.c +++ b/hpy/devel/src/runtime/ctx_call.c @@ -9,6 +9,20 @@ _HPy_HIDDEN HPy ctx_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw) { - PyObject *obj = PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw)); + PyObject *obj; + if (HPy_IsNull(kw)) { + obj = PyObject_CallObject(_h2py(callable), _h2py(args)); + } + else if (!HPy_IsNull(args)){ + obj = PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw)); + } + else { + // args is null, but kw is not, so we need to create an empty args tuple + // for CPython's PyObject_Call + HPy items[] = {}; + HPy empty_tuple = HPyTuple_FromArray(ctx, items, 0); + obj = PyObject_Call(_h2py(callable), _h2py(empty_tuple), _h2py(kw)); + HPy_Close(ctx, empty_tuple); + } return _py2h(obj); } From d4954f7e5f301f46600a14ead8b723d5c22ffbbc Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 4 Jan 2021 18:17:26 +0200 Subject: [PATCH 08/15] Update HPy_CallTupleDict tests. --- test/test_call.py | 75 ++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/test/test_call.py b/test/test_call.py index bbd31e062..3d2fa1c2a 100644 --- a/test/test_call.py +++ b/test/test_call.py @@ -2,20 +2,26 @@ class TestCall(HPyTest): - def test_hpy_call(self): + def test_hpy_calltupledict(self): import pytest mod = self.make_module(""" - HPyDef_METH(call, "call", call_impl, HPyFunc_VARARGS) + HPyDef_METH(call, "call", call_impl, HPyFunc_KEYWORDS) static HPy call_impl(HPyContext ctx, HPy self, - HPy *args, HPy_ssize_t nargs) + HPy *args, HPy_ssize_t nargs, HPy kw) { - HPy f, f_args; + HPy f, result; + HPy f_args = HPy_NULL; HPy f_kw = HPy_NULL; - if (!HPyArg_Parse(ctx, NULL, args, nargs, "OO|O", &f, &f_args, - &f_kw)) + HPyTracker ht; + static const char *kwlist[] = { "f", "args", "kw", NULL }; + if (!HPyArg_ParseKeywords(ctx, &ht, args, nargs, kw, "O|OO", + kwlist, &f, &f_args, &f_kw)) { return HPy_NULL; - return HPy_Call(ctx, f, f_args, f_kw); + } + result = HPy_CallTupleDict(ctx, f, f_args, f_kw); + HPyTracker_Close(ctx, ht); + return result; } @EXPORT(call) @INIT @@ -24,52 +30,29 @@ def test_hpy_call(self): def f(a, b): return a + b - assert mod.call(f, (1, 2)) == 3 - assert mod.call(f, (), {"a": 2, "b": 3}) == 5 - assert mod.call(str, (2,)) == "2" - assert mod.call(str, (2,), {}) == "2" + def g(): + return "this is g" + + # test calls with args (both with and without keywords) + assert mod.call(f, args=(1, 2)) == 3 + assert mod.call(f, args=(), kw={"a": 2, "b": 3}) == 5 + assert mod.call(g, (), {}) == "this is g" + assert mod.call(str, args=(2,)) == "2" + assert mod.call(str, args=(2,), kw={}) == "2" with pytest.raises(TypeError): - mod.call(f, (1,)) + mod.call(f, args=(1,)) with pytest.raises(TypeError): - mod.call(f, (), {"b": 2}) + mod.call(f, args=(), kw={"b": 2}) with pytest.raises(TypeError): - mod.call(f, (), {}) + mod.call(f, args=(), kw={}) with pytest.raises(TypeError): - mod.call("not callable", (2,)) + mod.call("not callable", args=(2,)) with pytest.raises(TypeError): - mod.call("not callable", (2,), {}) - - def test_hpy_callobject(self): - import pytest - mod = self.make_module(""" - HPyDef_METH(call, "call", call_impl, HPyFunc_VARARGS) - static HPy call_impl(HPyContext ctx, HPy self, - HPy *args, HPy_ssize_t nargs) - { - - HPy f; - HPy f_args = HPy_NULL; - if (!HPyArg_Parse(ctx, NULL, args, nargs, "O|O", &f, &f_args)) - return HPy_NULL; - return HPy_CallObject(ctx, f, f_args); - } - @EXPORT(call) - @INIT - """) - - def f(a, b): - return a + b - - def g(): - return "this is g" + mod.call("not callable", args=(2,), kw={}) - assert mod.call(f, (1, 2)) == 3 + # test calls without args (both and without keywords) assert mod.call(g) == "this is g" - assert mod.call(str, (2,)) == "2" - with pytest.raises(TypeError): - mod.call(f, (1,)) - with pytest.raises(TypeError): - mod.call("not callable", (2,)) + assert mod.call(g, kw={}) == "this is g" def test_hpycallable_check(self): mod = self.make_module(""" From 342bfa049a7d8a06bfa6e916f98689e03e5fa8e5 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 5 Jan 2021 17:27:01 +0200 Subject: [PATCH 09/15] Update tests to match expected HPy_CallTupleDict behaviour. --- test/test_call.py | 63 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/test/test_call.py b/test/test_call.py index 3d2fa1c2a..55c1005de 100644 --- a/test/test_call.py +++ b/test/test_call.py @@ -2,6 +2,21 @@ class TestCall(HPyTest): + def argument_combinations(self, *items): + """ Returns all possible ways of expressing the given items as + arguments to a function. + """ + for i in range(len(items) + 1): + args = tuple(item[1] for item in items[:i]) + kw = dict(items[i:]) + yield {"args": args, "kw": kw} + if not args: + yield {"kw": kw} + if not kw: + yield {"args": args} + if not args and not kw: + yield {} + def test_hpy_calltupledict(self): import pytest mod = self.make_module(""" @@ -33,26 +48,44 @@ def f(a, b): def g(): return "this is g" - # test calls with args (both with and without keywords) - assert mod.call(f, args=(1, 2)) == 3 - assert mod.call(f, args=(), kw={"a": 2, "b": 3}) == 5 - assert mod.call(g, (), {}) == "this is g" - assert mod.call(str, args=(2,)) == "2" - assert mod.call(str, args=(2,), kw={}) == "2" + # test passing arguments with handles of the correct type -- + # i.e. args is a tuple or a null handle, kw is a dict or a null handle. + for d in self.argument_combinations(("a", 1), ("b", 2)): + assert mod.call(f, **d) == 3 + for d in self.argument_combinations(("a", 1)): + with pytest.raises(TypeError): + mod.call(f, **d) + for d in self.argument_combinations(): + with pytest.raises(TypeError): + mod.call(f, **d) + for d in self.argument_combinations(): + assert mod.call(g, **d) == "this is g" + for d in self.argument_combinations(("object", 2)): + assert mod.call(str, **d) == "2" + for d in self.argument_combinations(): + with pytest.raises(TypeError): + mod.call("not callable", **d) + for d in self.argument_combinations(("unknown", 2)): + with pytest.raises(TypeError): + mod.call("not callable", **d) + + # test passing handles of the incorrect type as arguments with pytest.raises(TypeError): - mod.call(f, args=(1,)) + mod.call(f, args=[1, 2]) with pytest.raises(TypeError): - mod.call(f, args=(), kw={"b": 2}) + mod.call(f, args="string") with pytest.raises(TypeError): - mod.call(f, args=(), kw={}) + mod.call(f, args=1) with pytest.raises(TypeError): - mod.call("not callable", args=(2,)) + mod.call(f, args=None) with pytest.raises(TypeError): - mod.call("not callable", args=(2,), kw={}) - - # test calls without args (both and without keywords) - assert mod.call(g) == "this is g" - assert mod.call(g, kw={}) == "this is g" + mod.call(f, kw=[1, 2]) + with pytest.raises(TypeError): + mod.call(f, kw="string") + with pytest.raises(TypeError): + mod.call(f, kw=1) + with pytest.raises(TypeError): + mod.call(f, kw=None) def test_hpycallable_check(self): mod = self.make_module(""" From 98f448a1323216bda087c7adba167640047313e4 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 5 Jan 2021 17:27:33 +0200 Subject: [PATCH 10/15] Add HPyTuple_Check. --- hpy/devel/include/common/autogen_impl.h | 5 +++++ hpy/devel/include/universal/autogen_ctx.h | 1 + hpy/devel/include/universal/autogen_trampolines.h | 4 ++++ hpy/tools/autogen/public_api.h | 1 + hpy/universal/src/autogen_ctx_def.h | 1 + 5 files changed, 12 insertions(+) diff --git a/hpy/devel/include/common/autogen_impl.h b/hpy/devel/include/common/autogen_impl.h index 767e350ac..4b9a1b940 100644 --- a/hpy/devel/include/common/autogen_impl.h +++ b/hpy/devel/include/common/autogen_impl.h @@ -455,3 +455,8 @@ HPyAPI_STORAGE HPy _HPy_IMPL_NAME(Dict_New)(HPyContext ctx) return _py2h(PyDict_New()); } +HPyAPI_STORAGE int _HPy_IMPL_NAME(Tuple_Check)(HPyContext ctx, HPy h) +{ + return PyTuple_Check(_h2py(h)); +} + diff --git a/hpy/devel/include/universal/autogen_ctx.h b/hpy/devel/include/universal/autogen_ctx.h index 0c2f027c3..115a47ccd 100644 --- a/hpy/devel/include/universal/autogen_ctx.h +++ b/hpy/devel/include/universal/autogen_ctx.h @@ -188,6 +188,7 @@ struct _HPyContext_s { int (*ctx_List_Append)(HPyContext ctx, HPy h_list, HPy h_item); int (*ctx_Dict_Check)(HPyContext ctx, HPy h); HPy (*ctx_Dict_New)(HPyContext ctx); + int (*ctx_Tuple_Check)(HPyContext ctx, HPy h); HPy (*ctx_Tuple_FromArray)(HPyContext ctx, HPy items[], HPy_ssize_t n); HPy (*ctx_FromPyObject)(HPyContext ctx, cpy_PyObject *obj); cpy_PyObject *(*ctx_AsPyObject)(HPyContext ctx, HPy h); diff --git a/hpy/devel/include/universal/autogen_trampolines.h b/hpy/devel/include/universal/autogen_trampolines.h index 3a96080c6..179423863 100644 --- a/hpy/devel/include/universal/autogen_trampolines.h +++ b/hpy/devel/include/universal/autogen_trampolines.h @@ -414,6 +414,10 @@ static inline HPy HPyDict_New(HPyContext ctx) { return ctx->ctx_Dict_New ( ctx ); } +static inline int HPyTuple_Check(HPyContext ctx, HPy h) { + return ctx->ctx_Tuple_Check ( ctx, h ); +} + static inline HPy HPyTuple_FromArray(HPyContext ctx, HPy items[], HPy_ssize_t n) { return ctx->ctx_Tuple_FromArray ( ctx, items, n ); } diff --git a/hpy/tools/autogen/public_api.h b/hpy/tools/autogen/public_api.h index 9fb877a05..92539b2fc 100644 --- a/hpy/tools/autogen/public_api.h +++ b/hpy/tools/autogen/public_api.h @@ -237,6 +237,7 @@ int HPyDict_Check(HPyContext ctx, HPy h); HPy HPyDict_New(HPyContext ctx); /* tupleobject.h */ +int HPyTuple_Check(HPyContext ctx, HPy h); HPy HPyTuple_FromArray(HPyContext ctx, HPy items[], HPy_ssize_t n); // note: HPyTuple_Pack is implemented as a macro in common/macros.h diff --git a/hpy/universal/src/autogen_ctx_def.h b/hpy/universal/src/autogen_ctx_def.h index a0bfe5219..030b63271 100644 --- a/hpy/universal/src/autogen_ctx_def.h +++ b/hpy/universal/src/autogen_ctx_def.h @@ -188,6 +188,7 @@ struct _HPyContext_s global_ctx = { .ctx_List_Append = &ctx_List_Append, .ctx_Dict_Check = &ctx_Dict_Check, .ctx_Dict_New = &ctx_Dict_New, + .ctx_Tuple_Check = &ctx_Tuple_Check, .ctx_Tuple_FromArray = &ctx_Tuple_FromArray, .ctx_FromPyObject = &ctx_FromPyObject, .ctx_AsPyObject = &ctx_AsPyObject, From 287e63b3d125df5f6a3c07aece97a0865c5c1380 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 5 Jan 2021 17:27:56 +0200 Subject: [PATCH 11/15] Add type checking for handles used to pass arguments to HPy_CallTupleDict. --- hpy/devel/src/runtime/ctx_call.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/hpy/devel/src/runtime/ctx_call.c b/hpy/devel/src/runtime/ctx_call.c index c7f46d8c9..e11712151 100644 --- a/hpy/devel/src/runtime/ctx_call.c +++ b/hpy/devel/src/runtime/ctx_call.c @@ -10,19 +10,29 @@ _HPy_HIDDEN HPy ctx_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw) { PyObject *obj; + if (!HPy_IsNull(args) && !HPyTuple_Check(ctx, args)) { + HPyErr_SetString(ctx, ctx->h_TypeError, + "HPy_CallTupleDict requires args to be a tuple or null handle"); + return HPy_NULL; + } + if (!HPy_IsNull(kw) && !HPyDict_Check(ctx, kw)) { + HPyErr_SetString(ctx, ctx->h_TypeError, + "HPy_CallTupleDict requires kw to be a dict or null handle"); + return HPy_NULL; + } if (HPy_IsNull(kw)) { - obj = PyObject_CallObject(_h2py(callable), _h2py(args)); + obj = PyObject_CallObject(_h2py(callable), _h2py(args)); } else if (!HPy_IsNull(args)){ - obj = PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw)); + obj = PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw)); } else { - // args is null, but kw is not, so we need to create an empty args tuple - // for CPython's PyObject_Call - HPy items[] = {}; - HPy empty_tuple = HPyTuple_FromArray(ctx, items, 0); - obj = PyObject_Call(_h2py(callable), _h2py(empty_tuple), _h2py(kw)); - HPy_Close(ctx, empty_tuple); + // args is null, but kw is not, so we need to create an empty args tuple + // for CPython's PyObject_Call + HPy items[] = {}; + HPy empty_tuple = HPyTuple_FromArray(ctx, items, 0); + obj = PyObject_Call(_h2py(callable), _h2py(empty_tuple), _h2py(kw)); + HPy_Close(ctx, empty_tuple); } return _py2h(obj); } From b6540a280baeedc73f46e20073b1072844e40bd7 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 5 Jan 2021 20:00:09 +0200 Subject: [PATCH 12/15] Use keywords for argument_combination arguments. --- test/test_call.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/test_call.py b/test/test_call.py index 55c1005de..18ec74b65 100644 --- a/test/test_call.py +++ b/test/test_call.py @@ -2,10 +2,11 @@ class TestCall(HPyTest): - def argument_combinations(self, *items): + def argument_combinations(self, **items): """ Returns all possible ways of expressing the given items as arguments to a function. """ + items = list(items.items()) for i in range(len(items) + 1): args = tuple(item[1] for item in items[:i]) kw = dict(items[i:]) @@ -50,9 +51,9 @@ def g(): # test passing arguments with handles of the correct type -- # i.e. args is a tuple or a null handle, kw is a dict or a null handle. - for d in self.argument_combinations(("a", 1), ("b", 2)): + for d in self.argument_combinations(a=1, b=2): assert mod.call(f, **d) == 3 - for d in self.argument_combinations(("a", 1)): + for d in self.argument_combinations(a=1): with pytest.raises(TypeError): mod.call(f, **d) for d in self.argument_combinations(): @@ -60,12 +61,12 @@ def g(): mod.call(f, **d) for d in self.argument_combinations(): assert mod.call(g, **d) == "this is g" - for d in self.argument_combinations(("object", 2)): + for d in self.argument_combinations(object=2): assert mod.call(str, **d) == "2" for d in self.argument_combinations(): with pytest.raises(TypeError): mod.call("not callable", **d) - for d in self.argument_combinations(("unknown", 2)): + for d in self.argument_combinations(unknown=2): with pytest.raises(TypeError): mod.call("not callable", **d) From 085542f0f4819cba222542ba5741e32aa1ff5908 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 7 Jan 2021 17:00:32 +0200 Subject: [PATCH 13/15] Add microbenchmarks for HPy_CallTupleDict. --- microbench/src/cpy_simple.c | 32 ++++++++++++++++++++++++++++++++ microbench/src/hpy_simple.c | 29 +++++++++++++++++++++++++++++ microbench/test_microbench.py | 16 ++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/microbench/src/cpy_simple.c b/microbench/src/cpy_simple.c index 61d656876..dc754de64 100644 --- a/microbench/src/cpy_simple.c +++ b/microbench/src/cpy_simple.c @@ -17,6 +17,36 @@ static PyObject* varargs(PyObject* self, PyObject* args) Py_RETURN_NONE; } +static PyObject* call_with_tuple(PyObject* self, PyObject* args) +{ + PyObject* f; + PyObject* f_args; + f = PyTuple_GetItem(args, 0); + if (f == NULL) + Py_RETURN_NONE; + f_args = PyTuple_GetItem(args, 1); + if (f_args == NULL) + Py_RETURN_NONE; + return PyObject_CallObject(f, f_args); +} + +static PyObject* call_with_tuple_and_dict(PyObject* self, PyObject* args) +{ + PyObject* f; + PyObject* f_args; + PyObject* f_kw; + f = PyTuple_GetItem(args, 0); + if (f == NULL) + Py_RETURN_NONE; + f_args = PyTuple_GetItem(args, 1); + if (f_args == NULL) + Py_RETURN_NONE; + f_kw = PyTuple_GetItem(args, 2); + if (f_kw == NULL) + Py_RETURN_NONE; + return PyObject_Call(f, f_args, f_kw); +} + static PyObject* allocate_int(PyObject* self, PyObject* args) { return PyLong_FromLong(2048); @@ -41,6 +71,8 @@ static PyMethodDef SimpleMethods[] = { {"noargs", (PyCFunction)noargs, METH_NOARGS, ""}, {"onearg", (PyCFunction)onearg, METH_O, ""}, {"varargs", (PyCFunction)varargs, METH_VARARGS, ""}, + {"call_with_tuple", (PyCFunction)call_with_tuple, METH_VARARGS, ""}, + {"call_with_tuple_and_dict", (PyCFunction)call_with_tuple_and_dict, METH_VARARGS, ""}, {"allocate_int", (PyCFunction)allocate_int, METH_NOARGS, ""}, {"allocate_tuple", (PyCFunction)allocate_tuple, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} diff --git a/microbench/src/hpy_simple.c b/microbench/src/hpy_simple.c index 1e7f8e3f0..ecab91534 100644 --- a/microbench/src/hpy_simple.c +++ b/microbench/src/hpy_simple.c @@ -20,6 +20,33 @@ static HPy varargs_impl(HPyContext ctx, HPy self, HPy *args, HPy_ssize_t nargs) return HPy_Dup(ctx, ctx->h_None); } +HPyDef_METH(call_with_tuple, "call_with_tuple", call_with_tuple_impl, HPyFunc_VARARGS) +static HPy call_with_tuple_impl(HPyContext ctx, HPy self, HPy *args, HPy_ssize_t nargs) +{ + HPy f, f_args; + if (nargs != 2) { + HPyErr_SetString(ctx, ctx->h_TypeError, "call_with_tuple requires two arguments"); + return HPy_NULL; + } + f = args[0]; + f_args = args[1]; + return HPy_CallTupleDict(ctx, f, f_args, HPy_NULL); +} + +HPyDef_METH(call_with_tuple_and_dict, "call_with_tuple_and_dict", call_with_tuple_and_dict_impl, HPyFunc_VARARGS) +static HPy call_with_tuple_and_dict_impl(HPyContext ctx, HPy self, HPy *args, HPy_ssize_t nargs) +{ + HPy f, f_args, f_kw; + if (nargs != 3) { + HPyErr_SetString(ctx, ctx->h_TypeError, "call_with_tuple_and_dict requires three arguments"); + return HPy_NULL; + } + f = args[0]; + f_args = args[1]; + f_kw = args[2]; + return HPy_CallTupleDict(ctx, f, f_args, f_kw); +} + HPyDef_METH(allocate_int, "allocate_int", allocate_int_impl, HPyFunc_NOARGS) static HPy allocate_int_impl(HPyContext ctx, HPy self) { @@ -81,6 +108,8 @@ static HPyDef *module_defines[] = { &noargs, &onearg, &varargs, + &call_with_tuple, + &call_with_tuple_and_dict, &allocate_int, &allocate_tuple, NULL diff --git a/microbench/test_microbench.py b/microbench/test_microbench.py index 6642e0d96..9cf11818d 100644 --- a/microbench/test_microbench.py +++ b/microbench/test_microbench.py @@ -60,6 +60,22 @@ def test_varargs(self, simple, timer, N): for i in range(N): simple.varargs(None, None) + def test_call_with_tuple(self, simple, timer, N): + def f(a, b): + return a + b + + with timer: + for i in range(N): + simple.call_with_tuple(f, (1, 2)) + + def test_call_with_tuple_and_dict(self, simple, timer, N): + def f(a, b): + return a + b + + with timer: + for i in range(N): + simple.call_with_tuple_and_dict(f, (1,), {"b": 2}) + def test_allocate_int(self, simple, timer, N): with timer: for i in range(N): From 39da59f5db2d16fa37a5b75f69f6593706b723ed Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 7 Jan 2021 17:25:27 +0200 Subject: [PATCH 14/15] Add porting docs for HPy_CallTupleDict. --- docs/porting-guide.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/porting-guide.rst b/docs/porting-guide.rst index 900f884c6..9c51357c7 100644 --- a/docs/porting-guide.rst +++ b/docs/porting-guide.rst @@ -46,3 +46,24 @@ For lists of (say) integers:: HPy h_list = HPyListBuilder_i_Build(ctx, builder); And similar for building tuples or bytes + + +PyObject_Call and PyObject_CallObject +------------------------------------- + +Both ``PyObject_Call`` and ``PyObject_CallObject`` are replaced by +``HPy_CallTupleDict(callable, args, kwargs)`` in which either or both of +``args`` and ``kwargs`` may be null handles. + +``PyObject_Call(callable, args, kwargs)`` becomes: + + HPy result = HPy_CallTupleDict(ctx, callable, args, kwargs); + +``PyObject_CallObject(callable, args)`` becomes: + + HPy result = HPy_CallTupleDict(ctx, callable, args, HPy_NULL); + +If ``args`` is not a handle to a tuple or ``kwargs`` is not a handle to a +dictionary, ``HPy_CallTupleDict`` will return ``HPy_NULL`` and raise a +``TypeError``. This is different to ``PyObject_Call`` and +``PyObject_CallObject`` which may segfault instead. From c520f3ed8d88ff4a539cb81c19c6cfea271dc118 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 8 Jan 2021 15:21:36 +0200 Subject: [PATCH 15/15] Add test for HPyTuple_Check. --- test/test_hpytuple.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/test_hpytuple.py b/test/test_hpytuple.py index f95080da8..0498c37af 100644 --- a/test/test_hpytuple.py +++ b/test/test_hpytuple.py @@ -2,6 +2,25 @@ class TestTuple(HPyTest): + def test_Check(self): + mod = self.make_module(""" + HPyDef_METH(f, "f", f_impl, HPyFunc_O) + static HPy f_impl(HPyContext ctx, HPy self, HPy arg) + { + if (HPyTuple_Check(ctx, arg)) + return HPy_Dup(ctx, ctx->h_True); + return HPy_Dup(ctx, ctx->h_False); + } + @EXPORT(f) + @INIT + """) + class MyTuple(tuple): + pass + + assert mod.f(()) is True + assert mod.f([]) is False + assert mod.f(MyTuple()) is True + def test_FromArray(self): mod = self.make_module(""" HPyDef_METH(f, "f", f_impl, HPyFunc_O)