diff --git a/clif/backend/matcher.cc b/clif/backend/matcher.cc index d790e1ae..a0300fe3 100644 --- a/clif/backend/matcher.cc +++ b/clif/backend/matcher.cc @@ -39,10 +39,6 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" -#ifndef PYCLIF_LLVM_VERSION_MAJOR -#define PYCLIF_LLVM_VERSION_MAJOR LLVM_VERSION_MAJOR -#endif - #if PYCLIF_LLVM_VERSION_MAJOR > 16 #include "clang/Sema/EnterExpressionEvaluationContext.h" #endif @@ -2989,20 +2985,20 @@ const clang::FunctionDecl* ClifMatcher::SpecializeFunctionTemplate( } std::string ClifMatcher::TemplateDeductionResult( - Sema::TemplateDeductionResult specialized_result) const { + PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_TYPE specialized_result) const { switch (specialized_result) { - case Sema::TDK_Invalid: + case PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_ENUM(Invalid): return "The template function declaration was invalid."; - case Sema::TDK_InstantiationDepth: + case PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_ENUM(InstantiationDepth): return "Template argument deduction exceeded the maximum template " "instantiation depth."; - case Sema::TDK_Incomplete: + case PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_ENUM(Incomplete): return "Template argument deduction did not deduce a value for every " "template parameter."; - case Sema::TDK_Inconsistent: + case PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_ENUM(Inconsistent): return "Template argument deduction produced inconsistent deduced " "values."; - case Sema::TDK_Underqualified: + case PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_ENUM(Underqualified): return "Template argument deduction failed due to inconsistent " "cv-qualifiers."; default: diff --git a/clif/backend/matcher.h b/clif/backend/matcher.h index d5313d68..802f8a9a 100644 --- a/clif/backend/matcher.h +++ b/clif/backend/matcher.h @@ -48,6 +48,10 @@ #include "clif/protos/ast.pb.h" #include "gtest/gtest_prod.h" // Defines FRIEND_TEST. +#ifndef PYCLIF_LLVM_VERSION_MAJOR +#define PYCLIF_LLVM_VERSION_MAJOR LLVM_VERSION_MAJOR +#endif + namespace clif { // ############################################################################ @@ -177,6 +181,16 @@ enum TypeMatchFlags : unsigned int { class ClifError; class ClifMatcherTest; +#if PYCLIF_LLVM_VERSION_MAJOR >= 19 // llvm/llvm-project#81398 +#define PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_TYPE clang::TemplateDeductionResult +#define PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_ENUM(Name) \ + clang::TemplateDeductionResult::Name +#else +#define PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_TYPE \ + clang::Sema::TemplateDeductionResult +#define PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_ENUM(Name) Sema::TDK_##Name +#endif + class ClifMatcher { friend class ClifMatcherTest; public: @@ -402,7 +416,7 @@ class ClifMatcher { // Transform template type deduction error codes into error messages. std::string TemplateDeductionResult( - clang::Sema::TemplateDeductionResult specialized_result) const; + PYCLIF_CLANG_TEMPLATEDEDUCTIONRESULT_TYPE specialized_result) const; // Add the class type as the first parameter to a function. void AdjustForNonClassMethods(protos::FuncDecl* clif_func_decl) const; diff --git a/clif/pybind11/classes.py b/clif/pybind11/classes.py index 4f4aa7b1..2fb2b61f 100644 --- a/clif/pybind11/classes.py +++ b/clif/pybind11/classes.py @@ -26,6 +26,8 @@ I = utils.I +_USE_PYTYPE_TYPE_AS_METACLASS = True + _KNOWN_TYPES_WITH_MULTIPLE_INHERITANCE = { '::borg::Config': ['::borg::ConfigArgs'] } @@ -100,6 +102,8 @@ def generate_from( base.cpp_canonical_type in codegen_info.dynamic_attr_types): enable_instance_dict = True break + if _USE_PYTYPE_TYPE_AS_METACLASS: + definition += ', py::metaclass((PyObject*) &PyType_Type)' definition += ', py::release_gil_before_calling_cpp_dtor()' if mi_bases: definition += ', py::multiple_inheritance()' @@ -117,7 +121,7 @@ def generate_from( for member in class_decl.members: if member.decltype == ast_pb2.Decl.Type.CONST: for s in consts.generate_from(class_name, member.const): - yield I + I + s + yield I + s elif member.decltype == ast_pb2.Decl.Type.FUNC: if member.func.name.native in ('__reduce__', '__reduce_ex__'): reduce_or_reduce_ex_defined = True diff --git a/clif/python/BUILD b/clif/python/BUILD index 7d239ff0..a970fe89 100644 --- a/clif/python/BUILD +++ b/clif/python/BUILD @@ -1,6 +1,7 @@ # CLIF python frontend load("@clif_python_deps//:requirements.bzl", "requirement") +load("//devtools/clif/python:clif_build_rule.bzl", "py_clif_cc") load("//third_party/bazel_rules/rules_python/python:py_test.bzl", "py_test") package( @@ -412,3 +413,25 @@ py_library( srcs_version = "PY2AND3", visibility = ["//visibility:public"], ) + +cc_library( + name = "meta_ext_lib", + hdrs = ["meta_ext.h"], +) + +py_clif_cc( + name = "_meta_ext", + srcs = ["meta_ext.clif"], + deps = [ + ":meta_ext_lib", + ], +) + +py_library( + name = "abc_utils", + srcs = ["abc_utils.py"], + visibility = ["//visibility:public"], + deps = [ + ":_meta_ext", + ], +) diff --git a/clif/python/CMakeLists.txt b/clif/python/CMakeLists.txt index a9875fc8..caa81df8 100644 --- a/clif/python/CMakeLists.txt +++ b/clif/python/CMakeLists.txt @@ -92,6 +92,7 @@ target_link_libraries(proto_util_cc PRIVATE ${PYTHON_LIBRARIES} ) +add_py_library(abc_utils abc_utils.py) add_py_library(callback_exception_guard callback_exception_guard.py) add_py_library(type_customization type_customization.py) add_py_library(postproc postproc.py) diff --git a/clif/python/abc_utils.py b/clif/python/abc_utils.py new file mode 100644 index 00000000..bcd6806c --- /dev/null +++ b/clif/python/abc_utils.py @@ -0,0 +1,24 @@ +"""PyCLIF-pybind11 abc compatibility (in particular abc.ABCMeta). + +This is for compatibility between the "default pybind11 metaclass" +(which is a custom metaclass) and https://docs.python.org/3/library/abc.html. + +For background see: + +* Description of https://github.com/pybind/pybind11/pull/5015 + +* Corresponding tests in clif/testing/python/classes_test.py +""" + +import abc +import typing + +PyCLIFMeta = type + + +if typing.TYPE_CHECKING: + PyCLIFABCMeta = abc.ABCMeta +else: + + class PyCLIFABCMeta(abc.ABCMeta, PyCLIFMeta): + pass diff --git a/clif/python/meta_ext.clif b/clif/python/meta_ext.clif new file mode 100644 index 00000000..2ccb5813 --- /dev/null +++ b/clif/python/meta_ext.clif @@ -0,0 +1,4 @@ +from "clif/python/meta_ext.h": + namespace `clif_meta_ext`: + class empty: + pass diff --git a/clif/python/meta_ext.h b/clif/python/meta_ext.h new file mode 100644 index 00000000..41862c68 --- /dev/null +++ b/clif/python/meta_ext.h @@ -0,0 +1,10 @@ +#ifndef CLIF_PYTHON_META_EXT_H_ +#define CLIF_PYTHON_META_EXT_H_ + +namespace clif_meta_ext { + +struct empty {}; + +} // namespace clif_meta_ext + +#endif // CLIF_PYTHON_META_EXT_H_ diff --git a/clif/python/primer.md b/clif/python/primer.md index 4672020e..a0207560 100644 --- a/clif/python/primer.md +++ b/clif/python/primer.md @@ -713,8 +713,8 @@ of the return values as positional arguments. Whatever it returns becomes the actual return value. Some commonly needed post-processors have been provided in the above imported -library with CLIF. You can also -provide your own Python post-processor library. +library with CLIF. +You can also provide your own Python post-processor library. ## Naming Rules in CLIF Files {#naming_rules} diff --git a/clif/python/types.cc b/clif/python/types.cc index 75a86d97..0e573331 100644 --- a/clif/python/types.cc +++ b/clif/python/types.cc @@ -209,7 +209,6 @@ bool Clif_PyObjAs(PyObject* py, long* c) { //NOLINT: runtime/int } // int64 -#ifdef HAVE_LONG_LONG bool Clif_PyObjAs(PyObject* py, long long* c) { //NOLINT: runtime/int CHECK(c != nullptr); if (!PyLong_Check(py)) { @@ -270,7 +269,6 @@ bool Clif_PyObjAs(PyObject* py, absl::uint128* c) { // NOLINT: runtime/int } return !PyErr_Occurred(); } -#endif // HAVE_LONG_LONG #ifdef ABSL_HAVE_INTRINSIC_INT128 bool Clif_PyObjAs(PyObject* py, __int128* c) { diff --git a/clif/python/types.h b/clif/python/types.h index 174f5c7a..4c635c3e 100644 --- a/clif/python/types.h +++ b/clif/python/types.h @@ -102,7 +102,6 @@ inline PyObject* Clif_PyObjFrom(unsigned long c, // NOLINT runtime/int return pc.Apply(PyLong_FromSize_t(c)); } // CLIF use `int64` as int64 -#ifdef HAVE_LONG_LONG inline PyObject* Clif_PyObjFrom(long long c, // NOLINT runtime/int const py::PostConv& pc) { return pc.Apply(PyLong_FromLongLong(c)); @@ -128,7 +127,6 @@ inline PyObject* Clif_PyObjFrom(absl::uint128 c, const py::PostConv& pc) { auto lo = PyLong_FromUnsignedLongLong(absl::Uint128Low64(c)); return pc.Apply(PyNumber_Add(hi, lo)); } -#endif #ifdef ABSL_HAVE_INTRINSIC_INT128 // CLIF use2 `__int128` as int @@ -195,12 +193,10 @@ bool Clif_PyObjAs(PyObject*, unsigned char*); bool Clif_PyObjAs(PyObject*, unsigned short*); // NOLINT runtime/int bool Clif_PyObjAs(PyObject*, unsigned int*); bool Clif_PyObjAs(PyObject*, unsigned long*); // NOLINT runtime/int -#ifdef HAVE_LONG_LONG bool Clif_PyObjAs(PyObject*, unsigned long long*); // NOLINT runtime/int bool Clif_PyObjAs(PyObject*, long long*); // NOLINT runtime/int bool Clif_PyObjAs(PyObject*, absl::int128*); // NOLINT runtime/int bool Clif_PyObjAs(PyObject*, absl::uint128*); // NOLINT runtime/int -#endif #ifdef ABSL_HAVE_INTRINSIC_INT128 bool Clif_PyObjAs(PyObject*, __int128*); bool Clif_PyObjAs(PyObject*, unsigned __int128*); diff --git a/clif/testing/python/CMakeLists.txt b/clif/testing/python/CMakeLists.txt index 753fe372..911b880b 100644 --- a/clif/testing/python/CMakeLists.txt +++ b/clif/testing/python/CMakeLists.txt @@ -93,7 +93,9 @@ add_pyclif_library_for_test(call_method call_method.clif) add_pyclif_library_for_test(circular circular.clif) -add_pyclif_library_for_test(classes classes.clif) +add_pyclif_library_for_test(classes classes.clif + PY_DEPS clif_python_abc_utils +) add_pyclif_library_for_test(class_module_attr class_module_attr.clif) diff --git a/clif/testing/python/classes_test.py b/clif/testing/python/classes_test.py index 0a598dd2..65c7e171 100644 --- a/clif/testing/python/classes_test.py +++ b/clif/testing/python/classes_test.py @@ -14,11 +14,13 @@ """Tests for clif.testing.python.classes.""" +import abc from unittest import mock from absl.testing import absltest from absl.testing import parameterized +from clif.python import abc_utils from clif.testing.python import classes @@ -35,6 +37,20 @@ def testKlass(self): # AttributeError on CPython; TypeError on PyPy. with self.assertRaises((AttributeError, TypeError)): k.i2 = 0 + self.assertEqual(classes.Klass.C, 1) + self.assertEqual(k.C, 1) + with self.assertRaisesRegex(AttributeError, 'read-only'): + k.C = 0 + + # UNDESIRABLE but long established behavior. + classes.Klass.C = 0 # C++ const but not read-only in Python. + self.assertEqual(classes.Klass.C, 0) + self.assertEqual(k.C, 0) + # Restore original value, to minimize the potential for surprises, + # just in case. + classes.Klass.C = 1 + self.assertEqual(classes.Klass.C, 1) + self.assertEqual(k.C, 1) def testMockIsRejected(self): k_inst = classes.Klass(3) @@ -212,6 +228,42 @@ def testNestedUnproperty(self, attr, expected, new_value, new_expected): ret = getattr(obj, getter)() self.assertEqual(ret, new_expected) + def testABCMeta(self): + # Purely to help the linter. + ABCMeta = abc.ABCMeta # pylint: disable=unused-variable + + if abc_utils.PyCLIFMeta is type: + + class KlassABCMeta(classes.Klass, metaclass=ABCMeta): + pass + + self.assertEqual(type(classes.Klass).__name__, 'type') + km = KlassABCMeta(5) + self.assertEqual(km.i, 5) + + else: + self.assertEqual(classes.__pyclif_codegen_mode__, 'pybind11') + with self.assertRaisesRegex(TypeError, 'metaclass conflict'): + + class KlassABCMeta(classes.Klass, metaclass=ABCMeta): + pass + + def testPyCLIFABCMeta(self): + # Purely to help the linter. + PyCLIFABCMeta = abc_utils.PyCLIFABCMeta # pylint: disable=unused-variable,invalid-name + + class KlassPyCLIFABCMeta(classes.Klass, metaclass=PyCLIFABCMeta): + pass + + if abc_utils.PyCLIFMeta is type: + expected_type_name = 'type' + else: + self.assertEqual(classes.__pyclif_codegen_mode__, 'pybind11') + expected_type_name = 'pybind11_type' + self.assertEqual(type(classes.Klass).__name__, expected_type_name) + km = KlassPyCLIFABCMeta(5) + self.assertEqual(km.i, 5) + if __name__ == '__main__': absltest.main()