[clang] [libclang/python] Add the ErrorCode enumeration. (PR #182843)
Ebuka Ezike via cfe-commits
cfe-commits at lists.llvm.org
Tue Feb 24 06:53:33 PST 2026
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 <yerimyah1 at gmail.com>
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 <yerimyah1 at gmail.com>
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)
More information about the cfe-commits
mailing list