[clang] [libclang/python] Add typing annotations for the Cursor class (PR #138103)
Jannick Kremer via cfe-commits
cfe-commits at lists.llvm.org
Wed May 14 22:03:19 PDT 2025
https://github.com/DeinAlptraum updated https://github.com/llvm/llvm-project/pull/138103
>From 65ad7a888dafc9992793baee8a1ccc27f1b79fa5 Mon Sep 17 00:00:00 2001
From: Jannick Kremer <jannick.kremer at mailbox.org>
Date: Thu, 1 May 2025 18:37:43 +0900
Subject: [PATCH 1/5] [libclang/python] Add typing annotations for the Cursor
class
---
clang/bindings/python/clang/cindex.py | 154 +++++++++++++-------------
1 file changed, 80 insertions(+), 74 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 4ff7f318416b7..2b0c0b12f1f5d 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -73,6 +73,7 @@
Callable,
cast as Tcast,
Generic,
+ Iterator,
Optional,
Sequence,
Type as TType,
@@ -1552,68 +1553,70 @@ class Cursor(Structure):
_fields_ = [("_kind_id", c_int), ("xdata", c_int), ("data", c_void_p * 3)]
+ _tu: TranslationUnit
+
@staticmethod
- def from_location(tu, location):
+ def from_location(tu: TranslationUnit, location: SourceLocation) -> Cursor:
# We store a reference to the TU in the instance so the TU won't get
# collected before the cursor.
- cursor = conf.lib.clang_getCursor(tu, location)
+ cursor: Cursor = conf.lib.clang_getCursor(tu, location)
cursor._tu = tu
return cursor
- def __eq__(self, other):
+ def __eq__(self, other: object) -> bool:
if not isinstance(other, Cursor):
return False
return conf.lib.clang_equalCursors(self, other) # type: ignore [no-any-return]
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
return self.hash
- def is_definition(self):
+ def is_definition(self) -> bool:
"""
Returns true if the declaration pointed at by the cursor is also a
definition of that entity.
"""
return conf.lib.clang_isCursorDefinition(self) # type: ignore [no-any-return]
- def is_const_method(self):
+ def is_const_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared 'const'.
"""
return conf.lib.clang_CXXMethod_isConst(self) # type: ignore [no-any-return]
- def is_converting_constructor(self):
+ def is_converting_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ converting constructor."""
return conf.lib.clang_CXXConstructor_isConvertingConstructor(self) # type: ignore [no-any-return]
- def is_copy_constructor(self):
+ def is_copy_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ copy constructor."""
return conf.lib.clang_CXXConstructor_isCopyConstructor(self) # type: ignore [no-any-return]
- def is_default_constructor(self):
+ def is_default_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ default constructor."""
return conf.lib.clang_CXXConstructor_isDefaultConstructor(self) # type: ignore [no-any-return]
- def is_move_constructor(self):
+ def is_move_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ move constructor."""
return conf.lib.clang_CXXConstructor_isMoveConstructor(self) # type: ignore [no-any-return]
- def is_default_method(self):
+ def is_default_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared '= default'.
"""
return conf.lib.clang_CXXMethod_isDefaulted(self) # type: ignore [no-any-return]
- def is_deleted_method(self):
+ def is_deleted_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared '= delete'.
"""
return conf.lib.clang_CXXMethod_isDeleted(self) # type: ignore [no-any-return]
- def is_copy_assignment_operator_method(self):
+ def is_copy_assignment_operator_method(self) -> bool:
"""Returnrs True if the cursor refers to a copy-assignment operator.
A copy-assignment operator `X::operator=` is a non-static,
@@ -1638,7 +1641,7 @@ class Bar {
"""
return conf.lib.clang_CXXMethod_isCopyAssignmentOperator(self) # type: ignore [no-any-return]
- def is_move_assignment_operator_method(self):
+ def is_move_assignment_operator_method(self) -> bool:
"""Returnrs True if the cursor refers to a move-assignment operator.
A move-assignment operator `X::operator=` is a non-static,
@@ -1663,7 +1666,7 @@ class Bar {
"""
return conf.lib.clang_CXXMethod_isMoveAssignmentOperator(self) # type: ignore [no-any-return]
- def is_explicit_method(self):
+ def is_explicit_method(self) -> bool:
"""Determines if a C++ constructor or conversion function is
explicit, returning 1 if such is the case and 0 otherwise.
@@ -1708,41 +1711,41 @@ class Foo {
"""
return conf.lib.clang_CXXMethod_isExplicit(self) # type: ignore [no-any-return]
- def is_mutable_field(self):
+ def is_mutable_field(self) -> bool:
"""Returns True if the cursor refers to a C++ field that is declared
'mutable'.
"""
return conf.lib.clang_CXXField_isMutable(self) # type: ignore [no-any-return]
- def is_pure_virtual_method(self):
+ def is_pure_virtual_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared pure virtual.
"""
return conf.lib.clang_CXXMethod_isPureVirtual(self) # type: ignore [no-any-return]
- def is_static_method(self):
+ def is_static_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared 'static'.
"""
return conf.lib.clang_CXXMethod_isStatic(self) # type: ignore [no-any-return]
- def is_virtual_method(self):
+ def is_virtual_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared 'virtual'.
"""
return conf.lib.clang_CXXMethod_isVirtual(self) # type: ignore [no-any-return]
- def is_abstract_record(self):
+ def is_abstract_record(self) -> bool:
"""Returns True if the cursor refers to a C++ record declaration
that has pure virtual member functions.
"""
return conf.lib.clang_CXXRecord_isAbstract(self) # type: ignore [no-any-return]
- def is_scoped_enum(self):
+ def is_scoped_enum(self) -> bool:
"""Returns True if the cursor refers to a scoped enum declaration."""
return conf.lib.clang_EnumDecl_isScoped(self) # type: ignore [no-any-return]
- def get_definition(self):
+ def get_definition(self) -> Cursor | None:
"""
If the cursor is a reference to a declaration or a declaration of
some entity, return a cursor that points to the definition of that
@@ -1752,7 +1755,7 @@ def get_definition(self):
# declaration prior to issuing the lookup.
return Cursor.from_result(conf.lib.clang_getCursorDefinition(self), self)
- def get_usr(self):
+ def get_usr(self) -> str:
"""Return the Unified Symbol Resolution (USR) for the entity referenced
by the given cursor.
@@ -1763,19 +1766,19 @@ def get_usr(self):
another translation unit."""
return _CXString.from_result(conf.lib.clang_getCursorUSR(self))
- def get_included_file(self):
+ def get_included_file(self) -> File:
"""Returns the File that is included by the current inclusion cursor."""
assert self.kind == CursorKind.INCLUSION_DIRECTIVE
return File.from_result(conf.lib.clang_getIncludedFile(self), self)
@property
- def kind(self):
+ def kind(self) -> CursorKind:
"""Return the kind of this cursor."""
return CursorKind.from_id(self._kind_id)
@property
- def spelling(self):
+ def spelling(self) -> str:
"""Return the spelling of the entity pointed at by the cursor."""
if not hasattr(self, "_spelling"):
self._spelling = _CXString.from_result(
@@ -1784,7 +1787,7 @@ def spelling(self):
return self._spelling
- def pretty_printed(self, policy):
+ def pretty_printed(self, policy: PrintingPolicy) -> str:
"""
Pretty print declarations.
Parameters:
@@ -1795,7 +1798,7 @@ def pretty_printed(self, policy):
)
@property
- def displayname(self):
+ def displayname(self) -> str:
"""
Return the display name for the entity referenced by this cursor.
@@ -1811,7 +1814,7 @@ def displayname(self):
return self._displayname
@property
- def mangled_name(self):
+ def mangled_name(self) -> str:
"""Return the mangled name for the entity referenced by this cursor."""
if not hasattr(self, "_mangled_name"):
self._mangled_name = _CXString.from_result(
@@ -1821,18 +1824,18 @@ def mangled_name(self):
return self._mangled_name
@property
- def location(self):
+ def location(self) -> SourceLocation:
"""
Return the source location (the starting character) of the entity
pointed at by the cursor.
"""
if not hasattr(self, "_loc"):
- self._loc = conf.lib.clang_getCursorLocation(self)
+ self._loc: SourceLocation = conf.lib.clang_getCursorLocation(self)
return self._loc
@property
- def linkage(self):
+ def linkage(self) -> LinkageKind:
"""Return the linkage of this cursor."""
if not hasattr(self, "_linkage"):
self._linkage = conf.lib.clang_getCursorLinkage(self)
@@ -1840,7 +1843,7 @@ def linkage(self):
return LinkageKind.from_id(self._linkage)
@property
- def tls_kind(self):
+ def tls_kind(self) -> TLSKind:
"""Return the thread-local storage (TLS) kind of this cursor."""
if not hasattr(self, "_tls_kind"):
self._tls_kind = conf.lib.clang_getCursorTLSKind(self)
@@ -1848,18 +1851,18 @@ def tls_kind(self):
return TLSKind.from_id(self._tls_kind)
@property
- def extent(self):
+ def extent(self) -> SourceRange:
"""
Return the source range (the range of text) occupied by the entity
pointed at by the cursor.
"""
if not hasattr(self, "_extent"):
- self._extent = conf.lib.clang_getCursorExtent(self)
+ self._extent: SourceRange = conf.lib.clang_getCursorExtent(self)
return self._extent
@property
- def storage_class(self):
+ def storage_class(self) -> StorageClass:
"""
Retrieves the storage class (if any) of the entity pointed at by the
cursor.
@@ -1870,7 +1873,7 @@ def storage_class(self):
return StorageClass.from_id(self._storage_class)
@property
- def availability(self):
+ def availability(self) -> AvailabilityKind:
"""
Retrieves the availability of the entity pointed at by the cursor.
"""
@@ -1880,7 +1883,7 @@ def availability(self):
return AvailabilityKind.from_id(self._availability)
@property
- def binary_operator(self):
+ def binary_operator(self) -> BinaryOperator:
"""
Retrieves the opcode if this cursor points to a binary operator
:return:
@@ -1892,7 +1895,7 @@ def binary_operator(self):
return BinaryOperator.from_id(self._binopcode)
@property
- def access_specifier(self):
+ def access_specifier(self) -> AccessSpecifier:
"""
Retrieves the access specifier (if any) of the entity pointed at by the
cursor.
@@ -1903,7 +1906,7 @@ def access_specifier(self):
return AccessSpecifier.from_id(self._access_specifier)
@property
- def type(self):
+ def type(self) -> Type:
"""
Retrieve the Type (if any) of the entity pointed at by the cursor.
"""
@@ -1913,7 +1916,7 @@ def type(self):
return self._type
@property
- def canonical(self):
+ def canonical(self) -> Cursor | None:
"""Return the canonical Cursor corresponding to this Cursor.
The canonical cursor is the cursor which is representative for the
@@ -1929,7 +1932,7 @@ def canonical(self):
return self._canonical
@property
- def result_type(self):
+ def result_type(self) -> Type:
"""Retrieve the Type of the result for this Cursor."""
if not hasattr(self, "_result_type"):
self._result_type = Type.from_result(
@@ -1939,7 +1942,7 @@ def result_type(self):
return self._result_type
@property
- def exception_specification_kind(self):
+ def exception_specification_kind(self) -> ExceptionSpecificationKind:
"""
Retrieve the exception specification kind, which is one of the values
from the ExceptionSpecificationKind enumeration.
@@ -1953,7 +1956,7 @@ def exception_specification_kind(self):
return self._exception_specification_kind
@property
- def underlying_typedef_type(self):
+ def underlying_typedef_type(self) -> Type:
"""Return the underlying type of a typedef declaration.
Returns a Type for the typedef this cursor is a declaration for. If
@@ -1968,7 +1971,7 @@ def underlying_typedef_type(self):
return self._underlying_type
@property
- def enum_type(self):
+ def enum_type(self) -> Type:
"""Return the integer type of an enum declaration.
Returns a Type corresponding to an integer. If the cursor is not for an
@@ -1983,9 +1986,10 @@ def enum_type(self):
return self._enum_type
@property
- def enum_value(self):
+ def enum_value(self) -> int:
"""Return the value of an enum constant."""
if not hasattr(self, "_enum_value"):
+ self._enum_value: int
assert self.kind == CursorKind.ENUM_CONSTANT_DECL
# Figure out the underlying type of the enum to know if it
# is a signed or unsigned quantity.
@@ -2009,7 +2013,7 @@ def enum_value(self):
return self._enum_value
@property
- def objc_type_encoding(self):
+ def objc_type_encoding(self) -> str:
"""Return the Objective-C type encoding as a str."""
if not hasattr(self, "_objc_type_encoding"):
self._objc_type_encoding = _CXString.from_result(
@@ -2019,15 +2023,15 @@ def objc_type_encoding(self):
return self._objc_type_encoding
@property
- def hash(self):
+ def hash(self) -> int:
"""Returns a hash of the cursor as an int."""
if not hasattr(self, "_hash"):
- self._hash = conf.lib.clang_hashCursor(self)
+ self._hash: int = conf.lib.clang_hashCursor(self)
return self._hash
@property
- def semantic_parent(self):
+ def semantic_parent(self) -> Cursor | None:
"""Return the semantic parent for this cursor."""
if not hasattr(self, "_semantic_parent"):
self._semantic_parent = Cursor.from_cursor_result(
@@ -2037,7 +2041,7 @@ def semantic_parent(self):
return self._semantic_parent
@property
- def lexical_parent(self):
+ def lexical_parent(self) -> Cursor | None:
"""Return the lexical parent for this cursor."""
if not hasattr(self, "_lexical_parent"):
self._lexical_parent = Cursor.from_cursor_result(
@@ -2054,14 +2058,14 @@ def specialized_template(self) -> Cursor | None:
)
@property
- def translation_unit(self):
+ def translation_unit(self) -> TranslationUnit:
"""Returns the TranslationUnit to which this Cursor belongs."""
# If this triggers an AttributeError, the instance was not properly
# created.
return self._tu
@property
- def referenced(self):
+ def referenced(self) -> Cursor | None:
"""
For a cursor that is a reference, returns a cursor
representing the entity that it references.
@@ -2074,51 +2078,51 @@ def referenced(self):
return self._referenced
@property
- def brief_comment(self):
+ def brief_comment(self) -> str:
"""Returns the brief comment text associated with that Cursor"""
return _CXString.from_result(conf.lib.clang_Cursor_getBriefCommentText(self))
@property
- def raw_comment(self):
+ def raw_comment(self) -> str:
"""Returns the raw comment text associated with that Cursor"""
return _CXString.from_result(conf.lib.clang_Cursor_getRawCommentText(self))
- def get_arguments(self):
+ def get_arguments(self) -> Iterator[Cursor | None]:
"""Return an iterator for accessing the arguments of this cursor."""
num_args = conf.lib.clang_Cursor_getNumArguments(self)
for i in range(0, num_args):
yield Cursor.from_result(conf.lib.clang_Cursor_getArgument(self, i), self)
- def get_num_template_arguments(self):
+ def get_num_template_arguments(self) -> int:
"""Returns the number of template args associated with this cursor."""
return conf.lib.clang_Cursor_getNumTemplateArguments(self) # type: ignore [no-any-return]
- def get_template_argument_kind(self, num):
+ def get_template_argument_kind(self, num: int) -> TemplateArgumentKind:
"""Returns the TemplateArgumentKind for the indicated template
argument."""
return TemplateArgumentKind.from_id(
conf.lib.clang_Cursor_getTemplateArgumentKind(self, num)
)
- def get_template_argument_type(self, num):
+ def get_template_argument_type(self, num: int) -> Type:
"""Returns the CXType for the indicated template argument."""
return Type.from_result(
conf.lib.clang_Cursor_getTemplateArgumentType(self, num), (self, num)
)
- def get_template_argument_value(self, num):
+ def get_template_argument_value(self, num: int) -> int:
"""Returns the value of the indicated arg as a signed 64b integer."""
return conf.lib.clang_Cursor_getTemplateArgumentValue(self, num) # type: ignore [no-any-return]
- def get_template_argument_unsigned_value(self, num):
+ def get_template_argument_unsigned_value(self, num: int) -> int:
"""Returns the value of the indicated arg as an unsigned 64b integer."""
return conf.lib.clang_Cursor_getTemplateArgumentUnsignedValue(self, num) # type: ignore [no-any-return]
- def get_children(self):
+ def get_children(self) -> Iterator[Cursor]:
"""Return an iterator for accessing the children of this cursor."""
# FIXME: Expose iteration from CIndex, PR6125.
- def visitor(child, parent, children):
+ def visitor(child: Cursor, _: Cursor, children: list[Cursor]) -> int:
# FIXME: Document this assertion in API.
# FIXME: There should just be an isNull method.
assert child != conf.lib.clang_getNullCursor()
@@ -2132,7 +2136,7 @@ def visitor(child, parent, children):
conf.lib.clang_visitChildren(self, cursor_visit_callback(visitor), children)
return iter(children)
- def walk_preorder(self):
+ def walk_preorder(self) -> Iterator[Cursor]:
"""Depth-first preorder walk over the cursor and its descendants.
Yields cursors.
@@ -2142,7 +2146,7 @@ def walk_preorder(self):
for descendant in child.walk_preorder():
yield descendant
- def get_tokens(self):
+ def get_tokens(self) -> Iterator[Token]:
"""Obtain Token instances formulating that compose this Cursor.
This is a generator for Token instances. It returns all tokens which
@@ -2150,19 +2154,19 @@ def get_tokens(self):
"""
return TokenGroup.get_tokens(self._tu, self.extent)
- def get_field_offsetof(self):
+ def get_field_offsetof(self) -> int:
"""Returns the offsetof the FIELD_DECL pointed by this Cursor."""
return conf.lib.clang_Cursor_getOffsetOfField(self) # type: ignore [no-any-return]
- def get_base_offsetof(self, parent):
+ def get_base_offsetof(self, parent: Cursor) -> int:
"""Returns the offsetof the CXX_BASE_SPECIFIER pointed by this Cursor."""
return conf.lib.clang_getOffsetOfBase(parent, self) # type: ignore [no-any-return]
- def is_virtual_base(self):
+ def is_virtual_base(self) -> bool:
"""Returns whether the CXX_BASE_SPECIFIER pointed by this Cursor is virtual."""
return conf.lib.clang_isVirtualBase(self) # type: ignore [no-any-return]
- def is_anonymous(self):
+ def is_anonymous(self) -> bool:
"""
Check whether this is a record type without a name, or a field where
the type is a record type without a name.
@@ -2174,7 +2178,7 @@ def is_anonymous(self):
return self.type.get_declaration().is_anonymous()
return conf.lib.clang_Cursor_isAnonymous(self) # type: ignore [no-any-return]
- def is_anonymous_record_decl(self):
+ def is_anonymous_record_decl(self) -> bool:
"""
Check if the record is an anonymous union as defined in the C/C++ standard
(or an "anonymous struct", the corresponding non-standard extension for
@@ -2184,13 +2188,13 @@ def is_anonymous_record_decl(self):
return self.type.get_declaration().is_anonymous_record_decl()
return conf.lib.clang_Cursor_isAnonymousRecordDecl(self) # type: ignore [no-any-return]
- def is_bitfield(self):
+ def is_bitfield(self) -> bool:
"""
Check if the field is a bitfield.
"""
return conf.lib.clang_Cursor_isBitField(self) # type: ignore [no-any-return]
- def get_bitfield_width(self):
+ def get_bitfield_width(self) -> int:
"""
Retrieve the width of a bitfield.
"""
@@ -2203,7 +2207,9 @@ def has_attrs(self) -> bool:
return bool(conf.lib.clang_Cursor_hasAttrs(self))
@staticmethod
- def from_result(res, arg):
+ def from_result(
+ res: Cursor, arg: Cursor | TranslationUnit | Type
+ ) -> Cursor | None:
assert isinstance(res, Cursor)
# FIXME: There should just be an isNull method.
if res == conf.lib.clang_getNullCursor():
@@ -2223,7 +2229,7 @@ def from_result(res, arg):
return res
@staticmethod
- def from_cursor_result(res, arg):
+ def from_cursor_result(res: Cursor, arg: Cursor) -> Cursor | None:
assert isinstance(res, Cursor)
if res == conf.lib.clang_getNullCursor():
return None
>From 41b7247f36578f9976b5c50f8027934f3d1030bb Mon Sep 17 00:00:00 2001
From: Jannick Kremer <jannick.kremer at mailbox.org>
Date: Thu, 1 May 2025 18:42:26 +0900
Subject: [PATCH 2/5] Fix formatting
---
clang/bindings/python/clang/cindex.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 2b0c0b12f1f5d..38a392483d803 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -2207,9 +2207,7 @@ def has_attrs(self) -> bool:
return bool(conf.lib.clang_Cursor_hasAttrs(self))
@staticmethod
- def from_result(
- res: Cursor, arg: Cursor | TranslationUnit | Type
- ) -> Cursor | None:
+ def from_result(res: Cursor, arg: Cursor | TranslationUnit | Type) -> Cursor | None:
assert isinstance(res, Cursor)
# FIXME: There should just be an isNull method.
if res == conf.lib.clang_getNullCursor():
>From e5299ee09a6dffa4d198d2a12c8de7fe56c113f0 Mon Sep 17 00:00:00 2001
From: Jannick Kremer <jannick.kremer at mailbox.org>
Date: Tue, 13 May 2025 13:59:52 +0900
Subject: [PATCH 3/5] Add Cursor.is_null() method
Guard all Cursor methods against being used with null cursors
---
clang/bindings/python/clang/cindex.py | 29 ++++++++++++++-----
.../python/tests/cindex/test_cursor.py | 14 +++++++++
2 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 38a392483d803..1b6a3eec3697e 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -1555,6 +1555,18 @@ class Cursor(Structure):
_tu: TranslationUnit
+ # This ensures that no operations are possible on null cursors
+ # by guarding all method calls with a not-null assert
+ def __getattribute__(self, key: str) -> object:
+ value = super().__getattribute__(key)
+ is_property = isinstance(getattr(Cursor, key, None), property)
+ # Don't guard the is_null method, since it is part of the guard
+ # and leads to infinite recursion otherwise
+ if is_property or callable(value):
+ if key != "is_null" and self.is_null():
+ raise Exception("Tried calling method on a null-cursor.")
+ return value
+
@staticmethod
def from_location(tu: TranslationUnit, location: SourceLocation) -> Cursor:
# We store a reference to the TU in the instance so the TU won't get
@@ -1575,6 +1587,9 @@ def __ne__(self, other: object) -> bool:
def __hash__(self) -> int:
return self.hash
+ def is_null(self) -> bool:
+ return self == conf.null_cursor
+
def is_definition(self) -> bool:
"""
Returns true if the declaration pointed at by the cursor is also a
@@ -2124,8 +2139,7 @@ def get_children(self) -> Iterator[Cursor]:
# FIXME: Expose iteration from CIndex, PR6125.
def visitor(child: Cursor, _: Cursor, children: list[Cursor]) -> int:
# FIXME: Document this assertion in API.
- # FIXME: There should just be an isNull method.
- assert child != conf.lib.clang_getNullCursor()
+ assert not child.is_null()
# Create reference to TU so it isn't GC'd before Cursor.
child._tu = self._tu
@@ -2210,7 +2224,7 @@ def has_attrs(self) -> bool:
def from_result(res: Cursor, arg: Cursor | TranslationUnit | Type) -> Cursor | None:
assert isinstance(res, Cursor)
# FIXME: There should just be an isNull method.
- if res == conf.lib.clang_getNullCursor():
+ if res.is_null():
return None
# Store a reference to the TU in the Python object so it won't get GC'd
@@ -2229,7 +2243,7 @@ def from_result(res: Cursor, arg: Cursor | TranslationUnit | Type) -> Cursor | N
@staticmethod
def from_cursor_result(res: Cursor, arg: Cursor) -> Cursor | None:
assert isinstance(res, Cursor)
- if res == conf.lib.clang_getNullCursor():
+ if res.is_null():
return None
res._tu = arg._tu
@@ -2728,7 +2742,7 @@ def get_fields(self):
"""Return an iterator for accessing the fields of this type."""
def visitor(field, children):
- assert field != conf.lib.clang_getNullCursor()
+ assert not field.is_null()
# Create reference to TU so it isn't GC'd before Cursor.
field._tu = self._tu
@@ -2743,7 +2757,7 @@ def get_bases(self):
"""Return an iterator for accessing the base classes of this type."""
def visitor(base, children):
- assert base != conf.lib.clang_getNullCursor()
+ assert not base.is_null()
# Create reference to TU so it isn't GC'd before Cursor.
base._tu = self._tu
@@ -2758,7 +2772,7 @@ def get_methods(self):
"""Return an iterator for accessing the methods of this type."""
def visitor(method, children):
- assert method != conf.lib.clang_getNullCursor()
+ assert not method.is_null()
# Create reference to TU so it isn't GC'd before Cursor.
method._tu = self._tu
@@ -4228,6 +4242,7 @@ def set_compatibility_check(check_status: bool) -> None:
def lib(self) -> CDLL:
lib = self.get_cindex_library()
register_functions(lib, not Config.compatibility_check)
+ self.null_cursor = lib.clang_getNullCursor()
Config.loaded = True
return lib
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index b90a0495ca7be..eb0d1d50601a6 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -12,6 +12,7 @@
TemplateArgumentKind,
TranslationUnit,
TypeKind,
+ conf,
)
if "CLANG_LIBRARY_PATH" in os.environ:
@@ -1050,3 +1051,16 @@ def test_equality(self):
self.assertEqual(cursor1, cursor1_2)
self.assertNotEqual(cursor1, cursor2)
self.assertNotEqual(cursor1, "foo")
+
+ def test_null_cursor(self):
+ tu = get_tu("int a = 729;")
+
+ for cursor in tu.cursor.walk_preorder():
+ self.assertFalse(cursor.is_null())
+
+ nc = conf.lib.clang_getNullCursor()
+ self.assertTrue(nc.is_null())
+ with self.assertRaises(Exception):
+ nc.is_definition()
+ with self.assertRaises(Exception):
+ nc.spelling
>From 819efd955afecd36e3656b07fa944a28dc9f3929 Mon Sep 17 00:00:00 2001
From: Jannick Kremer <jannick.kremer at mailbox.org>
Date: Tue, 13 May 2025 17:24:54 +0900
Subject: [PATCH 4/5] Add from_non_null_cursor_result
Use this to narrow return types where possible
---
clang/bindings/python/clang/cindex.py | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 1b6a3eec3697e..ffda7b5f20773 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -1931,7 +1931,7 @@ def type(self) -> Type:
return self._type
@property
- def canonical(self) -> Cursor | None:
+ def canonical(self) -> Cursor:
"""Return the canonical Cursor corresponding to this Cursor.
The canonical cursor is the cursor which is representative for the
@@ -1940,7 +1940,7 @@ def canonical(self) -> Cursor | None:
declarations will be identical.
"""
if not hasattr(self, "_canonical"):
- self._canonical = Cursor.from_cursor_result(
+ self._canonical = Cursor.from_non_null_cursor_result(
conf.lib.clang_getCanonicalCursor(self), self
)
@@ -2223,7 +2223,6 @@ def has_attrs(self) -> bool:
@staticmethod
def from_result(res: Cursor, arg: Cursor | TranslationUnit | Type) -> Cursor | None:
assert isinstance(res, Cursor)
- # FIXME: There should just be an isNull method.
if res.is_null():
return None
@@ -2249,6 +2248,14 @@ def from_cursor_result(res: Cursor, arg: Cursor) -> Cursor | None:
res._tu = arg._tu
return res
+ @staticmethod
+ def from_non_null_cursor_result(res: Cursor, arg: Cursor | Type) -> Cursor:
+ assert isinstance(res, Cursor)
+ assert not res.is_null()
+
+ res._tu = arg._tu
+ return res
+
class BinaryOperator(BaseEnumeration):
"""
@@ -2682,7 +2689,9 @@ def get_declaration(self):
"""
Return the cursor for the declaration of the given type.
"""
- return Cursor.from_result(conf.lib.clang_getTypeDeclaration(self), self)
+ return Cursor.from_non_null_cursor_result(
+ conf.lib.clang_getTypeDeclaration(self), self
+ )
def get_result(self):
"""
>From 3967314e676618d27839dc3232717c7c4974c317 Mon Sep 17 00:00:00 2001
From: Jannick Kremer <jannick.kremer at mailbox.org>
Date: Thu, 15 May 2025 14:02:55 +0900
Subject: [PATCH 5/5] Use decorator to guard null cursor use instead
---
clang/bindings/python/clang/cindex.py | 89 +++++++++++++++++++++++----
1 file changed, 77 insertions(+), 12 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index ffda7b5f20773..ffc2d368a8756 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -1545,6 +1545,16 @@ class ExceptionSpecificationKind(BaseEnumeration):
### Cursors ###
+# This guard is used to ensure that no operations are possible on null cursors
+def cursor_null_guard(func):
+ def inner(self, *args, **kwargs):
+ if self.is_null():
+ raise Exception("Tried calling method on a null-cursor.")
+ return func(self, *args, **kwargs)
+
+ return inner
+
+
class Cursor(Structure):
"""
The Cursor class represents a reference to an element within the AST. It
@@ -1555,18 +1565,6 @@ class Cursor(Structure):
_tu: TranslationUnit
- # This ensures that no operations are possible on null cursors
- # by guarding all method calls with a not-null assert
- def __getattribute__(self, key: str) -> object:
- value = super().__getattribute__(key)
- is_property = isinstance(getattr(Cursor, key, None), property)
- # Don't guard the is_null method, since it is part of the guard
- # and leads to infinite recursion otherwise
- if is_property or callable(value):
- if key != "is_null" and self.is_null():
- raise Exception("Tried calling method on a null-cursor.")
- return value
-
@staticmethod
def from_location(tu: TranslationUnit, location: SourceLocation) -> Cursor:
# We store a reference to the TU in the instance so the TU won't get
@@ -1584,12 +1582,14 @@ def __eq__(self, other: object) -> bool:
def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
+ @cursor_null_guard
def __hash__(self) -> int:
return self.hash
def is_null(self) -> bool:
return self == conf.null_cursor
+ @cursor_null_guard
def is_definition(self) -> bool:
"""
Returns true if the declaration pointed at by the cursor is also a
@@ -1597,40 +1597,48 @@ def is_definition(self) -> bool:
"""
return conf.lib.clang_isCursorDefinition(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_const_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared 'const'.
"""
return conf.lib.clang_CXXMethod_isConst(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_converting_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ converting constructor."""
return conf.lib.clang_CXXConstructor_isConvertingConstructor(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_copy_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ copy constructor."""
return conf.lib.clang_CXXConstructor_isCopyConstructor(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_default_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ default constructor."""
return conf.lib.clang_CXXConstructor_isDefaultConstructor(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_move_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ move constructor."""
return conf.lib.clang_CXXConstructor_isMoveConstructor(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_default_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared '= default'.
"""
return conf.lib.clang_CXXMethod_isDefaulted(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_deleted_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared '= delete'.
"""
return conf.lib.clang_CXXMethod_isDeleted(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_copy_assignment_operator_method(self) -> bool:
"""Returnrs True if the cursor refers to a copy-assignment operator.
@@ -1656,6 +1664,7 @@ class Bar {
"""
return conf.lib.clang_CXXMethod_isCopyAssignmentOperator(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_move_assignment_operator_method(self) -> bool:
"""Returnrs True if the cursor refers to a move-assignment operator.
@@ -1681,6 +1690,7 @@ class Bar {
"""
return conf.lib.clang_CXXMethod_isMoveAssignmentOperator(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_explicit_method(self) -> bool:
"""Determines if a C++ constructor or conversion function is
explicit, returning 1 if such is the case and 0 otherwise.
@@ -1726,40 +1736,47 @@ class Foo {
"""
return conf.lib.clang_CXXMethod_isExplicit(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_mutable_field(self) -> bool:
"""Returns True if the cursor refers to a C++ field that is declared
'mutable'.
"""
return conf.lib.clang_CXXField_isMutable(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_pure_virtual_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared pure virtual.
"""
return conf.lib.clang_CXXMethod_isPureVirtual(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_static_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared 'static'.
"""
return conf.lib.clang_CXXMethod_isStatic(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_virtual_method(self) -> bool:
"""Returns True if the cursor refers to a C++ member function or member
function template that is declared 'virtual'.
"""
return conf.lib.clang_CXXMethod_isVirtual(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_abstract_record(self) -> bool:
"""Returns True if the cursor refers to a C++ record declaration
that has pure virtual member functions.
"""
return conf.lib.clang_CXXRecord_isAbstract(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_scoped_enum(self) -> bool:
"""Returns True if the cursor refers to a scoped enum declaration."""
return conf.lib.clang_EnumDecl_isScoped(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def get_definition(self) -> Cursor | None:
"""
If the cursor is a reference to a declaration or a declaration of
@@ -1770,6 +1787,7 @@ def get_definition(self) -> Cursor | None:
# declaration prior to issuing the lookup.
return Cursor.from_result(conf.lib.clang_getCursorDefinition(self), self)
+ @cursor_null_guard
def get_usr(self) -> str:
"""Return the Unified Symbol Resolution (USR) for the entity referenced
by the given cursor.
@@ -1781,6 +1799,7 @@ def get_usr(self) -> str:
another translation unit."""
return _CXString.from_result(conf.lib.clang_getCursorUSR(self))
+ @cursor_null_guard
def get_included_file(self) -> File:
"""Returns the File that is included by the current inclusion cursor."""
assert self.kind == CursorKind.INCLUSION_DIRECTIVE
@@ -1788,11 +1807,13 @@ def get_included_file(self) -> File:
return File.from_result(conf.lib.clang_getIncludedFile(self), self)
@property
+ @cursor_null_guard
def kind(self) -> CursorKind:
"""Return the kind of this cursor."""
return CursorKind.from_id(self._kind_id)
@property
+ @cursor_null_guard
def spelling(self) -> str:
"""Return the spelling of the entity pointed at by the cursor."""
if not hasattr(self, "_spelling"):
@@ -1802,6 +1823,7 @@ def spelling(self) -> str:
return self._spelling
+ @cursor_null_guard
def pretty_printed(self, policy: PrintingPolicy) -> str:
"""
Pretty print declarations.
@@ -1813,6 +1835,7 @@ def pretty_printed(self, policy: PrintingPolicy) -> str:
)
@property
+ @cursor_null_guard
def displayname(self) -> str:
"""
Return the display name for the entity referenced by this cursor.
@@ -1829,6 +1852,7 @@ def displayname(self) -> str:
return self._displayname
@property
+ @cursor_null_guard
def mangled_name(self) -> str:
"""Return the mangled name for the entity referenced by this cursor."""
if not hasattr(self, "_mangled_name"):
@@ -1839,6 +1863,7 @@ def mangled_name(self) -> str:
return self._mangled_name
@property
+ @cursor_null_guard
def location(self) -> SourceLocation:
"""
Return the source location (the starting character) of the entity
@@ -1850,6 +1875,7 @@ def location(self) -> SourceLocation:
return self._loc
@property
+ @cursor_null_guard
def linkage(self) -> LinkageKind:
"""Return the linkage of this cursor."""
if not hasattr(self, "_linkage"):
@@ -1858,6 +1884,7 @@ def linkage(self) -> LinkageKind:
return LinkageKind.from_id(self._linkage)
@property
+ @cursor_null_guard
def tls_kind(self) -> TLSKind:
"""Return the thread-local storage (TLS) kind of this cursor."""
if not hasattr(self, "_tls_kind"):
@@ -1866,6 +1893,7 @@ def tls_kind(self) -> TLSKind:
return TLSKind.from_id(self._tls_kind)
@property
+ @cursor_null_guard
def extent(self) -> SourceRange:
"""
Return the source range (the range of text) occupied by the entity
@@ -1877,6 +1905,7 @@ def extent(self) -> SourceRange:
return self._extent
@property
+ @cursor_null_guard
def storage_class(self) -> StorageClass:
"""
Retrieves the storage class (if any) of the entity pointed at by the
@@ -1888,6 +1917,7 @@ def storage_class(self) -> StorageClass:
return StorageClass.from_id(self._storage_class)
@property
+ @cursor_null_guard
def availability(self) -> AvailabilityKind:
"""
Retrieves the availability of the entity pointed at by the cursor.
@@ -1898,6 +1928,7 @@ def availability(self) -> AvailabilityKind:
return AvailabilityKind.from_id(self._availability)
@property
+ @cursor_null_guard
def binary_operator(self) -> BinaryOperator:
"""
Retrieves the opcode if this cursor points to a binary operator
@@ -1910,6 +1941,7 @@ def binary_operator(self) -> BinaryOperator:
return BinaryOperator.from_id(self._binopcode)
@property
+ @cursor_null_guard
def access_specifier(self) -> AccessSpecifier:
"""
Retrieves the access specifier (if any) of the entity pointed at by the
@@ -1921,6 +1953,7 @@ def access_specifier(self) -> AccessSpecifier:
return AccessSpecifier.from_id(self._access_specifier)
@property
+ @cursor_null_guard
def type(self) -> Type:
"""
Retrieve the Type (if any) of the entity pointed at by the cursor.
@@ -1931,6 +1964,7 @@ def type(self) -> Type:
return self._type
@property
+ @cursor_null_guard
def canonical(self) -> Cursor:
"""Return the canonical Cursor corresponding to this Cursor.
@@ -1947,6 +1981,7 @@ def canonical(self) -> Cursor:
return self._canonical
@property
+ @cursor_null_guard
def result_type(self) -> Type:
"""Retrieve the Type of the result for this Cursor."""
if not hasattr(self, "_result_type"):
@@ -1957,6 +1992,7 @@ def result_type(self) -> Type:
return self._result_type
@property
+ @cursor_null_guard
def exception_specification_kind(self) -> ExceptionSpecificationKind:
"""
Retrieve the exception specification kind, which is one of the values
@@ -1971,6 +2007,7 @@ def exception_specification_kind(self) -> ExceptionSpecificationKind:
return self._exception_specification_kind
@property
+ @cursor_null_guard
def underlying_typedef_type(self) -> Type:
"""Return the underlying type of a typedef declaration.
@@ -1986,6 +2023,7 @@ def underlying_typedef_type(self) -> Type:
return self._underlying_type
@property
+ @cursor_null_guard
def enum_type(self) -> Type:
"""Return the integer type of an enum declaration.
@@ -2001,6 +2039,7 @@ def enum_type(self) -> Type:
return self._enum_type
@property
+ @cursor_null_guard
def enum_value(self) -> int:
"""Return the value of an enum constant."""
if not hasattr(self, "_enum_value"):
@@ -2028,6 +2067,7 @@ def enum_value(self) -> int:
return self._enum_value
@property
+ @cursor_null_guard
def objc_type_encoding(self) -> str:
"""Return the Objective-C type encoding as a str."""
if not hasattr(self, "_objc_type_encoding"):
@@ -2038,6 +2078,7 @@ def objc_type_encoding(self) -> str:
return self._objc_type_encoding
@property
+ @cursor_null_guard
def hash(self) -> int:
"""Returns a hash of the cursor as an int."""
if not hasattr(self, "_hash"):
@@ -2046,6 +2087,7 @@ def hash(self) -> int:
return self._hash
@property
+ @cursor_null_guard
def semantic_parent(self) -> Cursor | None:
"""Return the semantic parent for this cursor."""
if not hasattr(self, "_semantic_parent"):
@@ -2056,6 +2098,7 @@ def semantic_parent(self) -> Cursor | None:
return self._semantic_parent
@property
+ @cursor_null_guard
def lexical_parent(self) -> Cursor | None:
"""Return the lexical parent for this cursor."""
if not hasattr(self, "_lexical_parent"):
@@ -2066,6 +2109,7 @@ def lexical_parent(self) -> Cursor | None:
return self._lexical_parent
@property
+ @cursor_null_guard
def specialized_template(self) -> Cursor | None:
"""Return the primary template that this cursor is a specialization of, if any."""
return Cursor.from_cursor_result(
@@ -2073,6 +2117,7 @@ def specialized_template(self) -> Cursor | None:
)
@property
+ @cursor_null_guard
def translation_unit(self) -> TranslationUnit:
"""Returns the TranslationUnit to which this Cursor belongs."""
# If this triggers an AttributeError, the instance was not properly
@@ -2080,6 +2125,7 @@ def translation_unit(self) -> TranslationUnit:
return self._tu
@property
+ @cursor_null_guard
def referenced(self) -> Cursor | None:
"""
For a cursor that is a reference, returns a cursor
@@ -2093,25 +2139,30 @@ def referenced(self) -> Cursor | None:
return self._referenced
@property
+ @cursor_null_guard
def brief_comment(self) -> str:
"""Returns the brief comment text associated with that Cursor"""
return _CXString.from_result(conf.lib.clang_Cursor_getBriefCommentText(self))
@property
+ @cursor_null_guard
def raw_comment(self) -> str:
"""Returns the raw comment text associated with that Cursor"""
return _CXString.from_result(conf.lib.clang_Cursor_getRawCommentText(self))
+ @cursor_null_guard
def get_arguments(self) -> Iterator[Cursor | None]:
"""Return an iterator for accessing the arguments of this cursor."""
num_args = conf.lib.clang_Cursor_getNumArguments(self)
for i in range(0, num_args):
yield Cursor.from_result(conf.lib.clang_Cursor_getArgument(self, i), self)
+ @cursor_null_guard
def get_num_template_arguments(self) -> int:
"""Returns the number of template args associated with this cursor."""
return conf.lib.clang_Cursor_getNumTemplateArguments(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def get_template_argument_kind(self, num: int) -> TemplateArgumentKind:
"""Returns the TemplateArgumentKind for the indicated template
argument."""
@@ -2119,20 +2170,24 @@ def get_template_argument_kind(self, num: int) -> TemplateArgumentKind:
conf.lib.clang_Cursor_getTemplateArgumentKind(self, num)
)
+ @cursor_null_guard
def get_template_argument_type(self, num: int) -> Type:
"""Returns the CXType for the indicated template argument."""
return Type.from_result(
conf.lib.clang_Cursor_getTemplateArgumentType(self, num), (self, num)
)
+ @cursor_null_guard
def get_template_argument_value(self, num: int) -> int:
"""Returns the value of the indicated arg as a signed 64b integer."""
return conf.lib.clang_Cursor_getTemplateArgumentValue(self, num) # type: ignore [no-any-return]
+ @cursor_null_guard
def get_template_argument_unsigned_value(self, num: int) -> int:
"""Returns the value of the indicated arg as an unsigned 64b integer."""
return conf.lib.clang_Cursor_getTemplateArgumentUnsignedValue(self, num) # type: ignore [no-any-return]
+ @cursor_null_guard
def get_children(self) -> Iterator[Cursor]:
"""Return an iterator for accessing the children of this cursor."""
@@ -2150,6 +2205,7 @@ def visitor(child: Cursor, _: Cursor, children: list[Cursor]) -> int:
conf.lib.clang_visitChildren(self, cursor_visit_callback(visitor), children)
return iter(children)
+ @cursor_null_guard
def walk_preorder(self) -> Iterator[Cursor]:
"""Depth-first preorder walk over the cursor and its descendants.
@@ -2160,6 +2216,7 @@ def walk_preorder(self) -> Iterator[Cursor]:
for descendant in child.walk_preorder():
yield descendant
+ @cursor_null_guard
def get_tokens(self) -> Iterator[Token]:
"""Obtain Token instances formulating that compose this Cursor.
@@ -2168,18 +2225,22 @@ def get_tokens(self) -> Iterator[Token]:
"""
return TokenGroup.get_tokens(self._tu, self.extent)
+ @cursor_null_guard
def get_field_offsetof(self) -> int:
"""Returns the offsetof the FIELD_DECL pointed by this Cursor."""
return conf.lib.clang_Cursor_getOffsetOfField(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def get_base_offsetof(self, parent: Cursor) -> int:
"""Returns the offsetof the CXX_BASE_SPECIFIER pointed by this Cursor."""
return conf.lib.clang_getOffsetOfBase(parent, self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_virtual_base(self) -> bool:
"""Returns whether the CXX_BASE_SPECIFIER pointed by this Cursor is virtual."""
return conf.lib.clang_isVirtualBase(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_anonymous(self) -> bool:
"""
Check whether this is a record type without a name, or a field where
@@ -2192,6 +2253,7 @@ def is_anonymous(self) -> bool:
return self.type.get_declaration().is_anonymous()
return conf.lib.clang_Cursor_isAnonymous(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_anonymous_record_decl(self) -> bool:
"""
Check if the record is an anonymous union as defined in the C/C++ standard
@@ -2202,18 +2264,21 @@ def is_anonymous_record_decl(self) -> bool:
return self.type.get_declaration().is_anonymous_record_decl()
return conf.lib.clang_Cursor_isAnonymousRecordDecl(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def is_bitfield(self) -> bool:
"""
Check if the field is a bitfield.
"""
return conf.lib.clang_Cursor_isBitField(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def get_bitfield_width(self) -> int:
"""
Retrieve the width of a bitfield.
"""
return conf.lib.clang_getFieldDeclBitWidth(self) # type: ignore [no-any-return]
+ @cursor_null_guard
def has_attrs(self) -> bool:
"""
Determine whether the given cursor has any attributes.
More information about the cfe-commits
mailing list