https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/182843
>From 8f7a6c529e09b4c4688c4fa87812edef7d39a22d Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Mon, 23 Feb 2026 13:08:11 +0000 Subject: [PATCH 1/2] [libclang/python] Add the ErrorCode enumeration. The TranslationUnitLoadError now adds more context in the exception thrown. --- clang/bindings/python/clang/cindex.py | 98 +++++++++++++++---- .../python/tests/cindex/test_enums.py | 37 ++++--- .../tests/cindex/test_translation_unit.py | 4 +- 3 files changed, 105 insertions(+), 34 deletions(-) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 1896a0a9c1c34..99ae7526e9820 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -182,10 +182,28 @@ class TranslationUnitLoadError(Exception): This is raised in the case where a TranslationUnit could not be instantiated due to failure in the libclang library. - FIXME: Make libclang expose additional error information in this scenario. """ - pass + def __init__(self, message: str, err_code: Optional[ErrorCode] = None): + assert isinstance(err_code, ErrorCode) + if err_code is None: + err_code = ErrorCode.FAILURE + assert err_code != ErrorCode.SUCCESS + + def get_error_info(err_code: ErrorCode) -> str: + if err_code == ErrorCode.FAILURE: + return "\nA generic error code, no further details are available." + if err_code == ErrorCode.CRASHED: + return "\nlibclang crashed while performing the requested operation." + if err_code == ErrorCode.INVALID_ARGUMENTS: + return "\nThe function detected that the arguments violate the function contract" + if err_code == ErrorCode.AST_READ_ERROR: + return "\nAn AST deserialization error has occurred." + return "" + + err_info = get_error_info(err_code) + + Exception.__init__(self, f"error {err_code.name}: {message}{err_info}") class TranslationUnitSaveError(Exception): @@ -1610,6 +1628,23 @@ class ExceptionSpecificationKind(BaseEnumeration): UNPARSED = 8 NOTHROW = 9 + +### ErrorCode ### +class ErrorCode(BaseEnumeration): + """ + Error codes returned by libclang routines. + + `ErrorCode.Success` is the only error code indicating success. Other + error codes. indicate errors. + """ + + SUCCESS = 0 + FAILURE = 1 + CRASHED = 2 + INVALID_ARGUMENTS = 3 + AST_READ_ERROR = 4 + + ### Cursors ### @@ -3528,20 +3563,24 @@ def from_source( unsaved_array = cls.process_unsaved_files(unsaved_files) - ptr = conf.lib.clang_parseTranslationUnit( - index, - os.fspath(filename) if filename is not None else None, - args_array, - len(args), - unsaved_array, - len(unsaved_files), - options, + tu_ptr = c_object_p() + err_code: ErrorCode = ErrorCode.from_id( + conf.lib.clang_parseTranslationUnit2( + index, + os.fspath(filename) if filename is not None else None, + args_array, + len(args), + unsaved_array, + len(unsaved_files), + options, + byref(tu_ptr), + ) ) - if not ptr: - raise TranslationUnitLoadError("Error parsing translation unit.") + if err_code != ErrorCode.SUCCESS: + raise TranslationUnitLoadError("Error parsing translation unit.", err_code) - return cls(ptr, index=index) + return cls(tu_ptr, index=index) @classmethod def from_ast_file(cls, filename, index=None): @@ -3561,11 +3600,16 @@ def from_ast_file(cls, filename, index=None): if index is None: index = Index.create() - ptr = conf.lib.clang_createTranslationUnit(index, os.fspath(filename)) - if not ptr: - raise TranslationUnitLoadError(filename) + tu_ptr = c_object_p() + err_id = conf.lib.clang_createTranslationUnit2( + index, os.fspath(filename), tu_ptr + ) + err_code: ErrorCode = ErrorCode.from_id(err_id) + + if err_code != ErrorCode.SUCCESS: + raise TranslationUnitLoadError(filename, err_code) - return cls(ptr=ptr, index=index) + return cls(ptr=tu_ptr, index=index) def __init__(self, ptr, index): """Create a TranslationUnit instance. @@ -4238,6 +4282,11 @@ def set_property(self, property, value): ("clang_codeCompleteGetNumDiagnostics", [CodeCompletionResults], c_int), ("clang_createIndex", [c_int, c_int], c_object_p), ("clang_createTranslationUnit", [Index, c_interop_string], c_object_p), + ( + "clang_createTranslationUnit2", + [Index, c_interop_string, POINTER(c_object_p)], + c_int, + ), ("clang_CXRewriter_create", [TranslationUnit], c_object_p), ("clang_CXRewriter_dispose", [Rewriter]), ("clang_CXRewriter_insertTextBefore", [Rewriter, SourceLocation, c_interop_string]), @@ -4411,6 +4460,20 @@ def set_property(self, property, value): [Index, c_interop_string, c_void_p, c_int, c_void_p, c_int, c_int], c_object_p, ), + ( + "clang_parseTranslationUnit2", + [ + Index, + c_interop_string, + c_void_p, + c_int, + c_void_p, + c_int, + c_int, + POINTER(c_object_p), # TranslationUnit, + ], + c_int, + ), ("clang_reparseTranslationUnit", [TranslationUnit, c_int, c_void_p, c_int], c_int), ("clang_saveTranslationUnit", [TranslationUnit, c_interop_string, c_uint], c_int), ( @@ -4609,6 +4672,7 @@ def get_cindex_library(self) -> CDLL: "CursorKind", "Cursor", "Diagnostic", + "ErrorCode", "ExceptionSpecificationKind", "File", "FixIt", diff --git a/clang/bindings/python/tests/cindex/test_enums.py b/clang/bindings/python/tests/cindex/test_enums.py index 283a54998470c..fe6e79f8b9b5a 100644 --- a/clang/bindings/python/tests/cindex/test_enums.py +++ b/clang/bindings/python/tests/cindex/test_enums.py @@ -8,6 +8,7 @@ CompletionChunkKind, CompletionString, CursorKind, + ErrorCode, ExceptionSpecificationKind, LanguageKind, LinkageKind, @@ -48,6 +49,7 @@ def test_all_variants(self): "CXBinaryOperatorKind": BinaryOperator, "CXCompletionChunkKind": CompletionChunkKind, "CXCursorKind": CursorKind, + "CXErrorCode": ErrorCode, "CXCursor_ExceptionSpecificationKind": ExceptionSpecificationKind, "CXLanguageKind": LanguageKind, "CXLinkageKind": LinkageKind, @@ -59,23 +61,26 @@ def test_all_variants(self): "CXTypeKind": TypeKind, } - indexheader = ( - Path(__file__).parent.parent.parent.parent.parent - / "include/clang-c/Index.h" - ) - # FIXME: Index.h is a C file, but we read it as a C++ file because we - # don't get ENUM_CONSTANT_DECL cursors otherwise, which we need here - # See bug report: https://github.com/llvm/llvm-project/issues/159075 - tu = TranslationUnit.from_source(indexheader, ["-x", "c++"]) - + include_path = Path(__file__).parent.parent.parent.parent.parent + indexheaders = [ + include_path / "include/clang-c/Index.h", + include_path / "include/clang-c/CXErrorCode.h", + ] enum_variant_map = {} - # For all enums in self.enums, extract all enum variants defined in Index.h - for cursor in tu.cursor.walk_preorder(): - if cursor.kind == CursorKind.ENUM_CONSTANT_DECL: - python_enum = cenum_to_pythonenum.get(cursor.type.spelling) - if python_enum not in enum_variant_map: - enum_variant_map[python_enum] = dict() - enum_variant_map[python_enum][cursor.enum_value] = cursor.spelling + + for indexheader in indexheaders: + # FIXME: The headers are C files, but we read it as a C++ file because we + # don't get ENUM_CONSTANT_DECL cursors otherwise, which we need here + # See bug report: https://github.com/llvm/llvm-project/issues/159075 + tu = TranslationUnit.from_source(str(indexheader), ["-x", "c++"]) + + # For all enums in self.enums, extract all enum variants defined in Index.h + for cursor in tu.cursor.walk_preorder(): + if cursor.kind == CursorKind.ENUM_CONSTANT_DECL: + python_enum = cenum_to_pythonenum.get(cursor.type.spelling) + if python_enum not in enum_variant_map: + enum_variant_map[python_enum] = dict() + enum_variant_map[python_enum][cursor.enum_value] = cursor.spelling for enum in self.enums: with self.subTest(enum): diff --git a/clang/bindings/python/tests/cindex/test_translation_unit.py b/clang/bindings/python/tests/cindex/test_translation_unit.py index d43cebcef3310..1f247e1fee863 100644 --- a/clang/bindings/python/tests/cindex/test_translation_unit.py +++ b/clang/bindings/python/tests/cindex/test_translation_unit.py @@ -3,6 +3,7 @@ from clang.cindex import ( Cursor, CursorKind, + ErrorCode, File, Index, SourceLocation, @@ -341,7 +342,8 @@ def test_fail_from_source(self): path = os.path.join(INPUTS_DIR, "non-existent.cpp") try: tu = TranslationUnit.from_source(path) - except TranslationUnitLoadError: + except TranslationUnitLoadError as err: + self.assertIn(ErrorCode.FAILURE.name, str(err)) tu = None self.assertEqual(tu, None) >From 26e2bd4588cd688c9ea601ee272be2f0ecbbb5af Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <[email protected]> Date: Tue, 24 Feb 2026 14:52:43 +0000 Subject: [PATCH 2/2] [libclang/python] Add review changes --- clang/bindings/python/clang/cindex.py | 89 +++++++------------ .../python/tests/cindex/test_enums.py | 37 ++++---- .../tests/cindex/test_translation_unit.py | 4 +- 3 files changed, 50 insertions(+), 80 deletions(-) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 99ae7526e9820..e5198d89bb0c6 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -182,28 +182,10 @@ class TranslationUnitLoadError(Exception): This is raised in the case where a TranslationUnit could not be instantiated due to failure in the libclang library. + FIXME: Make libclang expose additional error information in this scenario. """ - def __init__(self, message: str, err_code: Optional[ErrorCode] = None): - assert isinstance(err_code, ErrorCode) - if err_code is None: - err_code = ErrorCode.FAILURE - assert err_code != ErrorCode.SUCCESS - - def get_error_info(err_code: ErrorCode) -> str: - if err_code == ErrorCode.FAILURE: - return "\nA generic error code, no further details are available." - if err_code == ErrorCode.CRASHED: - return "\nlibclang crashed while performing the requested operation." - if err_code == ErrorCode.INVALID_ARGUMENTS: - return "\nThe function detected that the arguments violate the function contract" - if err_code == ErrorCode.AST_READ_ERROR: - return "\nAn AST deserialization error has occurred." - return "" - - err_info = get_error_info(err_code) - - Exception.__init__(self, f"error {err_code.name}: {message}{err_info}") + pass class TranslationUnitSaveError(Exception): @@ -1628,23 +1610,6 @@ class ExceptionSpecificationKind(BaseEnumeration): UNPARSED = 8 NOTHROW = 9 - -### ErrorCode ### -class ErrorCode(BaseEnumeration): - """ - Error codes returned by libclang routines. - - `ErrorCode.Success` is the only error code indicating success. Other - error codes. indicate errors. - """ - - SUCCESS = 0 - FAILURE = 1 - CRASHED = 2 - INVALID_ARGUMENTS = 3 - AST_READ_ERROR = 4 - - ### Cursors ### @@ -3505,6 +3470,21 @@ def process_unsaved_files(unsaved_files) -> Array[_CXUnsavedFile] | None: unsaved_array[i].length = len(binary_contents) return unsaved_array + @staticmethod + def __get_error_info(err_code: int) -> str: + # FIXME: the `err_code` currently mimics the values of CXErrorCode + # change once we have stablised a way of handling errors. + err_msg = "Error parsing translation unit." + if err_code == 1: + err_msg += "\nA generic error code, no further details are available." + elif err_code == 2: + err_msg += "\nlibclang crashed while performing the requested operation." + elif err_code == 3: + err_msg += "\nThe function detected that the arguments violate the function contract" + elif err_code == 4: + err_msg += "\nAn AST deserialization error has occurred." + return err_msg + @classmethod def from_source( cls, filename, args=None, unsaved_files=None, options=0, index=None @@ -3564,21 +3544,19 @@ def from_source( unsaved_array = cls.process_unsaved_files(unsaved_files) tu_ptr = c_object_p() - err_code: ErrorCode = ErrorCode.from_id( - conf.lib.clang_parseTranslationUnit2( - index, - os.fspath(filename) if filename is not None else None, - args_array, - len(args), - unsaved_array, - len(unsaved_files), - options, - byref(tu_ptr), - ) + err_code = conf.lib.clang_parseTranslationUnit2( + index, + os.fspath(filename) if filename is not None else None, + args_array, + len(args), + unsaved_array, + len(unsaved_files), + options, + byref(tu_ptr), ) - - if err_code != ErrorCode.SUCCESS: - raise TranslationUnitLoadError("Error parsing translation unit.", err_code) + err_message = TranslationUnit.__get_error_info(err_code) + if err_code != 0: + raise TranslationUnitLoadError(err_message) return cls(tu_ptr, index=index) @@ -3601,13 +3579,13 @@ def from_ast_file(cls, filename, index=None): index = Index.create() tu_ptr = c_object_p() - err_id = conf.lib.clang_createTranslationUnit2( + err_code = conf.lib.clang_createTranslationUnit2( index, os.fspath(filename), tu_ptr ) - err_code: ErrorCode = ErrorCode.from_id(err_id) - if err_code != ErrorCode.SUCCESS: - raise TranslationUnitLoadError(filename, err_code) + err_message = TranslationUnit.__get_error_info(err_code) + if err_code != 0: + raise TranslationUnitLoadError(err_message) return cls(ptr=tu_ptr, index=index) @@ -4672,7 +4650,6 @@ def get_cindex_library(self) -> CDLL: "CursorKind", "Cursor", "Diagnostic", - "ErrorCode", "ExceptionSpecificationKind", "File", "FixIt", diff --git a/clang/bindings/python/tests/cindex/test_enums.py b/clang/bindings/python/tests/cindex/test_enums.py index fe6e79f8b9b5a..283a54998470c 100644 --- a/clang/bindings/python/tests/cindex/test_enums.py +++ b/clang/bindings/python/tests/cindex/test_enums.py @@ -8,7 +8,6 @@ CompletionChunkKind, CompletionString, CursorKind, - ErrorCode, ExceptionSpecificationKind, LanguageKind, LinkageKind, @@ -49,7 +48,6 @@ def test_all_variants(self): "CXBinaryOperatorKind": BinaryOperator, "CXCompletionChunkKind": CompletionChunkKind, "CXCursorKind": CursorKind, - "CXErrorCode": ErrorCode, "CXCursor_ExceptionSpecificationKind": ExceptionSpecificationKind, "CXLanguageKind": LanguageKind, "CXLinkageKind": LinkageKind, @@ -61,26 +59,23 @@ def test_all_variants(self): "CXTypeKind": TypeKind, } - include_path = Path(__file__).parent.parent.parent.parent.parent - indexheaders = [ - include_path / "include/clang-c/Index.h", - include_path / "include/clang-c/CXErrorCode.h", - ] - enum_variant_map = {} - - for indexheader in indexheaders: - # FIXME: The headers are C files, but we read it as a C++ file because we - # don't get ENUM_CONSTANT_DECL cursors otherwise, which we need here - # See bug report: https://github.com/llvm/llvm-project/issues/159075 - tu = TranslationUnit.from_source(str(indexheader), ["-x", "c++"]) + indexheader = ( + Path(__file__).parent.parent.parent.parent.parent + / "include/clang-c/Index.h" + ) + # FIXME: Index.h is a C file, but we read it as a C++ file because we + # don't get ENUM_CONSTANT_DECL cursors otherwise, which we need here + # See bug report: https://github.com/llvm/llvm-project/issues/159075 + tu = TranslationUnit.from_source(indexheader, ["-x", "c++"]) - # For all enums in self.enums, extract all enum variants defined in Index.h - for cursor in tu.cursor.walk_preorder(): - if cursor.kind == CursorKind.ENUM_CONSTANT_DECL: - python_enum = cenum_to_pythonenum.get(cursor.type.spelling) - if python_enum not in enum_variant_map: - enum_variant_map[python_enum] = dict() - enum_variant_map[python_enum][cursor.enum_value] = cursor.spelling + enum_variant_map = {} + # For all enums in self.enums, extract all enum variants defined in Index.h + for cursor in tu.cursor.walk_preorder(): + if cursor.kind == CursorKind.ENUM_CONSTANT_DECL: + python_enum = cenum_to_pythonenum.get(cursor.type.spelling) + if python_enum not in enum_variant_map: + enum_variant_map[python_enum] = dict() + enum_variant_map[python_enum][cursor.enum_value] = cursor.spelling for enum in self.enums: with self.subTest(enum): diff --git a/clang/bindings/python/tests/cindex/test_translation_unit.py b/clang/bindings/python/tests/cindex/test_translation_unit.py index 1f247e1fee863..d43cebcef3310 100644 --- a/clang/bindings/python/tests/cindex/test_translation_unit.py +++ b/clang/bindings/python/tests/cindex/test_translation_unit.py @@ -3,7 +3,6 @@ from clang.cindex import ( Cursor, CursorKind, - ErrorCode, File, Index, SourceLocation, @@ -342,8 +341,7 @@ def test_fail_from_source(self): path = os.path.join(INPUTS_DIR, "non-existent.cpp") try: tu = TranslationUnit.from_source(path) - except TranslationUnitLoadError as err: - self.assertIn(ErrorCode.FAILURE.name, str(err)) + except TranslationUnitLoadError: tu = None self.assertEqual(tu, None) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
