https://github.com/DeinAlptraum updated https://github.com/llvm/llvm-project/pull/180193
>From eb76c7a10cc0e8e1edf655449d94326ad626ea34 Mon Sep 17 00:00:00 2001 From: Jannick Kremer <[email protected]> Date: Fri, 6 Feb 2026 23:02:53 +0900 Subject: [PATCH 1/4] [libclang/python] Type-annotate SourceLocation and SourceRange --- clang/bindings/python/clang/cindex.py | 46 ++++++++++++++------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 2b6ab00c88219..f47398f5bcd0b 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -277,23 +277,25 @@ class SourceLocation(Structure): """ _fields_ = [("ptr_data", c_void_p * 2), ("int_data", c_uint)] - _data = None + _data: tuple[File | None, int, int, int] | None = None - def _get_instantiation(self): + def _get_instantiation(self) -> tuple[File | None, int, int, int]: if self._data is None: f, l, c, o = c_object_p(), c_uint(), c_uint(), c_uint() conf.lib.clang_getInstantiationLocation( self, byref(f), byref(l), byref(c), byref(o) ) if f: - f = File(f) + file = File(f) else: - f = None - self._data = (f, int(l.value), int(c.value), int(o.value)) + file = None + self._data = (file, int(l.value), int(c.value), int(o.value)) return self._data @staticmethod - def from_position(tu, file, line, column): + def from_position( + tu: TranslationUnit, file: File, line: int, column: int + ) -> SourceLocation: """ Retrieve the source location associated with a given file/line/column in a particular translation unit. @@ -301,7 +303,7 @@ def from_position(tu, file, line, column): return conf.lib.clang_getLocation(tu, file, line, column) # type: ignore [no-any-return] @staticmethod - def from_offset(tu, file, offset): + def from_offset(tu: TranslationUnit, file: File, offset: int) -> SourceLocation: """Retrieve a SourceLocation from a given character offset. tu -- TranslationUnit file belongs to @@ -311,36 +313,36 @@ def from_offset(tu, file, offset): return conf.lib.clang_getLocationForOffset(tu, file, offset) # type: ignore [no-any-return] @property - def file(self): + def file(self) -> File | None: """Get the file represented by this source location.""" return self._get_instantiation()[0] @property - def line(self): + def line(self) -> int: """Get the line represented by this source location.""" return self._get_instantiation()[1] @property - def column(self): + def column(self) -> int: """Get the column represented by this source location.""" return self._get_instantiation()[2] @property - def offset(self): + def offset(self) -> int: """Get the file offset represented by this source location.""" return self._get_instantiation()[3] @property - def is_in_system_header(self): + def is_in_system_header(self) -> bool: """Returns true if the given source location is in a system header.""" return bool(conf.lib.clang_Location_isInSystemHeader(self)) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, SourceLocation) and bool( conf.lib.clang_equalLocations(self, other) ) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not self.__eq__(other) def __lt__(self, other: SourceLocation) -> bool: @@ -349,7 +351,7 @@ def __lt__(self, other: SourceLocation) -> bool: def __le__(self, other: SourceLocation) -> bool: return self < other or self == other - def __repr__(self): + def __repr__(self) -> str: if self.file: filename = self.file.name else: @@ -376,11 +378,11 @@ class SourceRange(Structure): # FIXME: Eliminate this and make normal constructor? Requires hiding ctypes # object. @staticmethod - def from_locations(start, end): + def from_locations(start: SourceLocation, end: SourceLocation) -> SourceRange: return conf.lib.clang_getRange(start, end) # type: ignore [no-any-return] @property - def start(self): + def start(self) -> SourceLocation: """ Return a SourceLocation representing the first character within a source range. @@ -388,28 +390,28 @@ def start(self): return conf.lib.clang_getRangeStart(self) # type: ignore [no-any-return] @property - def end(self): + def end(self) -> SourceLocation: """ Return a SourceLocation representing the last character within a source range. """ return conf.lib.clang_getRangeEnd(self) # type: ignore [no-any-return] - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, SourceRange) and bool( conf.lib.clang_equalRanges(self, other) ) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not self.__eq__(other) - def __contains__(self, other): + def __contains__(self, other: object) -> bool: """Useful to detect the Token/Lexer bug""" if not isinstance(other, SourceLocation): return False return self.start <= other <= self.end - def __repr__(self): + def __repr__(self) -> str: return "<SourceRange start %r, end %r>" % (self.start, self.end) >From cf952b9468052a7fd8742674cfec15ce69416465 Mon Sep 17 00:00:00 2001 From: Jannick Kremer <[email protected]> Date: Fri, 6 Feb 2026 23:20:16 +0900 Subject: [PATCH 2/4] Implement __eq__ correctly --- clang/bindings/python/clang/cindex.py | 14 ++++++++------ .../bindings/python/tests/cindex/test_location.py | 2 +- .../python/tests/cindex/test_source_range.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 3b54b843fdace..181cff96ad806 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -339,9 +339,10 @@ def is_in_system_header(self) -> bool: return bool(conf.lib.clang_Location_isInSystemHeader(self)) def __eq__(self, other: object) -> bool: - return isinstance(other, SourceLocation) and bool( - conf.lib.clang_equalLocations(self, other) - ) + if isinstance(other, SourceLocation): + return bool(conf.lib.clang_equalLocations(self, other)) + else: + return NotImplemented def __ne__(self, other: object) -> bool: return not self.__eq__(other) @@ -399,9 +400,10 @@ def end(self) -> SourceLocation: return conf.lib.clang_getRangeEnd(self) # type: ignore [no-any-return] def __eq__(self, other: object) -> bool: - return isinstance(other, SourceRange) and bool( - conf.lib.clang_equalRanges(self, other) - ) + if isinstance(other, SourceRange): + return bool(conf.lib.clang_equalRanges(self, other)) + else: + return NotImplemented def __ne__(self, other: object) -> bool: return not self.__eq__(other) diff --git a/clang/bindings/python/tests/cindex/test_location.py b/clang/bindings/python/tests/cindex/test_location.py index 8d43d5012321a..35652782cf59d 100644 --- a/clang/bindings/python/tests/cindex/test_location.py +++ b/clang/bindings/python/tests/cindex/test_location.py @@ -168,4 +168,4 @@ def test_equality(self): self.assertEqual(location1, location1_2) self.assertNotEqual(location1, location2) self.assertNotEqual(location1, file2_location1) - self.assertNotEqual(location1, "foo") + self.assertFalse(location1 == "foo") diff --git a/clang/bindings/python/tests/cindex/test_source_range.py b/clang/bindings/python/tests/cindex/test_source_range.py index f1f2694b5820c..23589453d79d0 100644 --- a/clang/bindings/python/tests/cindex/test_source_range.py +++ b/clang/bindings/python/tests/cindex/test_source_range.py @@ -95,4 +95,4 @@ def test_equality(self): self.assertEqual(r1, r1) self.assertEqual(r1, r1_2) self.assertNotEqual(r1, r2) - self.assertNotEqual(r1, "foo") + self.assertFalse(r1 == "foo") >From 682201b416612426f2637ff789287edfa377b04e Mon Sep 17 00:00:00 2001 From: Jannick Kremer <[email protected]> Date: Sat, 7 Feb 2026 22:14:01 +0900 Subject: [PATCH 3/4] Check precondition first --- clang/bindings/python/clang/cindex.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 181cff96ad806..f4d7f4fe68966 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -339,10 +339,9 @@ def is_in_system_header(self) -> bool: return bool(conf.lib.clang_Location_isInSystemHeader(self)) def __eq__(self, other: object) -> bool: - if isinstance(other, SourceLocation): - return bool(conf.lib.clang_equalLocations(self, other)) - else: + if not isinstance(other, SourceLocation): return NotImplemented + return bool(conf.lib.clang_equalLocations(self, other)) def __ne__(self, other: object) -> bool: return not self.__eq__(other) @@ -400,10 +399,9 @@ def end(self) -> SourceLocation: return conf.lib.clang_getRangeEnd(self) # type: ignore [no-any-return] def __eq__(self, other: object) -> bool: - if isinstance(other, SourceRange): - return bool(conf.lib.clang_equalRanges(self, other)) - else: + if not isinstance(other, SourceRange): return NotImplemented + return bool(conf.lib.clang_equalRanges(self, other)) def __ne__(self, other: object) -> bool: return not self.__eq__(other) >From d5576fe973a2a13abed5386470e1514ee5a94e5a Mon Sep 17 00:00:00 2001 From: Jannick Kremer <[email protected]> Date: Sat, 7 Feb 2026 22:58:09 +0900 Subject: [PATCH 4/4] Add release note --- clang/docs/ReleaseNotes.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 24d4e07ca68b3..7c0ad9da1223a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -84,6 +84,10 @@ Clang Python Bindings Potentially Breaking Changes An alias is kept in the form of a ``SPELLING_CACHE`` variable, but it only supports ``__getitem__`` and ``__contains__``. It will be removed in a future release. Please migrate to using ``CompletionChunk.SPELLING_CACHE`` instead. +- ``SourceLocation`` and ``SourceRange`` now use ``NotImplemented`` to delegate + equality checks (``__eq__``) to the other object they are compared with when + they are of different classes. They previously returned ``False`` when compared + with objects of other classes. What's New in Clang |release|? ============================== _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
