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.kre...@mailbox.org> Date: Thu, 1 May 2025 18:37:43 +0900 Subject: [PATCH 1/7] [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.kre...@mailbox.org> Date: Thu, 1 May 2025 18:42:26 +0900 Subject: [PATCH 2/7] 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.kre...@mailbox.org> Date: Tue, 13 May 2025 13:59:52 +0900 Subject: [PATCH 3/7] 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.kre...@mailbox.org> Date: Tue, 13 May 2025 17:24:54 +0900 Subject: [PATCH 4/7] 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.kre...@mailbox.org> Date: Thu, 15 May 2025 14:02:55 +0900 Subject: [PATCH 5/7] 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. >From 86961bdc3ab401f7d510634c3b9666203639dd07 Mon Sep 17 00:00:00 2001 From: Jannick Kremer <jannick.kre...@mailbox.org> Date: Fri, 16 May 2025 14:43:02 +0900 Subject: [PATCH 6/7] Address review comments --- clang/bindings/python/clang/cindex.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index ffc2d368a8756..88c24aae11de4 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -1545,8 +1545,15 @@ class ExceptionSpecificationKind(BaseEnumeration): ### Cursors ### -# This guard is used to ensure that no operations are possible on null 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.") @@ -1566,19 +1573,16 @@ class Cursor(Structure): _tu: TranslationUnit @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 - # collected before the cursor. - cursor: Cursor = conf.lib.clang_getCursor(tu, location) - cursor._tu = tu - - return cursor + def from_location(tu: TranslationUnit, location: SourceLocation) -> Cursor | None: + return Cursor.from_result(conf.lib.clang_getCursor(tu, location), tu) + # 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] + # Not null-guarded for consistency with __eq__ def __ne__(self, other: object) -> bool: return not self.__eq__(other) @@ -1586,6 +1590,7 @@ def __ne__(self, other: object) -> bool: def __hash__(self) -> int: return self.hash + # 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 >From 6fb273e45983cffad5b1c27e9c294e391a17e87c Mon Sep 17 00:00:00 2001 From: Jannick Kremer <jannick.kre...@mailbox.org> Date: Fri, 16 May 2025 15:08:04 +0900 Subject: [PATCH 7/7] Add release note --- clang/docs/ReleaseNotes.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index fb0318146aa60..4668e5cb22df0 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -77,6 +77,8 @@ Clang Frontend Potentially Breaking Changes Clang Python Bindings Potentially Breaking Changes -------------------------------------------------- +- Calling methods on null-cursors now leads to an exception. +- ``Cursor.from_location`` now returns ``None`` instead of a null-cursor. 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