[clang] [libclang/python] Type-annotate SourceLocation and SourceRange (PR #180193)
Jannick Kremer via cfe-commits
cfe-commits at lists.llvm.org
Sat Feb 7 05:14:28 PST 2026
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 <jannick.kremer at mailbox.org>
Date: Fri, 6 Feb 2026 23:02:53 +0900
Subject: [PATCH 1/3] [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 <jannick.kremer at mailbox.org>
Date: Fri, 6 Feb 2026 23:20:16 +0900
Subject: [PATCH 2/3] 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 <jannick.kremer at mailbox.org>
Date: Sat, 7 Feb 2026 22:14:01 +0900
Subject: [PATCH 3/3] 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)
More information about the cfe-commits
mailing list