From a387fb9f35bea76b1a48ee4470c90e72e128b4db Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 14 May 2017 22:18:52 +0200 Subject: [PATCH] interrogate: nullptr handling, faster kwargs handling in some cases --- dtool/src/interrogate/functionRemap.cxx | 29 +- .../interfaceMakerPythonNative.cxx | 272 +++++++++++++----- dtool/src/interrogate/interrogateBuilder.cxx | 4 + dtool/src/interrogate/typeManager.cxx | 21 ++ dtool/src/interrogate/typeManager.h | 1 + .../src/interrogatedb/interrogate_interface.h | 6 +- dtool/src/interrogatedb/py_panda.I | 22 ++ dtool/src/interrogatedb/py_panda.cxx | 173 ++++++++++- dtool/src/interrogatedb/py_panda.h | 19 ++ dtool/src/parser-inc/stddef.h | 2 - dtool/src/parser-inc/stdtypedefs.h | 6 +- dtool/src/pystub/pystub.cxx | 18 +- 12 files changed, 482 insertions(+), 91 deletions(-) diff --git a/dtool/src/interrogate/functionRemap.cxx b/dtool/src/interrogate/functionRemap.cxx index 085d9421d1..d58cdf8b88 100644 --- a/dtool/src/interrogate/functionRemap.cxx +++ b/dtool/src/interrogate/functionRemap.cxx @@ -869,8 +869,14 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak } if (_args_type == InterfaceMaker::AT_varargs) { - // Of course methods named "make" can still take kwargs. - _args_type = InterfaceMaker::AT_keyword_args; + // Of course methods named "make" can still take kwargs, if they are + // named. + for (int i = first_param; i < _parameters.size(); ++i) { + if (_parameters[i]._has_name) { + _args_type = InterfaceMaker::AT_keyword_args; + break; + } + } } } else if (fname == "operator /") { @@ -898,8 +904,13 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak } else { if (_args_type == InterfaceMaker::AT_varargs) { // Every other method can take keyword arguments, if they take more - // than one argument. - _args_type |= InterfaceMaker::AT_keyword_args; + // than one argument, and the arguments are named. + for (int i = first_param; i < _parameters.size(); ++i) { + if (_parameters[i]._has_name) { + _args_type |= InterfaceMaker::AT_keyword_args; + break; + } + } } } break; @@ -941,8 +952,14 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak _flags |= F_coerce_constructor; } - // Constructors always take varargs and keyword args. - _args_type = InterfaceMaker::AT_keyword_args; + // Constructors always take varargs, and possibly keyword args. + _args_type = InterfaceMaker::AT_varargs; + for (int i = first_param; i < _parameters.size(); ++i) { + if (_parameters[i]._has_name) { + _args_type = InterfaceMaker::AT_keyword_args; + break; + } + } break; default: diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index 6ca45df628..c4dd2d3b21 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -3389,6 +3389,7 @@ write_function_for_name(ostream &out, Object *obj, FunctionRemap *remap = NULL; int max_required_args = 0; bool all_nonconst = true; + bool has_keywords = false; out << "/**\n * Python function wrapper for:\n"; for (ri = remaps.begin(); ri != remaps.end(); ++ri) { @@ -3404,6 +3405,10 @@ write_function_for_name(ostream &out, Object *obj, all_nonconst = false; } + if (remap->_args_type == AT_keyword_args) { + has_keywords = true; + } + max_required_args = max(max_num_args, max_required_args); for (int i = min_num_args; i <= max_num_args; ++i) { @@ -3450,6 +3455,19 @@ write_function_for_name(ostream &out, Object *obj, return; } + if (args_type == AT_keyword_args && !has_keywords) { + // We don't actually take keyword arguments. Make sure we didn't get any. + out << " if (kwds != NULL && PyDict_Size(kwds) > 0) {\n"; + out << "#ifdef NDEBUG\n"; + error_raise_return(out, 4, return_flags, "TypeError", "function takes no keyword arguments"); + out << "#else\n"; + error_raise_return(out, 4, return_flags, "TypeError", + methodNameFromCppName(remap, "", false) + "() takes no keyword arguments"); + out << "#endif\n"; + out << " }\n"; + args_type = AT_varargs; + } + if (args_type == AT_keyword_args || args_type == AT_varargs) { max_required_args = collapse_default_remaps(map_sets, max_required_args); } @@ -3461,6 +3479,7 @@ write_function_for_name(ostream &out, Object *obj, args_type, return_flags); } else if (map_sets.size() > 1 && (args_type == AT_varargs || args_type == AT_keyword_args)) { + // We have more than one remap. switch (args_type) { case AT_keyword_args: indent(out, 2) << "int parameter_count = (int)PyTuple_Size(args);\n"; @@ -3498,16 +3517,50 @@ write_function_for_name(ostream &out, Object *obj, indent(out, 2) << "case " << i << ":\n"; num_args.insert(i + add_self); } - indent(out, 4) << "{\n"; num_args.insert(max_args + add_self); - if (min_args == 1 && max_args == 1 && args_type == AT_varargs) { - // Might as well, since we already checked the number of args. - indent(out, 6) << " PyObject *arg = PyTuple_GET_ITEM(args, 0);\n"; + bool strip_keyword_args = false; + + // Check whether any remap actually takes keyword arguments. If not, + // then we don't have to bother checking that for every remap. + if (args_type == AT_keyword_args && max_args > 0) { + strip_keyword_args = true; + + std::set::iterator sii; + for (sii = mii->second.begin(); sii != mii->second.end(); ++sii) { + remap = (*sii); + int first_param = remap->_has_this ? 1 : 0; + for (int i = first_param; i < remap->_parameters.size(); ++i) { + if (remap->_parameters[i]._has_name) { + strip_keyword_args = false; + break; + } + } + } + } + + if (strip_keyword_args) { + // None of the remaps take any keyword arguments, so let's check that + // we take none. This saves some checks later on. + indent(out, 4) << "if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) {\n"; + if (min_args == 1 && min_args == 1) { + indent(out, 4) << " PyObject *arg = PyTuple_GET_ITEM(args, 0);\n"; + write_function_forset(out, mii->second, min_args, max_args, expected_params, 6, + coercion_allowed, true, AT_single_arg, return_flags, true, !all_nonconst); + } else { + write_function_forset(out, mii->second, min_args, max_args, expected_params, 6, + coercion_allowed, true, AT_varargs, return_flags, true, !all_nonconst); + } + } else if (min_args == 1 && max_args == 1 && args_type == AT_varargs) { + // We already checked that the args tuple has only one argument, so + // we might as well extract that from the tuple now. + indent(out, 4) << "{\n"; + indent(out, 4) << " PyObject *arg = PyTuple_GET_ITEM(args, 0);\n"; write_function_forset(out, mii->second, min_args, max_args, expected_params, 6, coercion_allowed, true, AT_single_arg, return_flags, true, !all_nonconst); } else { + indent(out, 4) << "{\n"; write_function_forset(out, mii->second, min_args, max_args, expected_params, 6, coercion_allowed, true, args_type, return_flags, true, !all_nonconst); } @@ -3574,14 +3627,14 @@ write_function_for_name(ostream &out, Object *obj, if (mii->first == 0 && args_type != AT_no_args) { switch (args_type) { case AT_keyword_args: - out << " if (PyTuple_Size(args) > 0 || (kwds != NULL && PyDict_Size(kwds) > 0)) {\n"; + out << " if (!Dtool_CheckNoArgs(args, kwds)) {\n"; out << " int parameter_count = (int)PyTuple_Size(args);\n"; out << " if (kwds != NULL) {\n"; out << " parameter_count += (int)PyDict_Size(kwds);\n"; out << " }\n"; break; case AT_varargs: - out << " if (PyTuple_Size(args) > 0) {\n"; + out << " if (!Dtool_CheckNoArgs(args)) {\n"; out << " const int parameter_count = (int)PyTuple_GET_SIZE(args);\n"; break; case AT_single_arg: @@ -3607,14 +3660,14 @@ write_function_for_name(ostream &out, Object *obj, } else if (args_type == AT_keyword_args && max_required_args == 1 && mii->first == 1) { // Check this to be sure, as we handle the case of only 1 keyword arg in // write_function_forset (not using ParseTupleAndKeywords). - out << " int parameter_count = (int)PyTuple_Size(args);\n" - " if (kwds != NULL) {\n" - " parameter_count += (int)PyDict_Size(kwds);\n" - " }\n" + out << " int parameter_count = (int)PyTuple_Size(args);\n" + " if (kwds != NULL) {\n" + " parameter_count += (int)PyDict_Size(kwds);\n" + " }\n" " if (parameter_count != 1) {\n" "#ifdef NDEBUG\n"; error_raise_return(out, 4, return_flags, "TypeError", - "function takes exactly 1 argument"); + "function takes exactly 1 argument"); out << "#else\n"; error_raise_return(out, 4, return_flags, "TypeError", methodNameFromCppName(remap, "", false) + "() takes exactly 1 argument (%d given)", @@ -4067,7 +4120,9 @@ int get_type_sort(CPPType *type) { // printf(" %s\n",type->get_local_name().c_str()); // The highest numbered one will be checked first. - if (TypeManager::is_pointer_to_Py_buffer(type)) { + if (TypeManager::is_nullptr(type)) { + return 15; + } else if (TypeManager::is_pointer_to_Py_buffer(type)) { return 14; } else if (TypeManager::is_pointer_to_PyTypeObject(type)) { return 13; @@ -4247,14 +4302,16 @@ write_function_forset(ostream &out, args_type == AT_keyword_args) { sii = remapsin.begin(); remap = (*sii); - first_param_name = remap->_parameters[(int)remap->_has_this]._name; - same_first_param = true; + if (remap->_parameters[(int)remap->_has_this]._has_name) { + first_param_name = remap->_parameters[(int)remap->_has_this]._name; + same_first_param = true; - for (++sii; sii != remapsin.end(); ++sii) { - remap = (*sii); - if (remap->_parameters[(int)remap->_has_this]._name != first_param_name) { - same_first_param = false; - break; + for (++sii; sii != remapsin.end(); ++sii) { + remap = (*sii); + if (remap->_parameters[(int)remap->_has_this]._name != first_param_name) { + same_first_param = false; + break; + } } } } @@ -4263,21 +4320,9 @@ write_function_forset(ostream &out, // Yes, they all have the same argument name (or there is only one remap). // Extract it from the dict so we don't have to call // ParseTupleAndKeywords. - indent(out, indent_level) << "PyObject *arg = NULL;\n"; - indent(out, indent_level) << "if (PyTuple_GET_SIZE(args) == 1) {\n"; - indent(out, indent_level) << " arg = PyTuple_GET_ITEM(args, 0);\n"; - indent(out, indent_level) << "} else if (kwds != NULL) {\n"; - indent(out, indent_level) << " arg = PyDict_GetItemString(kwds, \"" << first_param_name << "\");\n"; - indent(out, indent_level) << "}\n"; - if (report_errors) { - indent(out, indent_level) << "if (arg == (PyObject *)NULL) {\n"; - error_raise_return(out, indent_level + 2, return_flags, "TypeError", - "Required argument '" + first_param_name + "' (pos 1) not found"); - indent(out, indent_level) << "}\n"; - } else { - indent(out, indent_level) << "if (arg != (PyObject *)NULL) {\n"; - indent_level += 2; - } + indent(out, indent_level) << "PyObject *arg;\n"; + indent(out, indent_level) << "if (Dtool_ExtractArg(&arg, args, kwds, \"" << first_param_name << "\")) {\n"; + indent_level += 2; args_type = AT_single_arg; } @@ -4448,7 +4493,7 @@ write_function_forset(ostream &out, } // Close the brace we opened earlier. - if (same_first_param && !report_errors) { + if (same_first_param) { indent_level -= 2; indent(out, indent_level) << "}\n"; } @@ -4517,6 +4562,8 @@ write_function_instance(ostream &out, FunctionRemap *remap, string parameter_list; string container; string type_check; + string param_name; + bool has_keywords = false; vector_string pexprs; LineStream extra_convert; ostringstream extra_param_check; @@ -4612,7 +4659,7 @@ write_function_instance(ostream &out, FunctionRemap *remap, CPPType *orig_type = param->get_orig_type(); CPPType *type = param->get_new_type(); CPPExpression *default_value = param->get_default_value(); - string param_name = remap->get_parameter_name(pn); + param_name = remap->get_parameter_name(pn); if (!is_cpp_type_legal(orig_type)) { // We can't wrap this. We sometimes get here for default arguments. @@ -4667,7 +4714,14 @@ write_function_instance(ostream &out, FunctionRemap *remap, } string reported_name = remap->_parameters[pn]._name; - keyword_list += "\"" + reported_name + "\", "; + if (!keyword_list.empty()) { + keyword_list += ", \"" + reported_name + "\""; + } else { + keyword_list = "\"" + reported_name + "\""; + } + if (remap->_parameters[pn]._has_name) { + has_keywords = true; + } if (param->new_type_is_atomic_string()) { @@ -4869,6 +4923,19 @@ write_function_instance(ostream &out, FunctionRemap *remap, pexpr_string = "(PyObject_IsTrue(" + param_name + ") != 0)"; expected_params += "bool"; + } else if (TypeManager::is_nullptr(type)) { + if (args_type == AT_single_arg) { + type_check = "arg == Py_None"; + param_name = "arg"; + } else { + indent(out, indent_level) << "PyObject *" << param_name << default_expr << ";\n"; + extra_param_check << " && " << param_name << " == Py_None"; + format_specifiers += "O"; + parameter_list += ", &" + param_name; + } + pexpr_string = "NULL"; + expected_params += "NoneType"; + } else if (TypeManager::is_char(type)) { indent(out, indent_level) << "char " << param_name << default_expr << ";\n"; @@ -4907,27 +4974,43 @@ write_function_instance(ostream &out, FunctionRemap *remap, only_pyobjects = false; } else if (TypeManager::is_size(type)) { - // It certainly isn't the exact same thing as size_t, but Py_ssize_t - // should at least be the same size. The problem with mapping this to - // unsigned int is that that doesn't work well on 64-bit systems, on - // which size_t is a 64-bit integer. - indent(out, indent_level) << "Py_ssize_t " << param_name << default_expr << ";\n"; - format_specifiers += "n"; - parameter_list += ", &" + param_name; + if (args_type == AT_single_arg) { + type_check = "PyLongOrInt_Check(arg)"; + + extra_convert << + "size_t arg_val = PyLongOrInt_AsSize_t(arg);\n" + "#ifndef NDEBUG\n" + "if (arg_val == (size_t)-1 && _PyErr_OCCURRED()) {\n"; + error_return(extra_convert, 2, return_flags); + extra_convert << + "}\n" + "#endif\n"; + + pexpr_string = "arg_val"; + + } else { + // It certainly isn't the exact same thing as size_t, but Py_ssize_t + // should at least be the same size. The problem with mapping this to + // unsigned int is that that doesn't work well on 64-bit systems, on + // which size_t is a 64-bit integer. + indent(out, indent_level) << "Py_ssize_t " << param_name << default_expr << ";\n"; + format_specifiers += "n"; + parameter_list += ", &" + param_name; + + extra_convert + << "#ifndef NDEBUG\n" + << "if (" << param_name << " < 0) {\n"; + + error_raise_return(extra_convert, 2, return_flags, "OverflowError", + "can't convert negative value %zd to size_t", + param_name); + extra_convert + << "}\n" + << "#endif\n"; + } expected_params += "int"; only_pyobjects = false; - extra_convert - << "#ifndef NDEBUG\n" - << "if (" << param_name << " < 0) {\n"; - - error_raise_return(extra_convert, 2, return_flags, "OverflowError", - "can't convert negative value %zd to size_t", - param_name); - extra_convert - << "}\n" - << "#endif\n"; - } else if (TypeManager::is_longlong(type)) { // It's not trivial to do overflow checking for a long long, so we // simply don't do it. @@ -5555,15 +5638,60 @@ write_function_instance(ostream &out, FunctionRemap *remap, switch (args_type) { case AT_keyword_args: // Wrapper takes a varargs tuple and a keyword args dict. - indent(out, indent_level) - << "static const char *keyword_list[] = {" << keyword_list << "NULL};\n"; - indent(out, indent_level) - << "if (PyArg_ParseTupleAndKeywords(args, kwds, \"" - << format_specifiers << ":" << method_name - << "\", (char **)keyword_list" << parameter_list << ")) {\n"; + if (has_keywords) { + if (only_pyobjects && max_num_args == 1) { + // But we are only expecting one object arg, which is an easy common + // case we have implemented ourselves. + if (min_num_args == 1) { + indent(out, indent_level) + << "if (Dtool_ExtractArg(&" << param_name << ", args, kwds, " << keyword_list << ")) {\n"; + } else { + indent(out, indent_level) + << "if (Dtool_ExtractOptionalArg(&" << param_name << ", args, kwds, " << keyword_list << ")) {\n"; + } + } else { + // We have to use the more expensive PyArg_ParseTupleAndKeywords. + clear_error = true; + indent(out, indent_level) + << "static const char *keyword_list[] = {" << keyword_list << ", NULL};\n"; + indent(out, indent_level) + << "if (PyArg_ParseTupleAndKeywords(args, kwds, \"" + << format_specifiers << ":" << method_name + << "\", (char **)keyword_list" << parameter_list << ")) {\n"; + } + + } else if (only_pyobjects) { + // This function actually has no named parameters, so let's not take + // any keyword arguments. + if (max_num_args == 1) { + if (min_num_args == 1) { + indent(out, indent_level) + << "if (Dtool_ExtractArg(&" << param_name << ", args, kwds)) {\n"; + } else { + indent(out, indent_level) + << "if (Dtool_ExtractOptionalArg(&" << param_name << ", args, kwds)) {\n"; + } + } else if (max_num_args == 0) { + indent(out, indent_level) + << "if (Dtool_CheckNoArgs(args, kwds)) {\n"; + } else { + clear_error = true; + indent(out, indent_level) + << "if ((kwds == NULL || PyDict_Size(kwds) == 0) && PyArg_UnpackTuple(args, \"" + << methodNameFromCppName(remap, "", false) + << "\", " << min_num_args << ", " << max_num_args + << parameter_list << ")) {\n"; + } + + } else { + clear_error = true; + indent(out, indent_level) + << "if ((kwds == NULL || PyDict_Size(kwds) == 0) && PyArg_ParseTuple(args, \"" + << format_specifiers << ":" << method_name + << "\"" << parameter_list << ")) {\n"; + } ++open_scopes; - clear_error = true; indent_level += 2; break; @@ -5572,20 +5700,28 @@ write_function_instance(ostream &out, FunctionRemap *remap, if (only_pyobjects) { // All parameters are PyObject*, so we can use the slightly more // efficient PyArg_UnpackTuple function instead. - indent(out, indent_level) - << "if (PyArg_UnpackTuple(args, \"" - << methodNameFromCppName(remap, "", false) - << "\", " << min_num_args << ", " << max_num_args - << parameter_list << ")) {\n"; + if (min_num_args == 1 && max_num_args == 1) { + indent(out, indent_level) + << "if (PyTuple_GET_SIZE(args) == 1) {\n"; + indent(out, indent_level + 2) + << param_name << " = PyTuple_GET_ITEM(args, 0);\n"; + } else { + clear_error = true; + indent(out, indent_level) + << "if (PyArg_UnpackTuple(args, \"" + << methodNameFromCppName(remap, "", false) + << "\", " << min_num_args << ", " << max_num_args + << parameter_list << ")) {\n"; + } } else { + clear_error = true; indent(out, indent_level) << "if (PyArg_ParseTuple(args, \"" << format_specifiers << ":" << method_name << "\"" << parameter_list << ")) {\n"; } ++open_scopes; - clear_error = true; indent_level += 2; break; diff --git a/dtool/src/interrogate/interrogateBuilder.cxx b/dtool/src/interrogate/interrogateBuilder.cxx index 83f36c533b..c7ed2a056f 100644 --- a/dtool/src/interrogate/interrogateBuilder.cxx +++ b/dtool/src/interrogate/interrogateBuilder.cxx @@ -2346,6 +2346,10 @@ define_atomic_type(InterrogateType &itype, CPPSimpleType *cpptype) { itype._atomic_token = AT_void; break; + case CPPSimpleType::T_nullptr: + itype._atomic_token = AT_null; + break; + default: nout << "Type \"" << *cpptype << "\" has invalid CPPSimpleType: " << (int)cpptype->_type << "\n"; diff --git a/dtool/src/interrogate/typeManager.cxx b/dtool/src/interrogate/typeManager.cxx index db5b07f58a..995f677cc6 100644 --- a/dtool/src/interrogate/typeManager.cxx +++ b/dtool/src/interrogate/typeManager.cxx @@ -364,6 +364,27 @@ is_const_ref_to_enum(CPPType *type) { } } +/** + * Returns true if the indicated type is nullptr_t, possibly const or a + * typedef to it. + */ +bool TypeManager:: +is_nullptr(CPPType *type) { + switch (type->get_subtype()) { + case CPPDeclaration::ST_simple: + return type->as_simple_type()->_type == CPPSimpleType::T_nullptr; + + case CPPDeclaration::ST_const: + return is_nullptr(type->as_const_type()->_wrapped_around); + + case CPPDeclaration::ST_typedef: + return is_nullptr(type->as_typedef_type()->_type); + + default: + return false; + } +} + /** * Returns true if the indicated type is something that a scripting language * can handle directly as a concrete, like an int or float, either const or diff --git a/dtool/src/interrogate/typeManager.h b/dtool/src/interrogate/typeManager.h index 4e5356a24e..97b4bd8158 100644 --- a/dtool/src/interrogate/typeManager.h +++ b/dtool/src/interrogate/typeManager.h @@ -55,6 +55,7 @@ public: static bool is_enum(CPPType *type); static bool is_const_enum(CPPType *type); static bool is_const_ref_to_enum(CPPType *type); + static bool is_nullptr(CPPType *type); static bool is_simple(CPPType *type); static bool is_const_simple(CPPType *type); static bool is_const_ref_to_simple(CPPType *type); diff --git a/dtool/src/interrogatedb/interrogate_interface.h b/dtool/src/interrogatedb/interrogate_interface.h index b3003be2fe..63e385d527 100644 --- a/dtool/src/interrogatedb/interrogate_interface.h +++ b/dtool/src/interrogatedb/interrogate_interface.h @@ -82,7 +82,11 @@ enum AtomicToken { // string means whatever the native string representation is. AT_string = 7, - AT_longlong = 8 + AT_longlong = 8, + + // This is not a type that C has, but C++ and many scripting languages do; + // it indicates a null value, or the absence of any value. + AT_null = 9, }; EXPCL_INTERROGATEDB void interrogate_add_search_directory(const char *dirname); diff --git a/dtool/src/interrogatedb/py_panda.I b/dtool/src/interrogatedb/py_panda.I index 0a06358beb..3650525aba 100644 --- a/dtool/src/interrogatedb/py_panda.I +++ b/dtool/src/interrogatedb/py_panda.I @@ -59,6 +59,23 @@ DTool_CreatePyInstanceTyped(T *obj, bool memory_rules) { return DTool_CreatePyInstanceTyped((void*) obj, *known_class, memory_rules, false, obj->get_type().get_index()); } +/** + * Checks that the tuple is empty. + */ +ALWAYS_INLINE bool +Dtool_CheckNoArgs(PyObject *args) { + return PyTuple_GET_SIZE(args) == 0; +} + +/** + * Checks that the tuple is empty, and that the dict is empty or NULL. + */ +ALWAYS_INLINE bool +Dtool_CheckNoArgs(PyObject *args, PyObject *kwds) { + return PyTuple_GET_SIZE(args) == 0 && + (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0); +} + /** * The following functions wrap an arbitrary C++ value into a PyObject. */ @@ -196,6 +213,11 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(wchar_t value) { return PyUnicode_FromWideChar(&value, 1); } +ALWAYS_INLINE PyObject *Dtool_WrapValue(nullptr_t) { + Py_INCREF(Py_None); + return Py_None; +} + ALWAYS_INLINE PyObject *Dtool_WrapValue(PyObject *value) { return value; } diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 9fe9916573..1ea11eab47 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -31,6 +31,49 @@ static RuntimeTypeMap runtime_type_map; static RuntimeTypeSet runtime_type_set; static NamedTypeMap named_type_map; +#if PY_MAJOR_VERSION < 3 +/** + * Given a long or int, returns a size_t, or raises an OverflowError if it is + * out of range. + */ +size_t PyLongOrInt_AsSize_t(PyObject *vv) { + if (PyInt_Check(vv)) { + long value = PyInt_AS_LONG(vv); + if (value < 0) { + PyErr_SetString(PyExc_OverflowError, + "can't convert negative value to size_t"); + return (size_t)-1; + } + return (size_t)value; + } + + if (!PyLong_Check(vv)) { + PyErr_BadInternalCall(); + return (size_t)-1; + } + + size_t bytes; + int one = 1; + int res = _PyLong_AsByteArray((PyLongObject *)vv, (unsigned char *)&bytes, + SIZEOF_SIZE_T, (int)*(unsigned char*)&one, 0); + + if (res < 0) { + return (size_t)res; + } else { + return bytes; + } +} +#endif + +#if PY_VERSION_HEX < 0x03060000 +INLINE static PyObject *_PyObject_CallNoArg(PyObject *func) { + PyObject *args = PyTuple_New(0); + PyObject *result = PyObject_Call(func, args, NULL); + Py_DECREF(args); + return result; +} +#endif + /** * Given a valid (non-NULL) PyObject, does a simple check to see if it might * be an instance of a Panda type. It does this using a signature that is @@ -767,12 +810,16 @@ int DTOOL_PyObject_Compare(PyObject *v1, PyObject *v2) { if (func == NULL) { PyErr_Clear(); } else { +#if PY_VERSION_HEX >= 0x03060000 + PyObject *res = _PyObject_FastCall(func, &v2, 1); +#else PyObject *res = NULL; PyObject *args = PyTuple_Pack(1, v2); if (args != NULL) { res = PyObject_Call(func, args, NULL); Py_DECREF(args); } +#endif Py_DECREF(func); PyErr_Clear(); // just in case the function threw an error // only use if the function returns an INT... hmm @@ -844,7 +891,13 @@ PyObject *DTOOL_PyObject_RichCompare(PyObject *v1, PyObject *v2, int op) { * make_copy() method. */ PyObject *copy_from_make_copy(PyObject *self, PyObject *noargs) { - return PyObject_CallMethod(self, (char *)"make_copy", (char *)"()"); + PyObject *callable = PyObject_GetAttrString(self, "make_copy"); + if (callable == NULL) { + return NULL; + } + PyObject *result = _PyObject_CallNoArg(callable); + Py_DECREF(callable); + return result; } /** @@ -852,13 +905,15 @@ PyObject *copy_from_make_copy(PyObject *self, PyObject *noargs) { * copy constructor. */ PyObject *copy_from_copy_constructor(PyObject *self, PyObject *noargs) { - PyObject *this_class = PyObject_Type(self); - if (this_class == NULL) { - return NULL; - } + PyObject *callable = (PyObject *)Py_TYPE(self); - PyObject *result = PyObject_CallFunction(this_class, (char *)"(O)", self); - Py_DECREF(this_class); +#if PY_VERSION_HEX >= 0x03060000 + PyObject *result = _PyObject_FastCall(callable, &self, 1); +#else + PyObject *args = PyTuple_Pack(1, self); + PyObject *result = PyObject_Call(callable, args, NULL); + Py_DECREF(args); +#endif return result; } @@ -868,7 +923,109 @@ PyObject *copy_from_copy_constructor(PyObject *self, PyObject *noargs) { * __copy__(). */ PyObject *map_deepcopy_to_copy(PyObject *self, PyObject *args) { - return PyObject_CallMethod(self, (char *)"__copy__", (char *)"()"); + PyObject *callable = PyObject_GetAttrString(self, "__copy__"); + if (callable == NULL) { + return NULL; + } + PyObject *result = _PyObject_CallNoArg(callable); + Py_DECREF(callable); + return result; +} + +/** + * A more efficient version of PyArg_ParseTupleAndKeywords for the special + * case where there is only a single PyObject argument. + */ +bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds, + const char *keyword) { + + if (PyTuple_GET_SIZE(args) == 1) { + if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) { + *result = PyTuple_GET_ITEM(args, 0); + return true; + } + } else if (PyTuple_GET_SIZE(args) == 0) { + PyObject *key; + Py_ssize_t ppos = 0; + if (kwds != NULL && ((PyDictObject *)kwds)->ma_used == 1 && + PyDict_Next(kwds, &ppos, &key, result)) { + // We got the item, we just need to make sure that it had the right key. +#if PY_VERSION_HEX >= 0x03050000 + return PyUnicode_CheckExact(key) && _PyUnicode_EqualToASCIIString(key, keyword); +#elif PY_MAJOR_VERSION >= 3 + return PyUnicode_CheckExact(key) && PyUnicode_CompareWithASCIIString(key, keyword) == 0; +#else + return PyString_CheckExact(key) && strcmp(PyString_AS_STRING(key), keyword) == 0; +#endif + } + } + + return false; +} + +/** + * Variant of Dtool_ExtractArg that does not accept a keyword argument. + */ +bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds) { + if (PyTuple_GET_SIZE(args) == 1 && + (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0)) { + *result = PyTuple_GET_ITEM(args, 0); + return true; + } + return false; +} + +/** + * A more efficient version of PyArg_ParseTupleAndKeywords for the special + * case where there is only a single optional PyObject argument. + * + * Returns true if valid (including if there were 0 items), false if there was + * an error, such as an invalid number of parameters. + */ +bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds, + const char *keyword) { + + if (PyTuple_GET_SIZE(args) == 1) { + if (kwds == NULL || ((PyDictObject *)kwds)->ma_used == 0) { + *result = PyTuple_GET_ITEM(args, 0); + return true; + } + } else if (PyTuple_GET_SIZE(args) == 0) { + if (kwds != NULL && ((PyDictObject *)kwds)->ma_used == 1) { + PyObject *key; + Py_ssize_t ppos = 0; + if (!PyDict_Next(kwds, &ppos, &key, result)) { + return true; + } + + // We got the item, we just need to make sure that it had the right key. +#if PY_VERSION_HEX >= 0x03050000 + return PyUnicode_CheckExact(key) && _PyUnicode_EqualToASCIIString(key, keyword); +#elif PY_MAJOR_VERSION >= 3 + return PyUnicode_CheckExact(key) && PyUnicode_CompareWithASCIIString(key, keyword) == 0; +#else + return PyString_CheckExact(key) && strcmp(PyString_AS_STRING(key), keyword) == 0; +#endif + } else { + return true; + } + } + + return false; +} + +/** + * Variant of Dtool_ExtractOptionalArg that does not accept a keyword argument. + */ +bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds) { + if (kwds != NULL && ((PyDictObject *)kwds)->ma_used != 0) { + return false; + } + if (PyTuple_GET_SIZE(args) == 1) { + *result = PyTuple_GET_ITEM(args, 0); + return true; + } + return (PyTuple_GET_SIZE(args) == 0); } /** diff --git a/dtool/src/interrogatedb/py_panda.h b/dtool/src/interrogatedb/py_panda.h index d66ee88c21..c2cc584914 100644 --- a/dtool/src/interrogatedb/py_panda.h +++ b/dtool/src/interrogatedb/py_panda.h @@ -107,11 +107,14 @@ inline PyObject* doPy_RETURN_FALSE() #define PyInt_Check PyLong_Check #define PyInt_AsLong PyLong_AsLong #define PyInt_AS_LONG PyLong_AS_LONG +#define PyLongOrInt_AsSize_t PyLong_AsSize_t #else #define PyLongOrInt_Check(x) (PyInt_Check(x) || PyLong_Check(x)) // PyInt_FromSize_t automatically picks the right type. #define PyLongOrInt_AS_LONG PyInt_AsLong +EXPCL_INTERROGATEDB size_t PyLongOrInt_AsSize_t(PyObject *); + // For more portably defining hash functions. typedef long Py_hash_t; #endif @@ -469,6 +472,21 @@ struct Dtool_SequenceWrapper { EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type; +/** + * These functions check whether the arguments passed to a function conform to + * certain expectations. + */ +ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args); +ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args, PyObject *kwds); +EXPCL_INTERROGATEDB bool Dtool_ExtractArg(PyObject **result, PyObject *args, + PyObject *kwds, const char *keyword); +EXPCL_INTERROGATEDB bool Dtool_ExtractArg(PyObject **result, PyObject *args, + PyObject *kwds); +EXPCL_INTERROGATEDB bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, + PyObject *kwds, const char *keyword); +EXPCL_INTERROGATEDB bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, + PyObject *kwds); + /** * These functions convert a C++ value into the corresponding Python object. * This used to be generated by the code generator, but it seems more reliable @@ -491,6 +509,7 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::string *value); ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::wstring *value); ALWAYS_INLINE PyObject *Dtool_WrapValue(char value); ALWAYS_INLINE PyObject *Dtool_WrapValue(wchar_t value); +ALWAYS_INLINE PyObject *Dtool_WrapValue(nullptr_t); ALWAYS_INLINE PyObject *Dtool_WrapValue(PyObject *value); ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::vector &value); diff --git a/dtool/src/parser-inc/stddef.h b/dtool/src/parser-inc/stddef.h index 6867ac2156..c0ff839b74 100644 --- a/dtool/src/parser-inc/stddef.h +++ b/dtool/src/parser-inc/stddef.h @@ -23,7 +23,5 @@ #define offsetof(type,member) ((size_t) &(((type*)0)->member)) -typedef decltype(nullptr) nullptr_t; - #endif diff --git a/dtool/src/parser-inc/stdtypedefs.h b/dtool/src/parser-inc/stdtypedefs.h index a14a140d2f..c1febd3955 100644 --- a/dtool/src/parser-inc/stdtypedefs.h +++ b/dtool/src/parser-inc/stdtypedefs.h @@ -41,11 +41,7 @@ inline namespace std { struct timeval; -#ifdef __cplusplus -#define NULL 0L -#else -#define NULL ((void *)0) -#endif +typedef decltype(nullptr) nullptr_t; // One day, we might extend interrogate to be able to parse this, // but we currently don't need it. diff --git a/dtool/src/pystub/pystub.cxx b/dtool/src/pystub/pystub.cxx index 1ebad938bd..d1de5fb189 100644 --- a/dtool/src/pystub/pystub.cxx +++ b/dtool/src/pystub/pystub.cxx @@ -32,6 +32,7 @@ extern "C" { EXPCL_PYSTUB int PyDict_GetItem(...); EXPCL_PYSTUB int PyDict_GetItemString(...); EXPCL_PYSTUB int PyDict_New(...); + EXPCL_PYSTUB int PyDict_Next(...); EXPCL_PYSTUB int PyDict_SetItem(...); EXPCL_PYSTUB int PyDict_SetItemString(...); EXPCL_PYSTUB int PyDict_Size(...); @@ -49,7 +50,6 @@ extern "C" { EXPCL_PYSTUB int PyErr_WarnEx(...); EXPCL_PYSTUB int PyEval_CallFunction(...); EXPCL_PYSTUB int PyEval_CallObjectWithKeywords(...); - EXPCL_PYSTUB int PyEval_InitThreads(...); EXPCL_PYSTUB int PyEval_RestoreThread(...); EXPCL_PYSTUB int PyEval_SaveThread(...); EXPCL_PYSTUB int PyFloat_AsDouble(...); @@ -163,6 +163,7 @@ extern "C" { EXPCL_PYSTUB int PyUnicode_AsUTF8AndSize(...); EXPCL_PYSTUB int PyUnicode_AsWideChar(...); EXPCL_PYSTUB int PyUnicode_AsWideCharString(...); + EXPCL_PYSTUB int PyUnicode_CompareWithASCIIString(...); EXPCL_PYSTUB int PyUnicode_FromFormat(...); EXPCL_PYSTUB int PyUnicode_FromString(...); EXPCL_PYSTUB int PyUnicode_FromStringAndSize(...); @@ -180,12 +181,16 @@ extern "C" { EXPCL_PYSTUB int _PyArg_ParseTuple_SizeT(...); EXPCL_PYSTUB int _PyArg_ParseTupleAndKeywords_SizeT(...); EXPCL_PYSTUB int _PyArg_Parse_SizeT(...); + EXPCL_PYSTUB int _PyErr_BadInternalCall(...); + EXPCL_PYSTUB int _PyLong_AsByteArray(...); EXPCL_PYSTUB int _PyObject_CallFunction_SizeT(...); EXPCL_PYSTUB int _PyObject_CallMethod_SizeT(...); EXPCL_PYSTUB int _PyObject_DebugFree(...); EXPCL_PYSTUB int _PyObject_Del(...); + EXPCL_PYSTUB int _PyObject_FastCallDict(...); EXPCL_PYSTUB int _PyUnicode_AsString(...); EXPCL_PYSTUB int _PyUnicode_AsStringAndSize(...); + EXPCL_PYSTUB int _PyUnicode_EqualToASCIIString(...); EXPCL_PYSTUB int _Py_AddToAllObjects(...); EXPCL_PYSTUB int _Py_BuildValue_SizeT(...); EXPCL_PYSTUB int _Py_Dealloc(...); @@ -198,6 +203,7 @@ extern "C" { EXPCL_PYSTUB void Py_Initialize(); EXPCL_PYSTUB int Py_IsInitialized(); + EXPCL_PYSTUB void PyEval_InitThreads(); EXPCL_PYSTUB extern void *PyExc_AssertionError; EXPCL_PYSTUB extern void *PyExc_AttributeError; @@ -208,6 +214,7 @@ extern "C" { EXPCL_PYSTUB extern void *PyExc_ImportError; EXPCL_PYSTUB extern void *PyExc_IndexError; EXPCL_PYSTUB extern void *PyExc_OSError; + EXPCL_PYSTUB extern void *PyExc_OverflowError; EXPCL_PYSTUB extern void *PyExc_RuntimeError; EXPCL_PYSTUB extern void *PyExc_StandardError; EXPCL_PYSTUB extern void *PyExc_StopIteration; @@ -241,6 +248,7 @@ int PyDict_DelItemString(...) { return 0; } int PyDict_GetItem(...) { return 0; } int PyDict_GetItemString(...) { return 0; } int PyDict_New(...) { return 0; }; +int PyDict_Next(...) { return 0; }; int PyDict_SetItem(...) { return 0; }; int PyDict_SetItemString(...) { return 0; }; int PyDict_Size(...){ return 0; } @@ -372,6 +380,7 @@ int PyUnicode_AsUTF8(...) { return 0; } int PyUnicode_AsUTF8AndSize(...) { return 0; } int PyUnicode_AsWideChar(...) { return 0; } int PyUnicode_AsWideCharString(...) { return 0; } +int PyUnicode_CompareWithASCIIString(...) { return 0; } int PyUnicode_FromFormat(...) { return 0; } int PyUnicode_FromString(...) { return 0; } int PyUnicode_FromStringAndSize(...) { return 0; } @@ -389,12 +398,16 @@ int Py_InitModule4TraceRefs_64(...) { return 0; }; int _PyArg_ParseTuple_SizeT(...) { return 0; }; int _PyArg_ParseTupleAndKeywords_SizeT(...) { return 0; }; int _PyArg_Parse_SizeT(...) { return 0; }; +int _PyErr_BadInternalCall(...) { return 0; }; +int _PyLong_AsByteArray(...) { return 0; }; int _PyObject_CallFunction_SizeT(...) { return 0; }; int _PyObject_CallMethod_SizeT(...) { return 0; }; int _PyObject_DebugFree(...) { return 0; }; int _PyObject_Del(...) { return 0; }; +int _PyObject_FastCallDict(...) { return 0; }; int _PyUnicode_AsString(...) { return 0; }; int _PyUnicode_AsStringAndSize(...) { return 0; }; +int _PyUnicode_EqualToASCIIString(...) { return 0; }; int _Py_AddToAllObjects(...) { return 0; }; int _Py_BuildValue_SizeT(...) { return 0; }; int _Py_Dealloc(...) { return 0; }; @@ -411,6 +424,8 @@ void Py_Initialize() { int Py_IsInitialized() { return 0; } +void PyEval_InitThreads() { +} void *PyExc_AssertionError = (void *)NULL; @@ -422,6 +437,7 @@ void *PyExc_FutureWarning = (void *)NULL; void *PyExc_ImportError = (void *)NULL; void *PyExc_IndexError = (void *)NULL; void *PyExc_OSError = (void *)NULL; +void *PyExc_OverflowError = (void *)NULL; void *PyExc_RuntimeError = (void *)NULL; void *PyExc_StandardError = (void *)NULL; void *PyExc_StopIteration = (void *)NULL;