[clang] [libclang/python] Add type annotations to the TranslationUnit class (PR #180876)

via cfe-commits cfe-commits at lists.llvm.org
Tue Feb 10 19:05:22 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Jannick Kremer (DeinAlptraum)

<details>
<summary>Changes</summary>

This adds type annotations to the `TranslationUnit` class, enough to pass a strict typecheck. This resolves 19 strict typing errors as the next step towards https://github.com/llvm/llvm-project/issues/76664

---
Full diff: https://github.com/llvm/llvm-project/pull/180876.diff


1 Files Affected:

- (modified) clang/bindings/python/clang/cindex.py (+65-29) 


``````````diff
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index f4d7f4fe68966..50b4301a21be5 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -103,9 +103,16 @@
 
 if TYPE_CHECKING:
     from ctypes import _Pointer
+    from io import TextIOWrapper
     from typing_extensions import Protocol, TypeAlias
 
     StrPath: TypeAlias = TUnion[str, os.PathLike[str]]
+    # The type that is compatible with os.fspath:
+    # str, bytes, or os.PathLikes that return either of these two
+    StrBytesPath: TypeAlias = TUnion[str, bytes, os.PathLike[str], os.PathLike[bytes]]
+    InMemoryFile: TypeAlias = (
+        "tuple[StrBytesPath, TUnion[str, bytes, TextIOWrapper]]"
+    )
     LibFunc: TypeAlias = TUnion[
         "tuple[str, Optional[list[Any]]]",
         "tuple[str, Optional[list[Any]], Any]",
@@ -3463,7 +3470,9 @@ class TranslationUnit(ClangObject):
     PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION = 128
 
     @staticmethod
-    def process_unsaved_files(unsaved_files) -> Array[_CXUnsavedFile] | None:
+    def process_unsaved_files(
+        unsaved_files: list[InMemoryFile],
+    ) -> Array[_CXUnsavedFile] | None:
         unsaved_array = None
         if len(unsaved_files):
             unsaved_array = (_CXUnsavedFile * len(unsaved_files))()
@@ -3478,8 +3487,13 @@ def process_unsaved_files(unsaved_files) -> Array[_CXUnsavedFile] | None:
 
     @classmethod
     def from_source(
-        cls, filename, args=None, unsaved_files=None, options=0, index=None
-    ):
+        cls,
+        filename: StrBytesPath | None,
+        args: list[TUnion[str, bytes]] | None = None,
+        unsaved_files: list[InMemoryFile] | None = None,
+        options: int = 0,
+        index: Index | None = None,
+    ) -> TranslationUnit:
         """Create a TranslationUnit by parsing source.
 
         This is capable of processing source code both from files on the
@@ -3490,11 +3504,12 @@ def from_source(
         etc. e.g. ["-Wall", "-I/path/to/include"].
 
         In-memory file content can be provided via unsaved_files. This is a
-        list of 2-tuples. The first element is the filename (str or
+        list of 2-tuples. The first element is the filename (str, bytes or
         PathLike). The second element defines the content. Content can be
-        provided as str source code or as file objects (anything with a read()
-        method). If a file object is being used, content will be read until EOF
-        and the read cursor will not be reset to its original position.
+        provided as str or bytes source code, or as file objects (anything with
+        a read() method). If a file object is being used, content will be read
+        until EOF and the read cursor will not be reset to its original
+        position.
 
         options is a bitwise or of TranslationUnit.PARSE_XXX flags which will
         control parsing behavior.
@@ -3550,7 +3565,9 @@ def from_source(
         return cls(ptr, index=index)
 
     @classmethod
-    def from_ast_file(cls, filename, index=None):
+    def from_ast_file(
+        cls, filename: StrPath, index: Index | None = None
+    ) -> TranslationUnit:
         """Create a TranslationUnit instance from a saved AST file.
 
         A previously-saved AST file (provided with -emit-ast or
@@ -3573,7 +3590,7 @@ def from_ast_file(cls, filename, index=None):
 
         return cls(ptr=ptr, index=index)
 
-    def __init__(self, ptr, index):
+    def __init__(self, ptr: CObjP, index: Index) -> None:
         """Create a TranslationUnit instance.
 
         TranslationUnits should be created using one of the from_* @classmethod
@@ -3583,20 +3600,20 @@ def __init__(self, ptr, index):
         self.index = index
         ClangObject.__init__(self, ptr)
 
-    def __del__(self):
+    def __del__(self) -> None:
         conf.lib.clang_disposeTranslationUnit(self)
 
     @property
-    def cursor(self):
+    def cursor(self) -> Cursor | None:
         """Retrieve the cursor that represents the given translation unit."""
         return Cursor.from_result(conf.lib.clang_getTranslationUnitCursor(self), self)
 
     @property
-    def spelling(self):
+    def spelling(self) -> str:
         """Get the original translation unit source file name."""
         return _CXString.from_result(conf.lib.clang_getTranslationUnitSpelling(self))
 
-    def get_includes(self):
+    def get_includes(self) -> Iterator[FileInclusion]:
         """
         Return an iterable sequence of FileInclusion objects that describe the
         sequence of inclusions in a translation unit. The first object in
@@ -3605,25 +3622,32 @@ def get_includes(self):
         headers.
         """
 
-        def visitor(fobj, lptr, depth, includes):
+        def visitor(
+            fobj: CObjP,
+            lptr: _Pointer[SourceLocation],
+            depth: int,
+            includes: list[FileInclusion],
+        ) -> None:
             if depth > 0:
                 loc = lptr.contents
                 includes.append(FileInclusion(loc.file, File(fobj), loc, depth))
 
         # Automatically adapt CIndex/ctype pointers to python objects
-        includes = []
+        includes: list[FileInclusion] = []
         conf.lib.clang_getInclusions(
             self, translation_unit_includes_callback(visitor), includes
         )
 
         return iter(includes)
 
-    def get_file(self, filename):
+    def get_file(self, filename: StrBytesPath) -> File:
         """Obtain a File from this translation unit."""
 
         return File.from_name(self, filename)
 
-    def get_location(self, filename, position):
+    def get_location(
+        self, filename: StrBytesPath, position: int | tuple[int, int]
+    ) -> SourceLocation:
         """Obtain a SourceLocation for a file in this translation unit.
 
         The position can be specified by passing:
@@ -3639,7 +3663,11 @@ def get_location(self, filename, position):
 
         return SourceLocation.from_position(self, f, position[0], position[1])
 
-    def get_extent(self, filename, locations):
+    def get_extent(
+        self,
+        filename: StrBytesPath,
+        locations: Sequence[SourceLocation] | Sequence[int] | Sequence[Sequence[int]],
+    ) -> SourceRange:
         """Obtain a SourceRange from this translation unit.
 
         The bounds of the SourceRange must ultimately be defined by a start and
@@ -3703,7 +3731,9 @@ def __getitem__(self, key: int) -> Diagnostic:
 
         return DiagIterator(self)
 
-    def reparse(self, unsaved_files=None, options=0):
+    def reparse(
+        self, unsaved_files: list[InMemoryFile] | None = None, options: int = 0
+    ) -> None:
         """
         Reparse an already parsed translation unit.
 
@@ -3725,7 +3755,7 @@ def reparse(self, unsaved_files=None, options=0):
             msg = "Error reparsing translation unit. Error code: " + str(result)
             raise TranslationUnitLoadError(msg)
 
-    def save(self, filename):
+    def save(self, filename: StrBytesPath) -> None:
         """Saves the TranslationUnit to a file.
 
         This is equivalent to passing -emit-ast to the clang frontend. The
@@ -3753,14 +3783,14 @@ def save(self, filename):
 
     def codeComplete(
         self,
-        path,
-        line,
-        column,
-        unsaved_files=None,
-        include_macros=False,
-        include_code_patterns=False,
-        include_brief_comments=False,
-    ):
+        path: StrBytesPath,
+        line: int,
+        column: int,
+        unsaved_files: list[InMemoryFile] | None = None,
+        include_macros: bool = False,
+        include_code_patterns: bool = False,
+        include_brief_comments: bool = False,
+    ) -> CodeCompletionResults | None:
         """
         Code complete in this translation unit.
 
@@ -3797,7 +3827,11 @@ def codeComplete(
             return CodeCompletionResults(ptr)
         return None
 
-    def get_tokens(self, locations=None, extent=None):
+    def get_tokens(
+        self,
+        locations: tuple[SourceLocation, SourceLocation] | None = None,
+        extent: SourceRange | None = None,
+    ) -> Iterator[Token]:
         """Obtain tokens in this translation unit.
 
         This is a generator for Token instances. The caller specifies a range
@@ -3809,6 +3843,8 @@ def get_tokens(self, locations=None, extent=None):
             raise TypeError("get_tokens() requires at least one argument")
         if locations is not None:
             extent = SourceRange(start=locations[0], end=locations[1])
+        if extent is None:
+            raise TypeError("get_tokens() requires at least one argument")
 
         return TokenGroup.get_tokens(self, extent)
 

``````````

</details>


https://github.com/llvm/llvm-project/pull/180876


More information about the cfe-commits mailing list