Author: Jannick Kremer Date: 2025-05-16T10:11:03+02:00 New Revision: c2045f24eab06960e0418d7d82856407b19156ad
URL: https://github.com/llvm/llvm-project/commit/c2045f24eab06960e0418d7d82856407b19156ad DIFF: https://github.com/llvm/llvm-project/commit/c2045f24eab06960e0418d7d82856407b19156ad.diff LOG: [libclang/python] Add typing annotations for the Cursor class (#138103) This fully annotates the Cursor class, resolving 95 strict typing errors as the next step towards #76664 These changes are a superset of the typing annotation changes from #120590 Added: Modified: clang/bindings/python/clang/cindex.py clang/bindings/python/tests/cindex/test_cursor.py clang/docs/ReleaseNotes.rst Removed: ################################################################################ diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 7a10f5618aad0..a49441e815004 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -90,6 +90,7 @@ Callable, cast as Tcast, Generic, + Iterator, Optional, Sequence, Type as TType, @@ -1561,6 +1562,24 @@ class ExceptionSpecificationKind(BaseEnumeration): ### Cursors ### +def cursor_null_guard(func): + """ + This decorator is used to ensure that no methods are called on null-cursors. + The bindings map null cursors to `None`, so users are not expected + to encounter them. + + If necessary, you can check whether a cursor is the null-cursor by + calling its `is_null` method. + """ + + 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 @@ -1569,68 +1588,81 @@ class Cursor(Structure): _fields_ = [("_kind_id", c_int), ("xdata", c_int), ("data", c_void_p * 3)] - @staticmethod - def from_location(tu, location): - # 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._tu = tu + _tu: TranslationUnit - return cursor + @staticmethod + def from_location(tu: TranslationUnit, location: SourceLocation) -> Cursor | None: + return Cursor.from_result(conf.lib.clang_getCursor(tu, location), tu) - def __eq__(self, other): + # This function is not null-guarded because it is used in cursor_null_guard itself + 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): + # Not null-guarded for consistency with __eq__ + def __ne__(self, other: object) -> bool: return not self.__eq__(other) + @cursor_null_guard def __hash__(self) -> int: return self.hash - def is_definition(self): + # This function is not null-guarded because it is used in cursor_null_guard itself + 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 definition of that entity. """ return conf.lib.clang_isCursorDefinition(self) # type: ignore [no-any-return] - def is_const_method(self): + @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] - def is_converting_constructor(self): + @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] - def is_copy_constructor(self): + @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] - def is_default_constructor(self): + @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] - def is_move_constructor(self): + @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] - def is_default_method(self): + @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] - def is_deleted_method(self): + @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] - def is_copy_assignment_operator_method(self): + @cursor_null_guard + 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, @@ -1655,7 +1687,8 @@ class Bar { """ return conf.lib.clang_CXXMethod_isCopyAssignmentOperator(self) # type: ignore [no-any-return] - def is_move_assignment_operator_method(self): + @cursor_null_guard + 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, @@ -1680,7 +1713,8 @@ class Bar { """ return conf.lib.clang_CXXMethod_isMoveAssignmentOperator(self) # type: ignore [no-any-return] - def is_explicit_method(self): + @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. @@ -1725,41 +1759,48 @@ class Foo { """ return conf.lib.clang_CXXMethod_isExplicit(self) # type: ignore [no-any-return] - def is_mutable_field(self): + @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] - def is_pure_virtual_method(self): + @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] - def is_static_method(self): + @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] - def is_virtual_method(self): + @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] - def is_abstract_record(self): + @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] - def is_scoped_enum(self): + @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] - def get_definition(self): + @cursor_null_guard + 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 @@ -1769,7 +1810,8 @@ def get_definition(self): # declaration prior to issuing the lookup. return Cursor.from_result(conf.lib.clang_getCursorDefinition(self), self) - def get_usr(self): + @cursor_null_guard + def get_usr(self) -> str: """Return the Unified Symbol Resolution (USR) for the entity referenced by the given cursor. @@ -1780,19 +1822,22 @@ def get_usr(self): another translation unit.""" return _CXString.from_result(conf.lib.clang_getCursorUSR(self)) - def get_included_file(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 return File.from_result(conf.lib.clang_getIncludedFile(self), self) @property - def kind(self): + @cursor_null_guard + def kind(self) -> CursorKind: """Return the kind of this cursor.""" return CursorKind.from_id(self._kind_id) @property - def spelling(self): + @cursor_null_guard + 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( @@ -1801,7 +1846,8 @@ def spelling(self): return self._spelling - def pretty_printed(self, policy): + @cursor_null_guard + def pretty_printed(self, policy: PrintingPolicy) -> str: """ Pretty print declarations. Parameters: @@ -1812,7 +1858,8 @@ def pretty_printed(self, policy): ) @property - def displayname(self): + @cursor_null_guard + def displayname(self) -> str: """ Return the display name for the entity referenced by this cursor. @@ -1828,7 +1875,8 @@ def displayname(self): return self._displayname @property - def mangled_name(self): + @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"): self._mangled_name = _CXString.from_result( @@ -1838,18 +1886,20 @@ def mangled_name(self): return self._mangled_name @property - def location(self): + @cursor_null_guard + 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): + @cursor_null_guard + def linkage(self) -> LinkageKind: """Return the linkage of this cursor.""" if not hasattr(self, "_linkage"): self._linkage = conf.lib.clang_getCursorLinkage(self) @@ -1857,7 +1907,8 @@ def linkage(self): return LinkageKind.from_id(self._linkage) @property - def tls_kind(self): + @cursor_null_guard + 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) @@ -1865,18 +1916,20 @@ def tls_kind(self): return TLSKind.from_id(self._tls_kind) @property - def extent(self): + @cursor_null_guard + 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): + @cursor_null_guard + def storage_class(self) -> StorageClass: """ Retrieves the storage class (if any) of the entity pointed at by the cursor. @@ -1887,7 +1940,8 @@ def storage_class(self): return StorageClass.from_id(self._storage_class) @property - def availability(self): + @cursor_null_guard + def availability(self) -> AvailabilityKind: """ Retrieves the availability of the entity pointed at by the cursor. """ @@ -1897,7 +1951,8 @@ def availability(self): return AvailabilityKind.from_id(self._availability) @property - def binary_operator(self): + @cursor_null_guard + def binary_operator(self) -> BinaryOperator: """ Retrieves the opcode if this cursor points to a binary operator :return: @@ -1909,7 +1964,8 @@ def binary_operator(self): return BinaryOperator.from_id(self._binopcode) @property - def access_specifier(self): + @cursor_null_guard + def access_specifier(self) -> AccessSpecifier: """ Retrieves the access specifier (if any) of the entity pointed at by the cursor. @@ -1920,7 +1976,8 @@ def access_specifier(self): return AccessSpecifier.from_id(self._access_specifier) @property - def type(self): + @cursor_null_guard + def type(self) -> Type: """ Retrieve the Type (if any) of the entity pointed at by the cursor. """ @@ -1930,7 +1987,8 @@ def type(self): return self._type @property - def canonical(self): + @cursor_null_guard + def canonical(self) -> Cursor: """Return the canonical Cursor corresponding to this Cursor. The canonical cursor is the cursor which is representative for the @@ -1939,14 +1997,15 @@ def canonical(self): 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 ) return self._canonical @property - def result_type(self): + @cursor_null_guard + 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( @@ -1956,7 +2015,8 @@ def result_type(self): return self._result_type @property - def exception_specification_kind(self): + @cursor_null_guard + def exception_specification_kind(self) -> ExceptionSpecificationKind: """ Retrieve the exception specification kind, which is one of the values from the ExceptionSpecificationKind enumeration. @@ -1970,7 +2030,8 @@ def exception_specification_kind(self): return self._exception_specification_kind @property - def underlying_typedef_type(self): + @cursor_null_guard + 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 @@ -1985,7 +2046,8 @@ def underlying_typedef_type(self): return self._underlying_type @property - def enum_type(self): + @cursor_null_guard + 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 @@ -2000,9 +2062,11 @@ def enum_type(self): return self._enum_type @property - def enum_value(self): + @cursor_null_guard + 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. @@ -2026,7 +2090,8 @@ def enum_value(self): return self._enum_value @property - def objc_type_encoding(self): + @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"): self._objc_type_encoding = _CXString.from_result( @@ -2036,15 +2101,17 @@ def objc_type_encoding(self): return self._objc_type_encoding @property - def hash(self): + @cursor_null_guard + 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): + @cursor_null_guard + 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( @@ -2054,7 +2121,8 @@ def semantic_parent(self): return self._semantic_parent @property - def lexical_parent(self): + @cursor_null_guard + 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( @@ -2064,6 +2132,7 @@ def lexical_parent(self): 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( @@ -2071,14 +2140,16 @@ def specialized_template(self) -> Cursor | None: ) @property - def translation_unit(self): + @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 # created. return self._tu @property - def referenced(self): + @cursor_null_guard + def referenced(self) -> Cursor | None: """ For a cursor that is a reference, returns a cursor representing the entity that it references. @@ -2091,54 +2162,62 @@ def referenced(self): return self._referenced @property - def brief_comment(self): + @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 - def raw_comment(self): + @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)) - def get_arguments(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) - def get_num_template_arguments(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] - def get_template_argument_kind(self, num): + @cursor_null_guard + 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): + @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) ) - def get_template_argument_value(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] - def get_template_argument_unsigned_value(self, num): + @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] - def get_children(self): + @cursor_null_guard + 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() + assert not child.is_null() # Create reference to TU so it isn't GC'd before Cursor. child._tu = self._tu @@ -2149,7 +2228,8 @@ def visitor(child, parent, children): conf.lib.clang_visitChildren(self, cursor_visit_callback(visitor), children) return iter(children) - def walk_preorder(self): + @cursor_null_guard + def walk_preorder(self) -> Iterator[Cursor]: """Depth-first preorder walk over the cursor and its descendants. Yields cursors. @@ -2159,7 +2239,8 @@ def walk_preorder(self): for descendant in child.walk_preorder(): yield descendant - def get_tokens(self): + @cursor_null_guard + 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 @@ -2167,19 +2248,23 @@ def get_tokens(self): """ return TokenGroup.get_tokens(self._tu, self.extent) - def get_field_offsetof(self): + @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] - def get_base_offsetof(self, parent): + @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] - def is_virtual_base(self): + @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] - def is_anonymous(self): + @cursor_null_guard + 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. @@ -2191,7 +2276,8 @@ 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): + @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 (or an "anonymous struct", the corresponding non-standard extension for @@ -2201,18 +2287,21 @@ 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): + @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] - def get_bitfield_width(self): + @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. @@ -2220,10 +2309,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(): + if res.is_null(): return None # Store a reference to the TU in the Python object so it won't get GC'd @@ -2240,14 +2328,22 @@ 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(): + if res.is_null(): return 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): """ @@ -2681,7 +2777,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): """ @@ -2741,7 +2839,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 @@ -2756,7 +2854,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 @@ -2771,7 +2869,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 @@ -4247,6 +4345,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 diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index cfe2a0277b226..8896a02eb0e18 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -79,6 +79,11 @@ Clang Frontend Potentially Breaking Changes Clang Python Bindings Potentially Breaking Changes -------------------------------------------------- +- ``Cursor.from_location`` now returns ``None`` instead of a null cursor. + This eliminates the last known source of null cursors. +- Almost all ``Cursor`` methods now assert that they are called on non-null cursors. + Most of the time null cursors were mapped to ``None``, + so no widespread breakages are expected. What's New in Clang |release|? ============================== _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits