-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Fix all mypy --strict errors in clang python binding #101784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Mostly adding return types because mypy cannot infer them.
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write If you have received no comments on your PR for a week, you can request a review If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-clang Author: None (TsXor) ChangesRelated: #76664 I used metadata reflection so that we can import C library functions just by declaring annotated python functions. This makes C function types visible to type checker, then it's easy to fix most typing errors. Patch is 153.58 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/101784.diff 5 Files Affected:
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 2038ef6045c7d..521dc2829ae41 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -62,36 +62,50 @@
#
# o implement additional SourceLocation, SourceRange, and File methods.
-from ctypes import *
+from ctypes import (c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, c_long, # pyright: ignore[reportUnusedImport]
+ c_ulong, c_longlong,c_ulonglong, c_size_t, c_ssize_t, # pyright: ignore[reportUnusedImport]
+ c_bool, c_char, c_wchar, c_float, c_double, c_longdouble, # pyright: ignore[reportUnusedImport]
+ c_char_p, c_wchar_p, c_void_p) # pyright: ignore[reportUnusedImport]
+from ctypes import py_object, Structure, POINTER, byref, cast, cdll
+from .ctyped import *
+from .ctyped import ANNO_CONVERTIBLE, generate_metadata
import os
import sys
from enum import Enum
from typing import (
+ cast as tcast,
Any,
Callable,
+ Dict,
+ Generator,
Generic,
+ Iterator,
+ List,
Optional,
+ Tuple,
Type as TType,
TypeVar,
TYPE_CHECKING,
Union as TUnion,
)
+from typing_extensions import Annotated
+
if TYPE_CHECKING:
- from ctypes import _Pointer
- from typing_extensions import Protocol, TypeAlias
+ from typing_extensions import Protocol, Self, TypeAlias
+ from ctypes import CDLL
StrPath: TypeAlias = TUnion[str, os.PathLike[str]]
- LibFunc: TypeAlias = TUnion[
- "tuple[str, Optional[list[Any]]]",
- "tuple[str, Optional[list[Any]], Any]",
- "tuple[str, Optional[list[Any]], Any, Callable[..., Any]]",
- ]
-
+ StrOrBytes: TypeAlias = TUnion[str, bytes]
+ FsPath: TypeAlias = TUnion[StrOrBytes, os.PathLike[str]]
TSeq = TypeVar("TSeq", covariant=True)
+ class SupportsReadStringData(Protocol):
+ def read(self) -> str | bytes:
+ ...
+
class NoSliceSequence(Protocol[TSeq]):
def __len__(self) -> int:
...
@@ -102,7 +116,7 @@ def __getitem__(self, key: int) -> TSeq:
# Python 3 strings are unicode, translate them to/from utf8 for C-interop.
class c_interop_string(c_char_p):
- def __init__(self, p: str | bytes | None = None):
+ def __init__(self, p: 'CInteropString' = None):
if p is None:
p = ""
if isinstance(p, str):
@@ -120,7 +134,7 @@ def value(self) -> str | None: # type: ignore [override]
return val.decode("utf8")
@classmethod
- def from_param(cls, param: str | bytes | None) -> c_interop_string:
+ def from_param(cls, param: 'CInteropString') -> c_interop_string:
if isinstance(param, str):
return cls(param)
if isinstance(param, bytes):
@@ -136,6 +150,8 @@ def from_param(cls, param: str | bytes | None) -> c_interop_string:
def to_python_string(x: c_interop_string, *args: Any) -> str | None:
return x.value
+CInteropString = Annotated[TUnion[str, bytes, None], ANNO_CONVERTIBLE, c_interop_string]
+
def b(x: str | bytes) -> bytes:
if isinstance(x, bytes):
@@ -147,7 +163,8 @@ def b(x: str | bytes) -> bytes:
# object. This is a problem, because it means that from_parameter will see an
# integer and pass the wrong value on platforms where int != void*. Work around
# this by marshalling object arguments as void**.
-c_object_p: TType[_Pointer[Any]] = POINTER(c_void_p)
+CObjectP = CPointer[c_void_p]
+c_object_p: TType[CObjectP] = convert_annotation(CObjectP)
### Exception Classes ###
@@ -183,7 +200,7 @@ class TranslationUnitSaveError(Exception):
# Indicates that the translation unit was somehow invalid.
ERROR_INVALID_TU = 3
- def __init__(self, enumeration, message):
+ def __init__(self, enumeration: int, message: str):
assert isinstance(enumeration, int)
if enumeration < 1 or enumeration > 3:
@@ -241,7 +258,7 @@ def __del__(self) -> None:
conf.lib.clang_disposeString(self)
@staticmethod
- def from_result(res: _CXString, fn: Any = None, args: Any = None) -> str:
+ def from_result(res: _CXString, fn: Optional[Callable[..., _CXString]] = None, args: Optional[Tuple[Any, ...]] = None) -> str:
assert isinstance(res, _CXString)
pystr: str | None = conf.lib.clang_getCString(res)
if pystr is None:
@@ -255,71 +272,73 @@ class SourceLocation(Structure):
"""
_fields_ = [("ptr_data", c_void_p * 2), ("int_data", c_uint)]
- _data = None
+ _data: Optional[Tuple[Optional[File], int, int, int]] = None
- def _get_instantiation(self):
+ def _get_instantiation(self) -> Tuple[Optional[File], int, int, int]:
if self._data is None:
- f, l, c, o = c_object_p(), c_uint(), c_uint(), c_uint()
+ fp, 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)
+ self, byref(fp), byref(l), byref(c), byref(o)
)
- if f:
- f = File(f)
+ if fp:
+ f = File(fp)
else:
f = None
self._data = (f, 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.
"""
- return conf.lib.clang_getLocation(tu, file, line, column) # type: ignore [no-any-return]
+ return conf.lib.clang_getLocation(tu, file, line, column)
@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
file -- File instance to obtain offset from
offset -- Integer character offset within file
"""
- return conf.lib.clang_getLocationForOffset(tu, file, offset) # type: ignore [no-any-return]
+ return conf.lib.clang_getLocationForOffset(tu, file, offset)
@property
- def file(self):
+ def file(self) -> Optional[File]:
"""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 conf.lib.clang_Location_isInSystemHeader(self) # type: ignore [no-any-return]
+ return conf.lib.clang_Location_isInSystemHeader(self)
- def __eq__(self, other):
- return conf.lib.clang_equalLocations(self, other) # type: ignore [no-any-return]
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, SourceLocation):
+ return NotImplemented
+ return conf.lib.clang_equalLocations(self, other)
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
- def __repr__(self):
+ def __repr__(self) -> str:
if self.file:
filename = self.file.name
else:
@@ -346,40 +365,43 @@ class SourceRange(Structure):
# FIXME: Eliminate this and make normal constructor? Requires hiding ctypes
# object.
@staticmethod
- def from_locations(start, end):
- return conf.lib.clang_getRange(start, end) # type: ignore [no-any-return]
+ def from_locations(start: SourceLocation, end: SourceLocation) -> SourceRange:
+ return conf.lib.clang_getRange(start, end)
@property
- def start(self):
+ def start(self) -> SourceLocation:
"""
Return a SourceLocation representing the first character within a
source range.
"""
- return conf.lib.clang_getRangeStart(self) # type: ignore [no-any-return]
+ return conf.lib.clang_getRangeStart(self)
@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]
+ return conf.lib.clang_getRangeEnd(self)
- def __eq__(self, other):
- return conf.lib.clang_equalRanges(self, other) # type: ignore [no-any-return]
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, SourceRange):
+ return NotImplemented
+ return 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
- if other.file is None and self.start.file is None:
- pass
- elif (
- self.start.file.name != other.file.name
- or other.file.name != self.end.file.name
+ if (
+ other.file is not None
+ and self.start.file is not None
+ and self.end.file is not None
+ and (other.file.name != self.start.file.name
+ or other.file.name != self.end.file.name)
):
# same file name
return False
@@ -396,7 +418,7 @@ def __contains__(self, other):
return True
return False
- def __repr__(self):
+ def __repr__(self) -> str:
return "<SourceRange start %r, end %r>" % (self.start, self.end)
@@ -421,23 +443,25 @@ class Diagnostic:
DisplayCategoryName = 0x20
_FormatOptionsMask = 0x3F
- def __init__(self, ptr):
+ ptr: CObjectP
+
+ def __init__(self, ptr: CObjectP):
self.ptr = ptr
- def __del__(self):
+ def __del__(self) -> None:
conf.lib.clang_disposeDiagnostic(self)
@property
- def severity(self):
- return conf.lib.clang_getDiagnosticSeverity(self) # type: ignore [no-any-return]
+ def severity(self) -> int:
+ return conf.lib.clang_getDiagnosticSeverity(self)
@property
- def location(self):
- return conf.lib.clang_getDiagnosticLocation(self) # type: ignore [no-any-return]
+ def location(self) -> SourceLocation:
+ return conf.lib.clang_getDiagnosticLocation(self)
@property
- def spelling(self):
- return conf.lib.clang_getDiagnosticSpelling(self) # type: ignore [no-any-return]
+ def spelling(self) -> str:
+ return conf.lib.clang_getDiagnosticSpelling(self)
@property
def ranges(self) -> NoSliceSequence[SourceRange]:
@@ -451,7 +475,7 @@ def __len__(self) -> int:
def __getitem__(self, key: int) -> SourceRange:
if key >= len(self):
raise IndexError
- return conf.lib.clang_getDiagnosticRange(self.diag, key) # type: ignore [no-any-return]
+ return conf.lib.clang_getDiagnosticRange(self.diag, key)
return RangeIterator(self)
@@ -492,28 +516,28 @@ def __getitem__(self, key: int) -> Diagnostic:
return ChildDiagnosticsIterator(self)
@property
- def category_number(self):
+ def category_number(self) -> int:
"""The category number for this diagnostic or 0 if unavailable."""
- return conf.lib.clang_getDiagnosticCategory(self) # type: ignore [no-any-return]
+ return conf.lib.clang_getDiagnosticCategory(self)
@property
- def category_name(self):
+ def category_name(self) -> str:
"""The string name of the category for this diagnostic."""
- return conf.lib.clang_getDiagnosticCategoryText(self) # type: ignore [no-any-return]
+ return conf.lib.clang_getDiagnosticCategoryText(self)
@property
- def option(self):
+ def option(self) -> str:
"""The command-line option that enables this diagnostic."""
- return conf.lib.clang_getDiagnosticOption(self, None) # type: ignore [no-any-return]
+ return conf.lib.clang_getDiagnosticOption(self, None)
@property
- def disable_option(self):
+ def disable_option(self) -> str:
"""The command-line option that disables this diagnostic."""
disable = _CXString()
conf.lib.clang_getDiagnosticOption(self, byref(disable))
return _CXString.from_result(disable)
- def format(self, options=None):
+ def format(self, options: Optional[int] = None) -> str:
"""
Format this diagnostic for display. The options argument takes
Diagnostic.Display* flags, which can be combined using bitwise OR. If
@@ -524,19 +548,19 @@ def format(self, options=None):
options = conf.lib.clang_defaultDiagnosticDisplayOptions()
if options & ~Diagnostic._FormatOptionsMask:
raise ValueError("Invalid format options")
- return conf.lib.clang_formatDiagnostic(self, options) # type: ignore [no-any-return]
+ return conf.lib.clang_formatDiagnostic(self, options)
- def __repr__(self):
+ def __repr__(self) -> str:
return "<Diagnostic severity %r, location %r, spelling %r>" % (
self.severity,
self.location,
self.spelling,
)
- def __str__(self):
+ def __str__(self) -> str:
return self.format()
- def from_param(self):
+ def from_param(self) -> CObjectP:
return self.ptr
@@ -547,11 +571,14 @@ class FixIt:
with the given value.
"""
- def __init__(self, range, value):
+ range: SourceRange
+ value: str
+
+ def __init__(self, range: SourceRange, value: str):
self.range = range
self.value = value
- def __repr__(self):
+ def __repr__(self) -> str:
return "<FixIt range %r, value %r>" % (self.range, self.value)
@@ -570,16 +597,20 @@ class TokenGroup:
You should not instantiate this class outside of this module.
"""
- def __init__(self, tu, memory, count):
+ _tu: TranslationUnit
+ _memory: CPointer[Token]
+ _count: c_uint
+
+ def __init__(self, tu: TranslationUnit, memory: CPointer[Token], count: c_uint):
self._tu = tu
self._memory = memory
self._count = count
- def __del__(self):
+ def __del__(self) -> None:
conf.lib.clang_disposeTokens(self._tu, self._memory, self._count)
@staticmethod
- def get_tokens(tu, extent):
+ def get_tokens(tu: TranslationUnit, extent: SourceRange) -> Generator[Token, None, None]:
"""Helper method to return all tokens in an extent.
This functionality is needed multiple places in this module. We define
@@ -616,16 +647,16 @@ class BaseEnumeration(Enum):
"""
Common base class for named enumerations held in sync with Index.h values.
"""
+ value: int # pyright: ignore[reportIncompatibleMethodOverride]
-
- def from_param(self):
+ def from_param(self) -> int:
return self.value
@classmethod
- def from_id(cls, id):
+ def from_id(cls, id: int) -> Self:
return cls(id)
- def __repr__(self):
+ def __repr__(self) -> str:
return "%s.%s" % (
self.__class__.__name__,
self.name,
@@ -636,7 +667,7 @@ class TokenKind(BaseEnumeration):
"""Describes a specific type of a Token."""
@classmethod
- def from_value(cls, value):
+ def from_value(cls, value: int) -> Self:
"""Obtain a registered TokenKind instance from its value."""
return cls.from_id(value)
@@ -653,45 +684,44 @@ class CursorKind(BaseEnumeration):
"""
@staticmethod
- def get_all_kinds():
+ def get_all_kinds() -> List[CursorKind]:
"""Return all CursorKind enumeration instances."""
return list(CursorKind)
- def is_declaration(self):
+ def is_declaration(self) -> bool:
"""Test if this is a declaration kind."""
- return conf.lib.clang_isDeclaration(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isDeclaration(self)
- def is_reference(self):
+ def is_reference(self) -> bool:
"""Test if this is a reference kind."""
- return conf.lib.clang_isReference(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isReference(self)
- def is_expression(self):
+ def is_expression(self) -> bool:
"""Test if this is an expression kind."""
- return conf.lib.clang_isExpression(self) # type: ignore [no-any-return]
-
- def is_statement(self):
+ return conf.lib.clang_isExpression(self)
+ def is_statement(self) -> bool:
"""Test if this is a statement kind."""
- return conf.lib.clang_isStatement(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isStatement(self)
- def is_attribute(self):
+ def is_attribute(self) -> bool:
"""Test if this is an attribute kind."""
- return conf.lib.clang_isAttribute(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isAttribute(self)
- def is_invalid(self):
+ def is_invalid(self) -> bool:
"""Test if this is an invalid kind."""
- return conf.lib.clang_isInvalid(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isInvalid(self)
- def is_translation_unit(self):
+ def is_translation_unit(self) -> bool:
"""Test if this is a translation unit kind."""
- return conf.lib.clang_isTranslationUnit(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isTranslationUnit(self)
- def is_preprocessing(self):
+ def is_preprocessing(self) -> bool:
"""Test if this is a preprocessing kind."""
- return conf.lib.clang_isPreprocessing(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isPreprocessing(self)
- def is_unexposed(self):
+ def is_unexposed(self) -> bool:
"""Test if this is an unexposed kind."""
- return conf.lib.clang_isUnexposed(self) # type: ignore [no-any-return]
+ return conf.lib.clang_isUnexposed(self)
###
@@ -1555,7 +1585,7 @@ class Cursor(Structure):
_fields_ = [("_kind_id", c_int), ("xdata", c_int), ("data", c_void_p * 3)]
@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)
@@ -1563,54 +1593,56 @@ def from_location(tu, location):
return cursor
- def __eq__(self, other):
- return conf.lib.clang_equalCursors(self, other) # type: ignore [no-any-return]
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Cursor):
+ return NotImplemented
+ return conf.lib.clang_equalCursors(self, other)
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
- 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]
+ return conf.lib.clang_isCursorDefinition(self)
- def is_const_method(self):
+ def is_const_method(self) -> bool:
"""Returns True if the cursor ...
[truncated]
|
- fixed error handling of TranslationUnit.reparse - fixed silly mistake in Config.function_exists
I tried many methods to work it around, but all end up breaking other tests. Seems that the best way is to modify the test.
@TsXor thank you for your work! That said, this PR is pretty massive and grows the Python bindings by about 800 lines of code (ignoring tests), which imo is quite a lot just to pass the strict type check. There are also a lot of refactoring and other changes in this PR that, while generally welcome, seem unrelated and should be factored out. In general, multiple smaller PRs are preferred over something of this size, to have clearer boundaries and ease reviewing. For reference, I'd like to point out that I've also opened a PR for strict typing in #78114, which has been under review for a while. |
You can test this locally with the following command:darker --check --diff -r 98e4413a38f286147b863a6ead9625228ab0ec7d...c3ebad6a3447101cb307d5ca118d28d1b78b4dbe clang/bindings/python/clang/ctyped.py clang/bindings/python/tests/ctyped/__init__.py clang/bindings/python/tests/ctyped/test_stub_conversion.py clang/bindings/python/clang/cindex.py clang/bindings/python/tests/cindex/test_translation_unit.py clang/bindings/python/tests/cindex/test_type.py View the diff from darker here.--- clang/cindex.py 2024-08-03 14:40:40.000000 +0000
+++ clang/cindex.py 2024-08-03 23:15:21.593600 +0000
@@ -60,14 +60,33 @@
# o cleanup ctypes wrapping, would be nice to separate the ctypes details more
# clearly, and hide from the external interface (i.e., help(cindex)).
#
# o implement additional SourceLocation, SourceRange, and File methods.
-from ctypes import (c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, c_long, # pyright: ignore[reportUnusedImport]
- c_ulong, c_longlong,c_ulonglong, c_size_t, c_ssize_t, # pyright: ignore[reportUnusedImport]
- c_bool, c_char, c_wchar, c_float, c_double, c_longdouble, # pyright: ignore[reportUnusedImport]
- c_char_p, c_wchar_p, c_void_p) # pyright: ignore[reportUnusedImport]
+from ctypes import (
+ c_byte,
+ c_ubyte,
+ c_short,
+ c_ushort,
+ c_int,
+ c_uint,
+ c_long, # pyright: ignore[reportUnusedImport]
+ c_ulong,
+ c_longlong,
+ c_ulonglong,
+ c_size_t,
+ c_ssize_t, # pyright: ignore[reportUnusedImport]
+ c_bool,
+ c_char,
+ c_wchar,
+ c_float,
+ c_double,
+ c_longdouble, # pyright: ignore[reportUnusedImport]
+ c_char_p,
+ c_wchar_p,
+ c_void_p,
+) # pyright: ignore[reportUnusedImport]
from ctypes import py_object, Structure, POINTER, byref, cast, cdll
from .ctyped import *
from .ctyped import ANNO_CONVERTIBLE, generate_metadata
import os
@@ -114,11 +133,11 @@
...
# Python 3 strings are unicode, translate them to/from utf8 for C-interop.
class c_interop_string(c_char_p):
- def __init__(self, p: 'CInteropString' = None):
+ def __init__(self, p: "CInteropString" = None):
if p is None:
p = ""
if isinstance(p, str):
p = p.encode("utf8")
super(c_char_p, self).__init__(p)
@@ -132,11 +151,11 @@
if val is None:
return None
return val.decode("utf8")
@classmethod
- def from_param(cls, param: 'CInteropString') -> c_interop_string:
+ def from_param(cls, param: "CInteropString") -> c_interop_string:
if isinstance(param, str):
return cls(param)
if isinstance(param, bytes):
return cls(param)
if param is None:
@@ -168,11 +187,11 @@
### Exception Classes ###
class CXError(Exception):
- '''Represents C error of type enum CXErrorCode.'''
+ """Represents C error of type enum CXErrorCode."""
# A generic error code, no further details are available.
#
# Errors of this kind can get their own specific error codes in future
# libclang versions.
@@ -297,11 +316,15 @@
def __del__(self) -> None:
conf.lib.clang_disposeString(self)
@staticmethod
- def from_result(res: _CXString, fn: Optional[Callable[..., _CXString]] = None, args: Optional[Tuple[Any, ...]] = None) -> str:
+ def from_result(
+ res: _CXString,
+ fn: Optional[Callable[..., _CXString]] = None,
+ args: Optional[Tuple[Any, ...]] = None,
+ ) -> str:
assert isinstance(res, _CXString)
pystr: str | None = conf.lib.clang_getCString(res)
if pystr is None:
return ""
return pystr
@@ -329,11 +352,13 @@
f = None
self._data = (f, int(l.value), int(c.value), int(o.value))
return self._data
@staticmethod
- def from_position(tu: TranslationUnit, file: File, line: int, column: int) -> SourceLocation:
+ 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.
"""
return conf.lib.clang_getLocation(tu, file, line, column)
@@ -445,12 +470,14 @@
return False
if (
other.file is not None
and self.start.file is not None
and self.end.file is not None
- and (other.file.name != self.start.file.name
- or other.file.name != self.end.file.name)
+ and (
+ other.file.name != self.start.file.name
+ or other.file.name != self.end.file.name
+ )
):
# same file name
return False
# same file, in between lines
if self.start.line < other.line < self.end.line:
@@ -655,11 +682,13 @@
def __del__(self) -> None:
conf.lib.clang_disposeTokens(self._tu, self._memory, self._count)
@staticmethod
- def get_tokens(tu: TranslationUnit, extent: SourceRange) -> Generator[Token, None, None]:
+ def get_tokens(
+ tu: TranslationUnit, extent: SourceRange
+ ) -> Generator[Token, None, None]:
"""Helper method to return all tokens in an extent.
This functionality is needed multiple places in this module. We define
it here because it seems like a logical place.
"""
@@ -692,11 +721,12 @@
### Cursor Kinds ###
class BaseEnumeration(Enum):
"""
Common base class for named enumerations held in sync with Index.h values.
"""
- value: int # pyright: ignore[reportIncompatibleMethodOverride]
+
+ value: int # pyright: ignore[reportIncompatibleMethodOverride]
def from_param(self) -> int:
return self.value
@classmethod
@@ -744,10 +774,11 @@
return conf.lib.clang_isReference(self)
def is_expression(self) -> bool:
"""Test if this is an expression kind."""
return conf.lib.clang_isExpression(self)
+
def is_statement(self) -> bool:
"""Test if this is a statement kind."""
return conf.lib.clang_isStatement(self)
def is_attribute(self) -> bool:
@@ -2214,11 +2245,13 @@
Retrieve the width of a bitfield.
"""
return conf.lib.clang_getFieldDeclBitWidth(self)
@staticmethod
- def from_result(res: Cursor, fn: Callable[..., Cursor], args: Tuple[Any, ...]) -> Optional[Cursor]:
+ def from_result(
+ res: Cursor, fn: Callable[..., Cursor], args: Tuple[Any, ...]
+ ) -> Optional[Cursor]:
assert isinstance(res, Cursor)
# FIXME: There should just be an isNull method.
if res == conf.lib.clang_getNullCursor():
return None
@@ -2238,11 +2271,13 @@
res._tu = tu
return res
@staticmethod
- def from_cursor_result(res: Cursor, fn: Callable[..., Cursor], args: Tuple[Any, ...]) -> Optional[Cursor]:
+ def from_cursor_result(
+ res: Cursor, fn: Callable[..., Cursor], args: Tuple[Any, ...]
+ ) -> Optional[Cursor]:
assert isinstance(res, Cursor)
if res == conf.lib.clang_getNullCursor():
return None
res._tu = args[0]._tu
@@ -2527,11 +2562,13 @@
return self.length
def __getitem__(self, key: int) -> Type:
# FIXME Support slice objects.
- if not isinstance(key, int): # pyright: ignore[reportUnnecessaryIsInstance]
+ if not isinstance(
+ key, int
+ ): # pyright: ignore[reportUnnecessaryIsInstance]
raise TypeError("Must supply a non-negative int.")
if key < 0:
raise IndexError("Only non-negative indexes are accepted.")
@@ -2580,11 +2617,11 @@
@property
def translation_unit(self) -> TranslationUnit:
"""The TranslationUnit to which this Type is associated."""
# If this triggers an AttributeError, the instance was not properly
# instantiated.
- return self._tu # type: ignore[no-any-return]
+ return self._tu # type: ignore[no-any-return]
@staticmethod
def from_result(res: Type, fn: Callable[..., Type], args: Tuple[Any, ...]) -> Type:
assert isinstance(res, Type)
@@ -2728,11 +2765,11 @@
def visitor(field: Cursor, children: List[Cursor]) -> int:
assert field != conf.lib.clang_getNullCursor()
# Create reference to TU so it isn't GC'd before Cursor.
- field._tu = self._tu # pyright: ignore[reportPrivateUsage]
+ field._tu = self._tu # pyright: ignore[reportPrivateUsage]
fields.append(field)
return 1 # continue
fields: List[Cursor] = []
conf.lib.clang_Type_visitFields(self, fields_visit_callback(visitor), fields)
@@ -2751,11 +2788,11 @@
def spelling(self) -> str:
"""Retrieve the spelling of this Type."""
return conf.lib.clang_getTypeSpelling(self)
def __eq__(self, other: object) -> bool:
- if other is None: # in case user write `x.type == None`
+ if other is None: # in case user write `x.type == None`
return False
elif not isinstance(other, Type):
return NotImplemented
else:
return conf.lib.clang_equalTypes(self, other)
@@ -2774,21 +2811,25 @@
class ClangObject:
"""
A helper for Clang objects. This class helps act as an intermediary for
the ctypes library and the Clang CIndex library.
"""
+
obj: CObjectP
_as_parameter_: CObjectP
def __init__(self, obj: CObjectP):
assert isinstance(obj, c_object_p) and obj
self.obj = self._as_parameter_ = obj
def from_param(self) -> CObjectP:
return self._as_parameter_
-ClangObjectParam = Annotated[TUnion[CObjectP, ClangObject], ANNO_CONVERTIBLE, c_object_p]
+
+ClangObjectParam = Annotated[
+ TUnion[CObjectP, ClangObject], ANNO_CONVERTIBLE, c_object_p
+]
class _CXUnsavedFile(Structure):
"""Helper for passing unsaved file arguments."""
@@ -2796,11 +2837,14 @@
name: r_char_p
contents: r_char_p
length: r_ulong
-UnsavedFileInfo: TypeAlias = Tuple['FsPath', TUnion['StrOrBytes', 'SupportsReadStringData']]
+
+UnsavedFileInfo: TypeAlias = Tuple[
+ "FsPath", TUnion["StrOrBytes", "SupportsReadStringData"]
+]
# Functions calls through the python interface are rather slow. Fortunately,
# for most symboles, we do not need to perform a function call. Their spelling
# never changes and is consequently provided by this spelling cache.
SpellingCache = {
@@ -2955,15 +2999,15 @@
@property
def brief_comment(self) -> str:
if conf.function_exists("clang_getCompletionBriefComment"):
return conf.lib.clang_getCompletionBriefComment(self.obj)
- return ''
+ return ""
def __repr__(self) -> str:
return (
- " | ".join([str(a) for a in self]) # type: ignore[attr-defined]
+ " | ".join([str(a) for a in self]) # type: ignore[attr-defined]
+ " || Priority: "
+ str(self.priority)
+ " || Availability: "
+ str(self.availability)
+ " || Brief comment: "
@@ -3010,20 +3054,22 @@
if len(self) <= key:
raise IndexError
# FIXME: Current type stub of ctypes does not provide signature of
# __getitem__ in class _Pointer. Remove this ignore when they
# fixed that.
- return self.results[key] # type: ignore[no-any-return]
+ return self.results[key] # type: ignore[no-any-return]
class CodeCompletionResults(ClangObject):
- obj: CPointer[CCRStructure] # type: ignore[assignment]
- _as_parameter_: CPointer[CCRStructure] # type: ignore[assignment]
+ obj: CPointer[CCRStructure] # type: ignore[assignment]
+ _as_parameter_: CPointer[CCRStructure] # type: ignore[assignment]
def __init__(self, ptr: CPointer[CCRStructure]):
assert isinstance(ptr, POINTER(CCRStructure)) and ptr
- self.obj = self._as_parameter_ = ptr # pyright: ignore[reportIncompatibleVariableOverride]
+ self.obj = (
+ self._as_parameter_
+ ) = ptr # pyright: ignore[reportIncompatibleVariableOverride]
def __del__(self) -> None:
conf.lib.clang_disposeCodeCompleteResults(self)
@property
@@ -3069,11 +3115,12 @@
def read(self, path: FsPath) -> TranslationUnit:
"""Load a TranslationUnit from the given AST file."""
return TranslationUnit.from_ast_file(path, self)
def parse(
- self, path: Optional[FsPath],
+ self,
+ path: Optional[FsPath],
args: Optional[List[str]] = None,
unsaved_files: Optional[List[UnsavedFileInfo]] = None,
options: int = 0,
) -> TranslationUnit:
"""Load the translation unit from the given source code file by running
@@ -3131,30 +3178,33 @@
PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION = 128
index: Index
@staticmethod
- def process_unsaved_files(unsaved_files: List[UnsavedFileInfo]) -> Optional[CArray[_CXUnsavedFile]]:
+ def process_unsaved_files(
+ unsaved_files: List[UnsavedFileInfo],
+ ) -> Optional[CArray[_CXUnsavedFile]]:
unsaved_array = None
if len(unsaved_files):
unsaved_array = (_CXUnsavedFile * len(unsaved_files))()
for i, (name, contents) in enumerate(unsaved_files):
if hasattr(contents, "read"):
- contents = tcast('SupportsReadStringData', contents).read()
- binary_contents = b(tcast('StrOrBytes', contents))
+ contents = tcast("SupportsReadStringData", contents).read()
+ binary_contents = b(tcast("StrOrBytes", contents))
unsaved_array[i].name = b(os.fspath(name))
unsaved_array[i].contents = binary_contents
unsaved_array[i].length = len(binary_contents)
return unsaved_array
@classmethod
def from_source(
- cls, filename: Optional[FsPath],
+ cls,
+ filename: Optional[FsPath],
args: Optional[List[str]] = None,
unsaved_files: Optional[List[UnsavedFileInfo]] = None,
options: int = 0,
- index: Optional[Index] = None
+ index: Optional[Index] = None,
) -> Self:
"""Create a TranslationUnit by parsing source.
This is capable of processing source code both from files on the
filesystem as well as in-memory contents.
@@ -3277,11 +3327,16 @@
this sequence is always the input file. Note that this method will not
recursively iterate over header files included through precompiled
headers.
"""
- def visitor(fobj: CObjectP, lptr: CPointer[SourceLocation], depth: int, includes: List[FileInclusion]) -> None:
+ def visitor(
+ fobj: CObjectP,
+ lptr: CPointer[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
@@ -3295,11 +3350,13 @@
def get_file(self, filename: FsPath) -> File:
"""Obtain a File from this translation unit."""
return File.from_name(self, filename)
- def get_location(self, filename: FsPath, position: Tuple[int, int]) -> SourceLocation:
+ def get_location(
+ self, filename: FsPath, position: Tuple[int, int]
+ ) -> SourceLocation:
"""Obtain a SourceLocation for a file in this translation unit.
The position can be specified by passing:
- Integer file offset. Initial file offset is 0.
@@ -3312,11 +3369,14 @@
return SourceLocation.from_offset(self, f, position)
return SourceLocation.from_position(self, f, position[0], position[1])
_Location = TUnion[int, Tuple[int, int], SourceLocation]
- def get_extent(self, filename: FsPath, locations: Tuple[_Location, _Location]) -> SourceRange:
+
+ def get_extent(
+ self, filename: FsPath, locations: Tuple[_Location, _Location]
+ ) -> SourceRange:
"""Obtain a SourceRange from this translation unit.
The bounds of the SourceRange must ultimately be defined by a start and
end SourceLocation. For the locations argument, you can pass:
@@ -3378,11 +3438,13 @@
raise IndexError
return Diagnostic(diag)
return DiagIterator(self)
- def reparse(self, unsaved_files: Optional[List[UnsavedFileInfo]] = None, options: int = 0) -> None:
+ def reparse(
+ self, unsaved_files: Optional[List[UnsavedFileInfo]] = None, options: int = 0
+ ) -> None:
"""
Reparse an already parsed translation unit.
In-memory contents for files can be provided by passing a list of pairs
as unsaved_files, the first items should be the filenames to be mapped
@@ -3395,11 +3457,11 @@
unsaved_files_array = self.process_unsaved_files(unsaved_files)
result = conf.lib.clang_reparseTranslationUnit(
self, len(unsaved_files), unsaved_files_array, options
)
if result != 0:
- raise CXError(result, 'Error reparsing TranslationUnit.')
+ raise CXError(result, "Error reparsing TranslationUnit.")
def save(self, filename: FsPath) -> None:
"""Saves the TranslationUnit to a file.
This is equivalent to passing -emit-ast to the clang frontend. The
@@ -3413,13 +3475,11 @@
TranslationUnit.diagnostics().
filename -- The path to save the translation unit to (str or PathLike).
"""
options = conf.lib.clang_defaultSaveOptions(self)
- result = conf.lib.clang_saveTranslationUnit(
- self, os.fspath(filename), options
- )
+ result = conf.lib.clang_saveTranslationUnit(self, os.fspath(filename), options)
if result != 0:
raise TranslationUnitSaveError(result, "Error saving TranslationUnit.")
def codeComplete(
self,
@@ -3465,25 +3525,31 @@
)
if ptr:
return CodeCompletionResults(ptr)
return None
- def get_tokens(self, locations: Optional[Tuple[SourceLocation, SourceLocation]] = None, extent: Optional[SourceRange] = None) -> Generator[Token, None, None]:
+ def get_tokens(
+ self,
+ locations: Optional[Tuple[SourceLocation, SourceLocation]] = None,
+ extent: Optional[SourceRange] = None,
+ ) -> Generator[Token, None, None]:
"""Obtain tokens in this translation unit.
This is a generator for Token instances. The caller specifies a range
of source code to obtain tokens for. The range can be specified as a
2-tuple of SourceLocation or as a SourceRange. If both are defined,
behavior is undefined.
"""
-
+
if locations is not None:
- final_extent = SourceRange.from_locations(start=locations[0], end=locations[1])
+ final_extent = SourceRange.from_locations(
+ start=locations[0], end=locations[1]
+ )
elif extent is not None:
final_extent = extent
else:
- raise ValueError('no extent given')
+ raise ValueError("no extent given")
return TokenGroup.get_tokens(self, final_extent)
class File(ClangObject):
@@ -3516,13 +3582,15 @@
def __repr__(self) -> str:
return "<File: %s>" % (self.name)
@staticmethod
- def from_result(res: CObjectP, fn: Callable[..., CObjectP], args: Tuple[Any, ...]) -> File:
+ def from_result(
+ res: CObjectP, fn: Callable[..., CObjectP], args: Tuple[Any, ...]
+ ) -> File:
assert isinstance(res, c_object_p)
- resobj = File(res) # pyright: ignore
+ resobj = File(res) # pyright: ignore
# Copy a reference to the TranslationUnit to prevent premature GC.
resobj._tu = args[0]._tu
return resobj
@@ -3640,11 +3708,13 @@
if not cc:
raise IndexError
return CompileCommand(cc, self)
@staticmethod
- def from_result(res: CObjectP, fn: Callable[..., CObjectP], args: Tuple[Any, ...]) -> Optional[CompileCommands]:
+ def from_result(
+ res: CObjectP, fn: Callable[..., CObjectP], args: Tuple[Any, ...]
+ ) -> Optional[CompileCommands]:
if not res:
return None
return CompileCommands(res)
@@ -3658,11 +3728,13 @@
def __del__(self) -> None:
conf.lib.clang_CompilationDatabase_dispose(self)
@staticmethod
- def from_result(res: CObjectP, fn: Callable[..., CObjectP], args: Tuple[Any, ...]) -> CompilationDatabase:
+ def from_result(
+ res: CObjectP, fn: Callable[..., CObjectP], args: Tuple[Any, ...]
+ ) -> CompilationDatabase:
if not res:
raise CompilationDatabaseError(0, "CompilationDatabase loading failed")
return CompilationDatabase(res)
@staticmethod
@@ -3737,11 +3809,11 @@
@property
def cursor(self) -> Cursor:
"""The Cursor this Token corresponds to."""
cursor = Cursor()
- cursor._tu = self._tu # pyright: ignore[reportPrivateUsage]
+ cursor._tu = self._tu # pyright: ignore[reportPrivateUsage]
conf.lib.clang_annotateTokens(self._tu, byref(self), 1, byref(cursor))
return cursor
@@ -3806,15 +3878,19 @@
# Now comes the plumbing to hook up the C library.
# Register callback types
-TranslationUnitIncludesCallback = Annotated[CFuncPointer, None, c_object_p, CPointer[SourceLocation], c_uint, py_object]
+TranslationUnitIncludesCallback = Annotated[
+ CFuncPointer, None, c_object_p, CPointer[SourceLocation], c_uint, py_object
+]
CursorVisitCallback = Annotated[CFuncPointer, c_int, Cursor, Cursor, py_object]
FieldsVisitCallback = Annotated[CFuncPointer, c_int, Cursor, py_object]
-translation_unit_includes_callback: TType[CFuncPointer] = convert_annotation(TranslationUnitIncludesCallback)
+translation_unit_includes_callback: TType[CFuncPointer] = convert_annotation(
+ TranslationUnitIncludesCallback
+)
cursor_visit_callback: TType[CFuncPointer] = convert_annotation(CursorVisitCallback)
fields_visit_callback: TType[CFuncPointer] = convert_annotation(FieldsVisitCallback)
# Functions strictly alphabetical order.
@@ -3823,26 +3899,38 @@
# - If Config.compatibility_check is set to `False`, then a function is allowed to be missing.
# - If a function is missing in C library, it will not be replaced, thus causing NotImplementedError when called.
# - Missing functions are given a `_missing_` attribute, you can check it with `hasattr(conf.lib.xxx, '_missing_')`.
# - These stub functions are generated with a script from old data and manually corrected, so parameter names are missing.
class LibclangExports:
- def clang_annotateTokens(self, p1: TranslationUnit, p2: CPointerParam[Token], p3: p_ulong, p4: CPointerParam[Cursor]) -> r_long:
+ def clang_annotateTokens(
+ self,
+ p1: TranslationUnit,
+ p2: CPointerParam[Token],
+ p3: p_ulong,
+ p4: CPointerParam[Cursor],
+ ) -> r_long:
raise NotImplementedError
def clang_CompilationDatabase_dispose(self, p1: ClangObjectParam) -> r_long:
raise NotImplementedError
@with_errcheck(CompilationDatabase.from_result)
- def clang_CompilationDatabase_fromDirectory(self, p1: CInteropString, p2: CPointerParam[c_ulong]) -> CObjectP:
+ def clang_CompilationDatabase_fromDirectory(
+ self, p1: CInteropString, p2: CPointerParam[c_ulong]
+ ) -> CObjectP:
raise NotImplementedError
@with_errcheck(CompileCommands.from_result)
- def clang_CompilationDatabase_getAllCompileCommands(self, p1: ClangObjectParam) -> CObjectP:
+ def clang_CompilationDatabase_getAllCompileCommands(
+ self, p1: ClangObjectParam
+ ) -> CObjectP:
raise NotImplementedError
@with_errcheck(CompileCommands.from_result)
- def clang_CompilationDatabase_getCompileCommands(self, p1: ClangObjectParam, p2: CInteropString) -> CObjectP:
+ def clang_CompilationDatabase_getCompileCommands(
+ self, p1: ClangObjectParam, p2: CInteropString
+ ) -> CObjectP:
raise NotImplementedError
def clang_CompileCommands_dispose(self, p1: CObjectP) -> r_long:
raise NotImplementedError
@@ -3865,14 +3953,25 @@
raise NotImplementedError
def clang_CompileCommand_getNumArgs(self, p1: CObjectP) -> r_ulong:
raise NotImplementedError
- def clang_codeCompleteAt(self, p1: TranslationUnit, p2: CInteropString, p3: p_long, p4: p_long, p5: CPointerParam[_CXUnsavedFile], p6: p_long, p7: p_long) -> CPointer[CCRStructure]:
- raise NotImplementedError
-
- def clang_codeCompleteGetDiagnostic(self, p1: CodeCompletionResults, p2: p_long) -> Diagnostic:
+ def clang_codeCompleteAt(
+ self,
+ p1: TranslationUnit,
+ p2: CInteropString,
+ p3: p_long,
+ p4: p_long,
+ p5: CPointerParam[_CXUnsavedFile],
+ p6: p_long,
+ p7: p_long,
+ ) -> CPointer[CCRStructure]:
+ raise NotImplementedError
+
+ def clang_codeCompleteGetDiagnostic(
+ self, p1: CodeCompletionResults, p2: p_long
+ ) -> Diagnostic:
raise NotImplementedError
def clang_codeCompleteGetNumDiagnostics(self, p1: CodeCompletionResults) -> r_long:
raise NotImplementedError
@@ -3886,20 +3985,24 @@
raise NotImplementedError
def clang_CXRewriter_dispose(self, p1: Rewriter) -> r_long:
raise NotImplementedError
- def clang_CXRewriter_insertTextBefore(self, p1: Rewriter, p2: SourceLocation, p3: CInteropString) -> r_long:
+ def clang_CXRewriter_insertTextBefore(
+ self, p1: Rewriter, p2: SourceLocation, p3: CInteropString
+ ) -> r_long:
raise NotImplementedError
def clang_CXRewriter_overwriteChangedFiles(self, p1: Rewriter) -> r_long:
raise NotImplementedError
def clang_CXRewriter_removeText(self, p1: Rewriter, p2: SourceRange) -> r_long:
raise NotImplementedError
- def clang_CXRewriter_replaceText(self, p1: Rewriter, p2: SourceRange, p3: CInteropString) -> r_long:
+ def clang_CXRewriter_replaceText(
+ self, p1: Rewriter, p2: SourceRange, p3: CInteropString
+ ) -> r_long:
raise NotImplementedError
def clang_CXRewriter_writeMainFileToStdOut(self, p1: Rewriter) -> r_long:
raise NotImplementedError
@@ -3970,11 +4073,13 @@
raise NotImplementedError
def clang_disposeString(self, p1: _CXString) -> r_long:
raise NotImplementedError
- def clang_disposeTokens(self, p1: TranslationUnit, p2: CPointer[Token], p3: p_uint) -> r_long:
+ def clang_disposeTokens(
+ self, p1: TranslationUnit, p2: CPointer[Token], p3: p_uint
+ ) -> r_long:
raise NotImplementedError
def clang_disposeTranslationUnit(self, p1: TranslationUnit) -> r_long:
raise NotImplementedError
@@ -4027,11 +4132,13 @@
@with_errcheck(_CXString.from_result)
def clang_getCompletionBriefComment(self, p1: CObjectP) -> _CXString:
raise NotImplementedError
- def clang_getCompletionChunkCompletionString(self, p1: CObjectP, p2: p_long) -> CObjectP:
+ def clang_getCompletionChunkCompletionString(
+ self, p1: CObjectP, p2: p_long
+ ) -> CObjectP:
raise NotImplementedError
def clang_getCompletionChunkKind(self, p1: CObjectP, p2: p_long) -> r_long:
raise NotImplementedError
@@ -4078,11 +4185,13 @@
@with_errcheck(Cursor.from_result)
def clang_getCursorReferenced(self, p1: Cursor) -> Cursor:
raise NotImplementedError
- def clang_getCursorReferenceNameRange(self, p1: Cursor, p2: p_ulong, p3: p_ulong) -> SourceRange:
+ def clang_getCursorReferenceNameRange(
+ self, p1: Cursor, p2: p_ulong, p3: p_ulong
+ ) -> SourceRange:
raise NotImplementedError
@with_errcheck(Type.from_result)
def clang_getCursorResultType(self, p1: Cursor) -> Type:
raise NotImplementedError
@@ -4126,11 +4235,13 @@
@with_errcheck(_CXString.from_result)
def clang_getDiagnosticCategoryText(self, p1: Diagnostic) -> _CXString:
raise NotImplementedError
@with_errcheck(_CXString.from_result)
- def clang_getDiagnosticFixIt(self, p1: Diagnostic, p2: p_ulong, p3: CPointerParam[SourceRange]) -> _CXString:
+ def clang_getDiagnosticFixIt(
+ self, p1: Diagnostic, p2: p_ulong, p3: CPointerParam[SourceRange]
+ ) -> _CXString:
raise NotImplementedError
def clang_getDiagnosticInSet(self, p1: CObjectP, p2: p_ulong) -> CObjectP:
raise NotImplementedError
@@ -4142,11 +4253,13 @@
def clang_getDiagnosticNumRanges(self, p1: Diagnostic) -> r_ulong:
raise NotImplementedError
@with_errcheck(_CXString.from_result)
- def clang_getDiagnosticOption(self, p1: Diagnostic, p2: CPointerParam[_CXString]) -> _CXString:
+ def clang_getDiagnosticOption(
+ self, p1: Diagnostic, p2: CPointerParam[_CXString]
+ ) -> _CXString:
raise NotImplementedError
def clang_getDiagnosticRange(self, p1: Diagnostic, p2: p_ulong) -> SourceRange:
raise NotImplementedError
@@ -4190,20 +4303,36 @@
@with_errcheck(File.from_result)
def clang_getIncludedFile(self, p1: Cursor) -> CObjectP:
raise NotImplementedError
- def clang_getInclusions(self, p1: TranslationUnit, p2: TranslationUnitIncludesCallback, p3: CPyObject[List[FileInclusion]]) -> r_long:
- raise NotImplementedError
-
- def clang_getInstantiationLocation(self, p1: SourceLocation, p2: CPointerParam[CObjectP], p3: CPointerParam[c_ulong], p4: CPointerParam[c_ulong], p5: CPointerParam[c_ulong]) -> r_long:
- raise NotImplementedError
-
- def clang_getLocation(self, p1: TranslationUnit, p2: File, p3: p_ulong, p4: p_ulong) -> SourceLocation:
- raise NotImplementedError
-
- def clang_getLocationForOffset(self, p1: TranslationUnit, p2: File, p3: p_ulong) -> SourceLocation:
+ def clang_getInclusions(
+ self,
+ p1: TranslationUnit,
+ p2: TranslationUnitIncludesCallback,
+ p3: CPyObject[List[FileInclusion]],
+ ) -> r_long:
+ raise NotImplementedError
+
+ def clang_getInstantiationLocation(
+ self,
+ p1: SourceLocation,
+ p2: CPointerParam[CObjectP],
+ p3: CPointerParam[c_ulong],
+ p4: CPointerParam[c_ulong],
+ p5: CPointerParam[c_ulong],
+ ) -> r_long:
+ raise NotImplementedError
+
+ def clang_getLocation(
+ self, p1: TranslationUnit, p2: File, p3: p_ulong, p4: p_ulong
+ ) -> SourceLocation:
+ raise NotImplementedError
+
+ def clang_getLocationForOffset(
+ self, p1: TranslationUnit, p2: File, p3: p_ulong
+ ) -> SourceLocation:
raise NotImplementedError
def clang_getNullCursor(self) -> Cursor:
raise NotImplementedError
@@ -4350,23 +4479,48 @@
raise NotImplementedError
def clang_isVolatileQualifiedType(self, p1: Type) -> bool:
raise NotImplementedError
- def clang_parseTranslationUnit(self, p1: Index, p2: CInteropString, p3: CPointerParam[c_char_p], p4: p_long, p5: CPointerParam[_CXUnsavedFile], p6: p_long, p7: p_long) -> CObjectP:
- raise NotImplementedError
-
- def clang_reparseTranslationUnit(self, p1: TranslationUnit, p2: p_long, p3: CPointerParam[_CXUnsavedFile], p4: p_long) -> r_long:
- raise NotImplementedError
-
- def clang_saveTranslationUnit(self, p1: TranslationUnit, p2: CInteropString, p3: p_ulong) -> r_long:
- raise NotImplementedError
-
- def clang_tokenize(self, p1: TranslationUnit, p2: SourceRange, p3: CPointerParam[CPointer[Token]], p4: CPointerParam[c_ulong]) -> r_long:
- raise NotImplementedError
-
- def clang_visitChildren(self, p1: Cursor, p2: CursorVisitCallback, p3: CPyObject[List[Cursor]]) -> r_ulong:
+ def clang_parseTranslationUnit(
+ self,
+ p1: Index,
+ p2: CInteropString,
+ p3: CPointerParam[c_char_p],
+ p4: p_long,
+ p5: CPointerParam[_CXUnsavedFile],
+ p6: p_long,
+ p7: p_long,
+ ) -> CObjectP:
+ raise NotImplementedError
+
+ def clang_reparseTranslationUnit(
+ self,
+ p1: TranslationUnit,
+ p2: p_long,
+ p3: CPointerParam[_CXUnsavedFile],
+ p4: p_long,
+ ) -> r_long:
+ raise NotImplementedError
+
+ def clang_saveTranslationUnit(
+ self, p1: TranslationUnit, p2: CInteropString, p3: p_ulong
+ ) -> r_long:
+ raise NotImplementedError
+
+ def clang_tokenize(
+ self,
+ p1: TranslationUnit,
+ p2: SourceRange,
+ p3: CPointerParam[CPointer[Token]],
+ p4: CPointerParam[c_ulong],
+ ) -> r_long:
+ raise NotImplementedError
+
+ def clang_visitChildren(
+ self, p1: Cursor, p2: CursorVisitCallback, p3: CPyObject[List[Cursor]]
+ ) -> r_ulong:
raise NotImplementedError
def clang_Cursor_getNumArguments(self, p1: Cursor) -> r_long:
raise NotImplementedError
@@ -4383,14 +4537,18 @@
@with_errcheck(Type.from_result)
def clang_Cursor_getTemplateArgumentType(self, p1: Cursor, p2: p_ulong) -> Type:
raise NotImplementedError
- def clang_Cursor_getTemplateArgumentValue(self, p1: Cursor, p2: p_ulong) -> r_longlong:
- raise NotImplementedError
-
- def clang_Cursor_getTemplateArgumentUnsignedValue(self, p1: Cursor, p2: p_ulong) -> r_ulonglong:
+ def clang_Cursor_getTemplateArgumentValue(
+ self, p1: Cursor, p2: p_ulong
+ ) -> r_longlong:
+ raise NotImplementedError
+
+ def clang_Cursor_getTemplateArgumentUnsignedValue(
+ self, p1: Cursor, p2: p_ulong
+ ) -> r_ulonglong:
raise NotImplementedError
def clang_Cursor_isAnonymous(self, p1: Cursor) -> bool:
raise NotImplementedError
@@ -4439,11 +4597,13 @@
@with_errcheck(Type.from_result)
def clang_Type_getNamedType(self, p1: Type) -> Type:
raise NotImplementedError
- def clang_Type_visitFields(self, p1: Type, p2: FieldsVisitCallback, p3: CPyObject[List[Cursor]]) -> r_ulong:
+ def clang_Type_visitFields(
+ self, p1: Type, p2: FieldsVisitCallback, p3: CPyObject[List[Cursor]]
+ ) -> r_ulong:
raise NotImplementedError
class LibclangError(Exception):
m: str
@@ -4553,15 +4713,15 @@
raise LibclangError(msg)
return library
def function_exists(self, name: str) -> bool:
- return not hasattr(getattr(self.lib, name), '_missing_')
+ return not hasattr(getattr(self.lib, name), "_missing_")
def generate_metadata_debug() -> Dict[str, Dict[str, Any]]:
- ''' Generate ctypes metadata for debugging purpose. '''
+ """Generate ctypes metadata for debugging purpose."""
return {name: info for name, info in generate_metadata(LibclangExports, globals())}
conf = Config()
--- clang/ctyped.py 2024-08-03 14:41:42.000000 +0000
+++ clang/ctyped.py 2024-08-03 23:15:21.802497 +0000
@@ -1,48 +1,94 @@
-from ctypes import (CFUNCTYPE, POINTER, WINFUNCTYPE, c_bool, c_byte, c_char,
- c_char_p, c_double, c_float, c_int, c_long, c_longdouble,
- c_longlong, c_short, c_size_t, c_ssize_t, c_ubyte, c_uint,
- c_ulong, c_ulonglong, c_ushort, c_void_p, c_wchar,
- c_wchar_p, py_object)
+from ctypes import (
+ CFUNCTYPE,
+ POINTER,
+ WINFUNCTYPE,
+ c_bool,
+ c_byte,
+ c_char,
+ c_char_p,
+ c_double,
+ c_float,
+ c_int,
+ c_long,
+ c_longdouble,
+ c_longlong,
+ c_short,
+ c_size_t,
+ c_ssize_t,
+ c_ubyte,
+ c_uint,
+ c_ulong,
+ c_ulonglong,
+ c_ushort,
+ c_void_p,
+ c_wchar,
+ c_wchar_p,
+ py_object,
+)
from inspect import Parameter, signature
-from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Generic,
- List, Optional, Tuple, Type, TypeVar, Union, cast)
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Generator,
+ Generic,
+ List,
+ Optional,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ cast,
+)
from typing_extensions import Annotated, ParamSpec, TypeAlias
-_T = TypeVar('_T')
+_T = TypeVar("_T")
if TYPE_CHECKING:
from ctypes import _CArgObject # pyright: ignore[reportPrivateUsage]
from ctypes import _CData # pyright: ignore[reportPrivateUsage]
-AnyCData = TypeVar('AnyCData', bound='_CData')
+AnyCData = TypeVar("AnyCData", bound="_CData")
if TYPE_CHECKING:
from ctypes import Array as _Array # pyright: ignore[reportPrivateUsage]
- from ctypes import \
- _FuncPointer as _FuncPointer # pyright: ignore[reportPrivateUsage]
- from ctypes import \
- _Pointer as _Pointer # pyright: ignore[reportPrivateUsage]
+ from ctypes import (
+ _FuncPointer as _FuncPointer,
+ ) # pyright: ignore[reportPrivateUsage]
+ from ctypes import _Pointer as _Pointer # pyright: ignore[reportPrivateUsage]
# ctypes documentation noted implicit conversion for pointers:
# "For example, you can pass compatible array instances instead of pointer
# types. So, for POINTER(c_int), ctypes accepts an array of c_int:"
# "In addition, if a function argument is explicitly declared to be a
# pointer type (such as POINTER(c_int)) in argtypes, an object of the
# pointed type (c_int in this case) can be passed to the function. ctypes
# will apply the required byref() conversion in this case automatically."
# also, current ctype typeshed thinks byref returns _CArgObject
- _PointerCompatible: TypeAlias = Union['_CArgObject', _Pointer[AnyCData], None, _Array[AnyCData], AnyCData]
- _PyObject: TypeAlias = Union['py_object[_T]', _T]
+ _PointerCompatible: TypeAlias = Union[
+ "_CArgObject", _Pointer[AnyCData], None, _Array[AnyCData], AnyCData
+ ]
+ _PyObject: TypeAlias = Union["py_object[_T]", _T]
else:
# at runtime we don't really import those symbols
- class _Array(Generic[AnyCData]): ...
- class _Pointer(Generic[AnyCData]): ...
- class _PointerCompatible(Generic[AnyCData]): ...
- class _FuncPointer: ...
- class _PyObject(Generic[AnyCData]): ...
+ class _Array(Generic[AnyCData]):
+ ...
+
+ class _Pointer(Generic[AnyCData]):
+ ...
+
+ class _PointerCompatible(Generic[AnyCData]):
+ ...
+
+ class _FuncPointer:
+ ...
+
+ class _PyObject(Generic[AnyCData]):
+ ...
# ANNO_CONVETIBLE can be used to declare that a class have a `from_param`
# method which can convert other types when used as `argtypes`.
# For example: `CClass = Annotated[bytes, ANNO_CONVERTIBLE, c_class]` means
@@ -100,13 +146,17 @@
p_size_t = Annotated[Union[c_size_t, int], ANNO_BASIC, c_size_t]
p_ssize_t = Annotated[Union[c_ssize_t, int], ANNO_BASIC, c_ssize_t]
p_float = Annotated[Union[c_float, float], ANNO_BASIC, c_float]
p_double = Annotated[Union[c_double, float], ANNO_BASIC, c_double]
p_longdouble = Annotated[Union[c_longdouble, float], ANNO_BASIC, c_longdouble]
-p_char_p = Annotated[Union[c_char_p, _Array[c_wchar], bytes, None], ANNO_BASIC, c_char_p]
-p_wchar_p = Annotated[Union[c_wchar_p, _Array[c_wchar], str, None], ANNO_BASIC, c_wchar_p]
-p_void_p = Annotated[Union['_CArgObject', c_void_p, int, None], ANNO_BASIC, c_void_p]
+p_char_p = Annotated[
+ Union[c_char_p, _Array[c_wchar], bytes, None], ANNO_BASIC, c_char_p
+]
+p_wchar_p = Annotated[
+ Union[c_wchar_p, _Array[c_wchar], str, None], ANNO_BASIC, c_wchar_p
+]
+p_void_p = Annotated[Union["_CArgObject", c_void_p, int, None], ANNO_BASIC, c_void_p]
# export Pointer, PointerCompatible, Array and FuncPointer annotation
CArray = Annotated[_Array[AnyCData], ANNO_ARRAY]
CPointer = Annotated[_Pointer[AnyCData], ANNO_POINTER]
@@ -114,163 +164,196 @@
CFuncPointer = Annotated[_FuncPointer, ANNO_CFUNC]
WinFuncPointer = Annotated[_FuncPointer, ANNO_WINFUNC]
CPyObject = Annotated[_PyObject[_T], ANNO_PYOBJ]
-_Params = ParamSpec('_Params')
-_OrigRet = TypeVar('_OrigRet')
-_NewRet = TypeVar('_NewRet')
-
-def with_errcheck(checker: Callable[[_OrigRet, Callable[..., _OrigRet], Tuple[Any, ...]], _NewRet]) -> Callable[[Callable[_Params, _OrigRet]], Callable[_Params, _NewRet]]:
- ''' Decorates a stub function with an error checker. '''
+_Params = ParamSpec("_Params")
+_OrigRet = TypeVar("_OrigRet")
+_NewRet = TypeVar("_NewRet")
+
+
+def with_errcheck(
+ checker: Callable[[_OrigRet, Callable[..., _OrigRet], Tuple[Any, ...]], _NewRet]
+) -> Callable[[Callable[_Params, _OrigRet]], Callable[_Params, _NewRet]]:
+ """Decorates a stub function with an error checker."""
+
def decorator(wrapped: Callable[_Params, _OrigRet]) -> Callable[_Params, _NewRet]:
def wrapper(*args: _Params.args, **kwargs: _Params.kwargs) -> _NewRet:
raise NotImplementedError
# attach original declaration and error checker to wrapper
- setattr(wrapper, '_decl_errcheck_', (wrapped, checker))
+ setattr(wrapper, "_decl_errcheck_", (wrapped, checker))
return wrapper
return decorator
+
# NOTE: Actually, converter is a deprecated form of `restype`.
# According to ctypes documentation:
# "It is possible to assign a callable Python object that is not a ctypes
# type, in this case the function is assumed to return a C int, and the
# callable will be called with this integer, allowing further processing
# or error checking. Using this is deprecated, for more flexible post
# processing or error checking use a ctypes data type as restype and
# assign a callable to the errcheck attribute."
-def with_converter(converter: Callable[[int], _NewRet]) -> Callable[[Callable[_Params, r_int]], Callable[_Params, _NewRet]]:
- ''' Decorates a stub function with a converter, its return type MUST be `r_int`. '''
+
+def with_converter(
+ converter: Callable[[int], _NewRet]
+) -> Callable[[Callable[_Params, r_int]], Callable[_Params, _NewRet]]:
+ """Decorates a stub function with a converter, its return type MUST be `r_int`."""
+
def decorator(wrapped: Callable[_Params, r_int]) -> Callable[_Params, _NewRet]:
def wrapper(*args: _Params.args, **kwargs: _Params.kwargs) -> _NewRet:
raise NotImplementedError
# attach original declaration and converter to wrapper
- setattr(wrapper, '_decl_converter_', (wrapped, converter))
+ setattr(wrapper, "_decl_converter_", (wrapped, converter))
return wrapper
return decorator
-def convert_annotation(typ: Any, global_ns: Optional[Dict[str, Any]] = None) -> Type[Any]:
- ''' Convert an annotation to effective runtime type. '''
+def convert_annotation(
+ typ: Any, global_ns: Optional[Dict[str, Any]] = None
+) -> Type[Any]:
+ """Convert an annotation to effective runtime type."""
if global_ns is None:
global_ns = globals()
if isinstance(typ, str):
- try: typ = eval(typ, global_ns)
+ try:
+ typ = eval(typ, global_ns)
except Exception as exc:
- raise ValueError('Evaluation of delayed annotation failed!') from exc
-
- if not hasattr(typ, '__metadata__'):
+ raise ValueError("Evaluation of delayed annotation failed!") from exc
+
+ if not hasattr(typ, "__metadata__"):
return cast(Type[Any], typ)
# type is Annotated
ident, *detail = typ.__metadata__
if ident is ANNO_CONVERTIBLE:
- ctyp, = detail
+ (ctyp,) = detail
return cast(Type[Any], ctyp)
elif ident is ANNO_ARRAY:
- try: count, = detail
+ try:
+ (count,) = detail
except ValueError:
- raise ValueError('CArray needs to be annotated with its size')
- ctyp, = typ.__args__[0].__args__
+ raise ValueError("CArray needs to be annotated with its size")
+ (ctyp,) = typ.__args__[0].__args__
return cast(Type[Any], convert_annotation(ctyp, global_ns=global_ns) * count)
elif ident is ANNO_POINTER:
assert not detail
- ctyp, = typ.__args__[0].__args__
- return POINTER(convert_annotation(ctyp, global_ns=global_ns)) # pyright: ignore
+ (ctyp,) = typ.__args__[0].__args__
+ return POINTER(convert_annotation(ctyp, global_ns=global_ns)) # pyright: ignore
elif ident is ANNO_CFUNC:
if not detail:
- raise ValueError('CFuncPointer needs to be annotated with its signature')
+ raise ValueError("CFuncPointer needs to be annotated with its signature")
return CFUNCTYPE(*(convert_annotation(t, global_ns=global_ns) for t in detail))
elif ident is ANNO_WINFUNC:
if not detail:
- raise ValueError('WinFuncPointer needs to be annotated with its signature')
- return WINFUNCTYPE(*(convert_annotation(t, global_ns=global_ns) for t in detail))
+ raise ValueError("WinFuncPointer needs to be annotated with its signature")
+ return WINFUNCTYPE(
+ *(convert_annotation(t, global_ns=global_ns) for t in detail)
+ )
elif ident is ANNO_PYOBJ:
assert not detail
return py_object
else:
- raise ValueError(f'Unexpected annotated type {typ}')
-
-
-def convert_func_decl(decl: Callable[..., Any], global_ns: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
- ''' Converts a stub function to ctypes metadata. '''
+ raise ValueError(f"Unexpected annotated type {typ}")
+
+
+def convert_func_decl(
+ decl: Callable[..., Any], global_ns: Optional[Dict[str, Any]] = None
+) -> Dict[str, Any]:
+ """Converts a stub function to ctypes metadata."""
if global_ns is None:
global_ns = globals()
result: Dict[str, Any] = {}
errcheck = None
converter = None
while True:
- if hasattr(decl, '_decl_errcheck_'):
+ if hasattr(decl, "_decl_errcheck_"):
if errcheck is not None:
- raise ValueError('duplicate errcheck in stub function')
- decl, errcheck = getattr(decl, '_decl_errcheck_')
+ raise ValueError("duplicate errcheck in stub function")
+ decl, errcheck = getattr(decl, "_decl_errcheck_")
continue
- if hasattr(decl, '_decl_converter_'):
+ if hasattr(decl, "_decl_converter_"):
if converter is not None:
- raise ValueError('duplicate converter in stub function')
- decl, converter = getattr(decl, '_decl_converter_')
+ raise ValueError("duplicate converter in stub function")
+ decl, converter = getattr(decl, "_decl_converter_")
continue
break
sig = signature(decl)
- param_annos = [p.annotation for p in sig.parameters.values() if p.name != 'self']
+ param_annos = [p.annotation for p in sig.parameters.values() if p.name != "self"]
if all(anno is not Parameter.empty for anno in param_annos):
- result['argtypes'] = [convert_annotation(anno, global_ns=global_ns) for anno in param_annos] or None
+ result["argtypes"] = [
+ convert_annotation(anno, global_ns=global_ns) for anno in param_annos
+ ] or None
if sig.return_annotation is not Parameter.empty:
- result['restype'] = convert_annotation(sig.return_annotation, global_ns=global_ns)
-
- if errcheck is not None: result['errcheck'] = errcheck
- if converter is not None: result['restype'] = converter
+ result["restype"] = convert_annotation(
+ sig.return_annotation, global_ns=global_ns
+ )
+
+ if errcheck is not None:
+ result["errcheck"] = errcheck
+ if converter is not None:
+ result["restype"] = converter
return result
if TYPE_CHECKING:
from ctypes import CDLL, WinDLL
- _DLLT = TypeVar('_DLLT', bound=CDLL)
-_LibDecl = TypeVar('_LibDecl')
-
-
-def generate_metadata(decl_cls: Type[_LibDecl], global_ns: Optional[Dict[str, Any]] = None) -> Generator[Tuple[str, Dict[str, Any]], None, None]:
- ''' Generate ctypes metadata for a stub class. '''
+
+ _DLLT = TypeVar("_DLLT", bound=CDLL)
+_LibDecl = TypeVar("_LibDecl")
+
+
+def generate_metadata(
+ decl_cls: Type[_LibDecl], global_ns: Optional[Dict[str, Any]] = None
+) -> Generator[Tuple[str, Dict[str, Any]], None, None]:
+ """Generate ctypes metadata for a stub class."""
if global_ns is None:
global_ns = globals()
for name in dir(decl_cls):
- if name.startswith('_'): continue
+ if name.startswith("_"):
+ continue
value = getattr(decl_cls, name)
- if not callable(value): continue
+ if not callable(value):
+ continue
yield name, convert_func_decl(value, global_ns=global_ns)
-def load_annotated_library(loader: 'Union[CDLL, WinDLL]', decl_cls: Type[_LibDecl], global_ns: Optional[Dict[str, Any]] = None) -> Tuple[_LibDecl, List[str]]:
- ''' Load a library and set signature metadata according to python type hints.
- `decl_cls` is a class which should only contain method declarations.
- Note: you should only name `self` as `self`, the converter depends on this.
- '''
+def load_annotated_library(
+ loader: "Union[CDLL, WinDLL]",
+ decl_cls: Type[_LibDecl],
+ global_ns: Optional[Dict[str, Any]] = None,
+) -> Tuple[_LibDecl, List[str]]:
+ """Load a library and set signature metadata according to python type hints.
+ `decl_cls` is a class which should only contain method declarations.
+ Note: you should only name `self` as `self`, the converter depends on this.
+ """
if global_ns is None:
global_ns = globals()
result = decl_cls()
missing: List[str] = []
for name, info in generate_metadata(decl_cls, global_ns=global_ns):
- try: func = getattr(loader, name)
+ try:
+ func = getattr(loader, name)
except AttributeError:
stub = getattr(result, name)
stub._missing_ = True
missing.append(name)
continue
@@ -282,64 +365,60 @@
return result, missing
__all__ = [
- 'ANNO_CONVERTIBLE',
- 'AnyCData',
-
- 'p_bool',
- 'p_char',
- 'p_wchar',
- 'p_byte',
- 'p_ubyte',
- 'p_short',
- 'p_ushort',
- 'p_int',
- 'p_uint',
- 'p_long',
- 'p_ulong',
- 'p_longlong',
- 'p_ulonglong',
- 'p_size_t',
- 'p_ssize_t',
- 'p_float',
- 'p_double',
- 'p_longdouble',
- 'p_char_p',
- 'p_wchar_p',
- 'p_void_p',
-
- 'r_bool',
- 'r_char',
- 'r_wchar',
- 'r_byte',
- 'r_ubyte',
- 'r_short',
- 'r_ushort',
- 'r_int',
- 'r_uint',
- 'r_long',
- 'r_ulong',
- 'r_longlong',
- 'r_ulonglong',
- 'r_size_t',
- 'r_ssize_t',
- 'r_float',
- 'r_double',
- 'r_longdouble',
- 'r_char_p',
- 'r_wchar_p',
- 'r_void_p',
-
- 'CArray',
- 'CPointer',
- 'CPointerParam',
- 'CFuncPointer',
- 'WinFuncPointer',
- 'CPyObject',
-
- 'convert_annotation',
- 'with_errcheck',
- 'with_converter',
- 'load_annotated_library',
+ "ANNO_CONVERTIBLE",
+ "AnyCData",
+ "p_bool",
+ "p_char",
+ "p_wchar",
+ "p_byte",
+ "p_ubyte",
+ "p_short",
+ "p_ushort",
+ "p_int",
+ "p_uint",
+ "p_long",
+ "p_ulong",
+ "p_longlong",
+ "p_ulonglong",
+ "p_size_t",
+ "p_ssize_t",
+ "p_float",
+ "p_double",
+ "p_longdouble",
+ "p_char_p",
+ "p_wchar_p",
+ "p_void_p",
+ "r_bool",
+ "r_char",
+ "r_wchar",
+ "r_byte",
+ "r_ubyte",
+ "r_short",
+ "r_ushort",
+ "r_int",
+ "r_uint",
+ "r_long",
+ "r_ulong",
+ "r_longlong",
+ "r_ulonglong",
+ "r_size_t",
+ "r_ssize_t",
+ "r_float",
+ "r_double",
+ "r_longdouble",
+ "r_char_p",
+ "r_wchar_p",
+ "r_void_p",
+ "CArray",
+ "CPointer",
+ "CPointerParam",
+ "CFuncPointer",
+ "WinFuncPointer",
+ "CPyObject",
+ "convert_annotation",
+ "with_errcheck",
+ "with_converter",
+ "load_annotated_library",
]
--- tests/ctyped/test_stub_conversion.py 2024-08-03 03:21:33.000000 +0000
+++ tests/ctyped/test_stub_conversion.py 2024-08-03 23:15:22.143008 +0000
@@ -7,14 +7,20 @@
from dictdiffer import diff as dictdiff # type: ignore
from clang.cindex import *
from clang.cindex import _CXString # pyright: ignore[reportPrivateUsage]
-from clang.cindex import (CCRStructure, Rewriter, c_interop_string, c_object_p,
- cursor_visit_callback, fields_visit_callback,
- generate_metadata_debug,
- translation_unit_includes_callback)
+from clang.cindex import (
+ CCRStructure,
+ Rewriter,
+ c_interop_string,
+ c_object_p,
+ cursor_visit_callback,
+ fields_visit_callback,
+ generate_metadata_debug,
+ translation_unit_includes_callback,
+)
# Functions strictly alphabetical order.
# This is previous version of ctypes metadata, we check equality to this so
# that we can ensure `ctyped` doesn't break anything in its conversion.
@@ -304,56 +310,73 @@
]
# Sadly, ctypes provides no API to check if type is pointer or array.
# Here we use regex to check type name.
-arr_regex = re.compile(r'(?P<typ>[A-Za-z0-9_]+)_Array_(?P<count>[0-9]+)')
-ptr_regex = re.compile(r'LP_(?P<typ>[A-Za-z0-9_]+)')
+arr_regex = re.compile(r"(?P<typ>[A-Za-z0-9_]+)_Array_(?P<count>[0-9]+)")
+ptr_regex = re.compile(r"LP_(?P<typ>[A-Za-z0-9_]+)")
+
def is_ptr_type(typ: Any):
- return typ in (c_void_p, c_char_p, c_wchar_p) or ptr_regex.fullmatch(typ.__name__) is not None
+ return (
+ typ in (c_void_p, c_char_p, c_wchar_p)
+ or ptr_regex.fullmatch(typ.__name__) is not None
+ )
+
def is_arr_type(typ: Any):
return arr_regex.fullmatch(typ.__name__) is not None
+
# If we change a c_void_p parameter to a more exact pointer types, it
# should still be working.
def is_void_specialization(old_type: Any, new_type: Any):
return old_type == c_void_p and is_ptr_type(new_type)
def old_data_to_dict(data: List[Any]):
result: Dict[str, Any] = {}
- result['argtypes'], *data = data
- if not result['argtypes']: result['argtypes'] = None
- if data: result['restype'], *data = data
- else: result['restype'] = c_int
- if data: result['errcheck'], *data = data
+ result["argtypes"], *data = data
+ if not result["argtypes"]:
+ result["argtypes"] = None
+ if data:
+ result["restype"], *data = data
+ else:
+ result["restype"] = c_int
+ if data:
+ result["errcheck"], *data = data
return result
def is_incompatible_diff(diff: Any):
- kind, path, detail = diff # pyright: ignore[reportUnusedVariable]
- if kind == 'add': return True
+ kind, path, detail = diff # pyright: ignore[reportUnusedVariable]
+ if kind == "add":
+ return True
old_type, new_type = detail
- if is_void_specialization(old_type, new_type): return False
+ if is_void_specialization(old_type, new_type):
+ return False
return True
class TestStubConversion(unittest.TestCase):
def test_equality(self):
"""Ensure that ctyped does not break anything."""
- old_function_dict: Dict[str, Dict[str, Any]] = {name: old_data_to_dict(val) for name, *val in FUNCTION_LIST}
+ old_function_dict: Dict[str, Dict[str, Any]] = {
+ name: old_data_to_dict(val) for name, *val in FUNCTION_LIST
+ }
new_function_dict = generate_metadata_debug()
missing_functions = set(old_function_dict.keys())
stable_functions: Set[str] = set()
for new_func in new_function_dict:
if new_func in missing_functions:
missing_functions.remove(new_func)
stable_functions.add(new_func)
- type_diff = [list(dictdiff(old_function_dict[name], new_function_dict[name])) for name in stable_functions] # type: ignore
- type_break = [diffset for diffset in type_diff if diffset and any(is_incompatible_diff(diff) for diff in diffset)] # type: ignore
-
- self.assertTrue(not missing_functions, f'Functions {missing_functions} are missing after stub conversion!')
- self.assertTrue(not type_break, f'Type break happens after stub conversion!')
+ type_diff = [list(dictdiff(old_function_dict[name], new_function_dict[name])) for name in stable_functions] # type: ignore
+ type_break = [diffset for diffset in type_diff if diffset and any(is_incompatible_diff(diff) for diff in diffset)] # type: ignore
+
+ self.assertTrue(
+ not missing_functions,
+ f"Functions {missing_functions} are missing after stub conversion!",
+ )
+ self.assertTrue(not type_break, f"Type break happens after stub conversion!")
|
Thank you for your contribution! This is somewhat tricky situation, now that we have multiple opened PR in the same area of the codebase. But I think there is a way forward:
|
@DeinAlptraum @Endilll
I'll later collect part 1 and submit a PR to @DeinAlptraum 's fork. |
Related: #76664
I used metadata reflection so that we can import C library functions just by declaring annotated python functions. This makes C function types visible to type checker, then it's easy to fix most typing errors.