https://github.com/TsXor updated https://github.com/llvm/llvm-project/pull/101941
>From cbe82fc24aa94c919aaa1a01ae72e11ddcd71f19 Mon Sep 17 00:00:00 2001 From: TsXor <zhang050...@outlook.com> Date: Mon, 5 Aug 2024 16:20:40 +0800 Subject: [PATCH] Introduce ctyped in an independent manner. --- clang/bindings/python/clang/binder.py | 804 ++++++++++++++++++ clang/bindings/python/clang/ctyped.py | 433 ++++++++++ .../bindings/python/tests/ctyped/__init__.py | 0 .../tests/ctyped/test_stub_conversion.py | 357 ++++++++ 4 files changed, 1594 insertions(+) create mode 100644 clang/bindings/python/clang/binder.py create mode 100644 clang/bindings/python/clang/ctyped.py create mode 100644 clang/bindings/python/tests/ctyped/__init__.py create mode 100644 clang/bindings/python/tests/ctyped/test_stub_conversion.py diff --git a/clang/bindings/python/clang/binder.py b/clang/bindings/python/clang/binder.py new file mode 100644 index 0000000000000..8cc661a097cb2 --- /dev/null +++ b/clang/bindings/python/clang/binder.py @@ -0,0 +1,804 @@ +# pyright: reportPrivateUsage=false + +# Enable delayed evaluation of function annotations. +from __future__ import annotations + +import os +from ctypes import cdll +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union + +from typing_extensions import Annotated + +from .ctyped import * +from .ctyped import (ANNO_PARAMETER, ANNO_RESULT, ANNO_RESULT_CONVERTER, + generate_metadata) + +if TYPE_CHECKING: + from ctypes import CDLL + from types import EllipsisType + + from .cindex import (CCRStructure, CodeCompletionResults, + CompilationDatabase, CompileCommands, Cursor, + CursorKind, Diagnostic, File, FileInclusion, Index, + Rewriter, SourceLocation, SourceRange, StrPath, + TemplateArgumentKind, Token, TranslationUnit) + from .cindex import Type as ASTType + from .cindex import _CXString, _CXUnsavedFile +else: + EllipsisType = type(Ellipsis) + + +# delayed imports, a list of import name and their alias +# if alias is same as name, use `...` +CINDEX_DELAYED_IMPORTS: List[Tuple[str, Union[str, EllipsisType]]] = [ + ('CCRStructure', ...), + ('CodeCompletionResults', ...), + ('CompilationDatabase', ...), + ('CompileCommands', ...), + ('Cursor', ...), + ('CursorKind', ...), + ('Diagnostic', ...), + ('File', ...), + ('FileInclusion', ...), + ('Index', ...), + ('Rewriter', ...), + ('SourceLocation', ...), + ('SourceRange', ...), + ('TemplateArgumentKind', ...), + ('Token', ...), + ('TranslationUnit', ...), + ('Type', 'ASTType'), + ('_CXString', ...), + ('_CXUnsavedFile', ...), + ('c_interop_string', ...), +] + +def load_cindex_types() -> None: + cindex_imports: Dict[str, Any] = {} + from . import cindex + for name, alias in CINDEX_DELAYED_IMPORTS: + if isinstance(alias, EllipsisType): alias = name + cindex_imports[alias] = getattr(cindex, name) + globals().update(cindex_imports) + + +# ctypes doesn't implicitly convert c_void_p to the appropriate wrapper +# 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**. +CObjectP = CPointer[c_void_p] +c_object_p: Type[CObjectP] = convert_annotation(CObjectP) + + +# Register callback types +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] + +# TODO: these lines should replace the definition in cindex.py +#translation_unit_includes_callback: Type[CFuncPointer] = convert_annotation(TranslationUnitIncludesCallback, globals()) +#cursor_visit_callback: Type[CFuncPointer] = convert_annotation(CursorVisitCallback, globals()) +#fields_visit_callback: Type[CFuncPointer] = convert_annotation(FieldsVisitCallback, globals()) + + +# Misc object param/result types +# A type may only have param type or result type, this is normal. +ASTTypeResult = Annotated['ASTType', ANNO_RESULT, 'ASTType', 'ASTType.from_result'] + +CInteropStringParam = Annotated[Union[str, bytes, None], ANNO_PARAMETER, 'c_interop_string'] +CInteropStringResult = Annotated[Optional[str], ANNO_RESULT, 'c_interop_string', 'c_interop_string.to_python_string'] + +CXStringResult = Annotated[str, ANNO_RESULT, '_CXString', '_CXString.from_result'] + +CompilationDatabaseParam = Annotated['CompilationDatabase', ANNO_PARAMETER, c_object_p] +CompilationDatabaseResult = Annotated['CompilationDatabase', ANNO_RESULT, c_object_p, 'CompilationDatabase.from_result'] + +CompileCommandsResult = Annotated['CompileCommands', ANNO_RESULT, c_object_p, 'CompileCommands.from_result'] + +CursorResult = Annotated['Cursor', ANNO_RESULT, 'Cursor', 'Cursor.from_cursor_result'] +CursorNullableResult = Annotated[Optional['Cursor'], ANNO_RESULT, 'Cursor', 'Cursor.from_result'] + +DiagnosticParam = Annotated['Diagnostic', ANNO_PARAMETER, c_object_p] + +FileResult = Annotated['File', ANNO_RESULT, c_object_p, 'File.from_result'] + +TemplateArgumentKindResult = Annotated['TemplateArgumentKind', ANNO_RESULT_CONVERTER, 'TemplateArgumentKind.from_id'] + +TranslationUnitParam = Annotated['TranslationUnit', ANNO_PARAMETER, c_object_p] + + +# Functions strictly alphabetical order. +# NOTE: +# - These functions are stubs, they are not implemented, and is replaced by C functions at runtime. +# - 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: CUlongParam, p4: CPointerParam[Cursor]) -> CLongResult: + raise NotImplementedError + + def clang_CompilationDatabase_dispose(self, p1: CompilationDatabaseParam) -> CLongResult: + raise NotImplementedError + + def clang_CompilationDatabase_fromDirectory(self, p1: CInteropStringParam, p2: CPointerParam[c_ulong]) -> CompilationDatabaseResult: + raise NotImplementedError + + def clang_CompilationDatabase_getAllCompileCommands(self, p1: CompilationDatabaseParam) -> CompileCommandsResult: + raise NotImplementedError + + def clang_CompilationDatabase_getCompileCommands(self, p1: CompilationDatabaseParam, p2: CInteropStringParam) -> CompileCommandsResult: + raise NotImplementedError + + def clang_CompileCommands_dispose(self, p1: CObjectP) -> CLongResult: + raise NotImplementedError + + def clang_CompileCommands_getCommand(self, p1: CObjectP, p2: CUlongParam) -> CObjectP: + raise NotImplementedError + + def clang_CompileCommands_getSize(self, p1: CObjectP) -> CUlongResult: + raise NotImplementedError + + def clang_CompileCommand_getArg(self, p1: CObjectP, p2: CUlongParam) -> CXStringResult: + raise NotImplementedError + + def clang_CompileCommand_getDirectory(self, p1: CObjectP) -> CXStringResult: + raise NotImplementedError + + def clang_CompileCommand_getFilename(self, p1: CObjectP) -> CXStringResult: + raise NotImplementedError + + def clang_CompileCommand_getNumArgs(self, p1: CObjectP) -> CUlongResult: + raise NotImplementedError + + def clang_codeCompleteAt(self, p1: TranslationUnit, p2: CInteropStringParam, p3: CLongParam, p4: CLongParam, p5: CPointerParam[_CXUnsavedFile], p6: CLongParam, p7: CLongParam) -> CPointer[CCRStructure]: + raise NotImplementedError + + def clang_codeCompleteGetDiagnostic(self, p1: CodeCompletionResults, p2: CLongParam) -> Diagnostic: + raise NotImplementedError + + def clang_codeCompleteGetNumDiagnostics(self, p1: CodeCompletionResults) -> CLongResult: + raise NotImplementedError + + def clang_createIndex(self, p1: CLongParam, p2: CLongParam) -> CObjectP: + raise NotImplementedError + + def clang_createTranslationUnit(self, p1: Index, p2: CInteropStringParam) -> CObjectP: + raise NotImplementedError + + def clang_CXRewriter_create(self, p1: TranslationUnit) -> CObjectP: + raise NotImplementedError + + def clang_CXRewriter_dispose(self, p1: Rewriter) -> CLongResult: + raise NotImplementedError + + def clang_CXRewriter_insertTextBefore(self, p1: Rewriter, p2: SourceLocation, p3: CInteropStringParam) -> CLongResult: + raise NotImplementedError + + def clang_CXRewriter_overwriteChangedFiles(self, p1: Rewriter) -> CLongResult: + raise NotImplementedError + + def clang_CXRewriter_removeText(self, p1: Rewriter, p2: SourceRange) -> CLongResult: + raise NotImplementedError + + def clang_CXRewriter_replaceText(self, p1: Rewriter, p2: SourceRange, p3: CInteropStringParam) -> CLongResult: + raise NotImplementedError + + def clang_CXRewriter_writeMainFileToStdOut(self, p1: Rewriter) -> CLongResult: + raise NotImplementedError + + def clang_CXXConstructor_isConvertingConstructor(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXConstructor_isCopyConstructor(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXConstructor_isDefaultConstructor(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXConstructor_isMoveConstructor(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXField_isMutable(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isConst(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isDefaulted(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isDeleted(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isCopyAssignmentOperator(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isMoveAssignmentOperator(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isExplicit(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isPureVirtual(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isStatic(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXMethod_isVirtual(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_CXXRecord_isAbstract(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_Cursor_getStorageClass(self, p1: Cursor) -> CIntResult: + raise NotImplementedError + + def clang_EnumDecl_isScoped(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_defaultDiagnosticDisplayOptions(self) -> CUlongResult: + raise NotImplementedError + + def clang_defaultSaveOptions(self, p1: TranslationUnit) -> CUlongResult: + raise NotImplementedError + + def clang_disposeCodeCompleteResults(self, p1: CodeCompletionResults) -> CLongResult: + raise NotImplementedError + + def clang_disposeDiagnostic(self, p1: Diagnostic) -> CLongResult: + raise NotImplementedError + + def clang_disposeIndex(self, p1: Index) -> CLongResult: + raise NotImplementedError + + def clang_disposeString(self, p1: _CXString) -> CLongResult: + raise NotImplementedError + + def clang_disposeTokens(self, p1: TranslationUnit, p2: CPointer[Token], p3: CUintParam) -> CLongResult: + raise NotImplementedError + + def clang_disposeTranslationUnit(self, p1: TranslationUnit) -> CLongResult: + raise NotImplementedError + + def clang_equalCursors(self, p1: Cursor, p2: Cursor) -> bool: + raise NotImplementedError + + def clang_equalLocations(self, p1: SourceLocation, p2: SourceLocation) -> bool: + raise NotImplementedError + + def clang_equalRanges(self, p1: SourceRange, p2: SourceRange) -> bool: + raise NotImplementedError + + def clang_equalTypes(self, p1: ASTType, p2: ASTType) -> bool: + raise NotImplementedError + + def clang_formatDiagnostic(self, p1: Diagnostic, p2: CUlongParam) -> CXStringResult: + raise NotImplementedError + + def clang_getAddressSpace(self, p1: ASTType) -> CIntResult: + raise NotImplementedError + + def clang_getArgType(self, p1: ASTType, p2: CUlongParam) -> ASTTypeResult: + raise NotImplementedError + + def clang_getArrayElementType(self, p1: ASTType) -> ASTTypeResult: + raise NotImplementedError + + def clang_getArraySize(self, p1: ASTType) -> CLonglongResult: + raise NotImplementedError + + def clang_getFieldDeclBitWidth(self, p1: Cursor) -> CLongResult: + raise NotImplementedError + + def clang_getCanonicalCursor(self, p1: Cursor) -> CursorResult: + raise NotImplementedError + + def clang_getCanonicalType(self, p1: ASTType) -> ASTTypeResult: + raise NotImplementedError + + def clang_getChildDiagnostics(self, p1: Diagnostic) -> CObjectP: + raise NotImplementedError + + def clang_getCompletionAvailability(self, p1: CObjectP) -> CLongResult: + raise NotImplementedError + + def clang_getCompletionBriefComment(self, p1: CObjectP) -> CXStringResult: + raise NotImplementedError + + def clang_getCompletionChunkCompletionString(self, p1: CObjectP, p2: CLongParam) -> CObjectP: + raise NotImplementedError + + def clang_getCompletionChunkKind(self, p1: CObjectP, p2: CLongParam) -> CLongResult: + raise NotImplementedError + + def clang_getCompletionChunkText(self, p1: CObjectP, p2: CLongParam) -> CXStringResult: + raise NotImplementedError + + def clang_getCompletionPriority(self, p1: CObjectP) -> CLongResult: + raise NotImplementedError + + def clang_getCString(self, p1: _CXString) -> CInteropStringResult: + raise NotImplementedError + + def clang_getCursor(self, p1: TranslationUnit, p2: SourceLocation) -> Cursor: + raise NotImplementedError + + def clang_getCursorAvailability(self, p1: Cursor) -> CLongResult: + raise NotImplementedError + + def clang_getCursorDefinition(self, p1: Cursor) -> CursorNullableResult: + raise NotImplementedError + + def clang_getCursorDisplayName(self, p1: Cursor) -> CXStringResult: + raise NotImplementedError + + def clang_getCursorExceptionSpecificationType(self, p1: Cursor) -> CIntResult: + raise NotImplementedError + + def clang_getCursorExtent(self, p1: Cursor) -> SourceRange: + raise NotImplementedError + + def clang_getCursorLexicalParent(self, p1: Cursor) -> CursorResult: + raise NotImplementedError + + def clang_getCursorLinkage(self, p1: Cursor) -> CIntResult: + raise NotImplementedError + + def clang_getCursorLocation(self, p1: Cursor) -> SourceLocation: + raise NotImplementedError + + def clang_getCursorReferenced(self, p1: Cursor) -> CursorNullableResult: + raise NotImplementedError + + def clang_getCursorReferenceNameRange(self, p1: Cursor, p2: CUlongParam, p3: CUlongParam) -> SourceRange: + raise NotImplementedError + + def clang_getCursorResultType(self, p1: Cursor) -> ASTTypeResult: + raise NotImplementedError + + def clang_getCursorSemanticParent(self, p1: Cursor) -> CursorResult: + raise NotImplementedError + + def clang_getCursorSpelling(self, p1: Cursor) -> CXStringResult: + raise NotImplementedError + + def clang_getCursorTLSKind(self, p1: Cursor) -> CIntResult: + raise NotImplementedError + + def clang_getCursorType(self, p1: Cursor) -> ASTTypeResult: + raise NotImplementedError + + def clang_getCursorUSR(self, p1: Cursor) -> CXStringResult: + raise NotImplementedError + + def clang_Cursor_getMangling(self, p1: Cursor) -> CXStringResult: + raise NotImplementedError + + def clang_getCXXAccessSpecifier(self, p1: Cursor) -> CUlongResult: + raise NotImplementedError + + def clang_getDeclObjCTypeEncoding(self, p1: Cursor) -> CXStringResult: + raise NotImplementedError + + def clang_getDiagnostic(self, p1: TranslationUnitParam, p2: CUlongParam) -> CObjectP: + raise NotImplementedError + + def clang_getDiagnosticCategory(self, p1: Diagnostic) -> CUlongResult: + raise NotImplementedError + + def clang_getDiagnosticCategoryText(self, p1: Diagnostic) -> CXStringResult: + raise NotImplementedError + + def clang_getDiagnosticFixIt(self, p1: Diagnostic, p2: CUlongParam, p3: CPointerParam[SourceRange]) -> CXStringResult: + raise NotImplementedError + + def clang_getDiagnosticInSet(self, p1: CObjectP, p2: CUlongParam) -> CObjectP: + raise NotImplementedError + + def clang_getDiagnosticLocation(self, p1: Diagnostic) -> SourceLocation: + raise NotImplementedError + + def clang_getDiagnosticNumFixIts(self, p1: Diagnostic) -> CUlongResult: + raise NotImplementedError + + def clang_getDiagnosticNumRanges(self, p1: Diagnostic) -> CUlongResult: + raise NotImplementedError + + def clang_getDiagnosticOption(self, p1: Diagnostic, p2: CPointerParam[_CXString]) -> CXStringResult: + raise NotImplementedError + + def clang_getDiagnosticRange(self, p1: Diagnostic, p2: CUlongParam) -> SourceRange: + raise NotImplementedError + + def clang_getDiagnosticSeverity(self, p1: Diagnostic) -> CLongResult: + raise NotImplementedError + + def clang_getDiagnosticSpelling(self, p1: Diagnostic) -> CXStringResult: + raise NotImplementedError + + def clang_getElementType(self, p1: ASTType) -> ASTTypeResult: + raise NotImplementedError + + def clang_getEnumConstantDeclUnsignedValue(self, p1: Cursor) -> CUlonglongResult: + raise NotImplementedError + + def clang_getEnumConstantDeclValue(self, p1: Cursor) -> CLonglongResult: + raise NotImplementedError + + def clang_getEnumDeclIntegerType(self, p1: Cursor) -> ASTTypeResult: + raise NotImplementedError + + def clang_getExceptionSpecificationType(self, p1: ASTType) -> CIntResult: + raise NotImplementedError + + def clang_getFile(self, p1: TranslationUnit, p2: CInteropStringParam) -> CObjectP: + raise NotImplementedError + + def clang_getFileName(self, p1: File) -> CXStringResult: + raise NotImplementedError + + def clang_getFileTime(self, p1: File) -> CUlongResult: + raise NotImplementedError + + def clang_getIBOutletCollectionType(self, p1: Cursor) -> ASTTypeResult: + raise NotImplementedError + + def clang_getIncludedFile(self, p1: Cursor) -> FileResult: + raise NotImplementedError + + def clang_getInclusions(self, p1: TranslationUnit, p2: TranslationUnitIncludesCallback, p3: CPyObject[List[FileInclusion]]) -> CLongResult: + raise NotImplementedError + + def clang_getInstantiationLocation(self, p1: SourceLocation, p2: CPointerParam[CObjectP], p3: CPointerParam[c_ulong], p4: CPointerParam[c_ulong], p5: CPointerParam[c_ulong]) -> CLongResult: + raise NotImplementedError + + def clang_getLocation(self, p1: TranslationUnit, p2: File, p3: CUlongParam, p4: CUlongParam) -> SourceLocation: + raise NotImplementedError + + def clang_getLocationForOffset(self, p1: TranslationUnit, p2: File, p3: CUlongParam) -> SourceLocation: + raise NotImplementedError + + def clang_getNullCursor(self) -> Cursor: + raise NotImplementedError + + def clang_getNumArgTypes(self, p1: ASTType) -> CUlongResult: + raise NotImplementedError + + def clang_getNumCompletionChunks(self, p1: CObjectP) -> CLongResult: + raise NotImplementedError + + def clang_getNumDiagnostics(self, p1: TranslationUnitParam) -> CUlongResult: + raise NotImplementedError + + def clang_getNumDiagnosticsInSet(self, p1: CObjectP) -> CUlongResult: + raise NotImplementedError + + def clang_getNumElements(self, p1: ASTType) -> CLonglongResult: + raise NotImplementedError + + def clang_getNumOverloadedDecls(self, p1: Cursor) -> CUlongResult: + raise NotImplementedError + + def clang_getOverloadedDecl(self, p1: Cursor, p2: CUlongParam) -> CursorResult: + raise NotImplementedError + + def clang_getPointeeType(self, p1: ASTType) -> ASTTypeResult: + raise NotImplementedError + + def clang_getRange(self, p1: SourceLocation, p2: SourceLocation) -> SourceRange: + raise NotImplementedError + + def clang_getRangeEnd(self, p1: SourceRange) -> SourceLocation: + raise NotImplementedError + + def clang_getRangeStart(self, p1: SourceRange) -> SourceLocation: + raise NotImplementedError + + def clang_getResultType(self, p1: ASTType) -> ASTTypeResult: + raise NotImplementedError + + def clang_getSpecializedCursorTemplate(self, p1: Cursor) -> CursorResult: + raise NotImplementedError + + def clang_getTemplateCursorKind(self, p1: Cursor) -> CUlongResult: + raise NotImplementedError + + def clang_getTokenExtent(self, p1: TranslationUnit, p2: Token) -> SourceRange: + raise NotImplementedError + + def clang_getTokenKind(self, p1: Token) -> CUlongResult: + raise NotImplementedError + + def clang_getTokenLocation(self, p1: TranslationUnit, p2: Token) -> SourceLocation: + raise NotImplementedError + + def clang_getTokenSpelling(self, p1: TranslationUnit, p2: Token) -> CXStringResult: + raise NotImplementedError + + def clang_getTranslationUnitCursor(self, p1: TranslationUnit) -> CursorNullableResult: + raise NotImplementedError + + def clang_getTranslationUnitSpelling(self, p1: TranslationUnit) -> CXStringResult: + raise NotImplementedError + + def clang_getTUResourceUsageName(self, p1: CUlongParam) -> CInteropStringResult: + raise NotImplementedError + + def clang_getTypeDeclaration(self, p1: ASTType) -> CursorNullableResult: + raise NotImplementedError + + def clang_getTypedefDeclUnderlyingType(self, p1: Cursor) -> ASTTypeResult: + raise NotImplementedError + + def clang_getTypedefName(self, p1: ASTType) -> CXStringResult: + raise NotImplementedError + + def clang_getTypeKindSpelling(self, p1: CUlongParam) -> CXStringResult: + raise NotImplementedError + + def clang_getTypeSpelling(self, p1: ASTType) -> CXStringResult: + raise NotImplementedError + + def clang_hashCursor(self, p1: Cursor) -> CUlongResult: + raise NotImplementedError + + def clang_isAttribute(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isConstQualifiedType(self, p1: ASTType) -> bool: + raise NotImplementedError + + def clang_isCursorDefinition(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_isDeclaration(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isExpression(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isFileMultipleIncludeGuarded(self, p1: TranslationUnit, p2: File) -> bool: + raise NotImplementedError + + def clang_isFunctionTypeVariadic(self, p1: ASTType) -> bool: + raise NotImplementedError + + def clang_isInvalid(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isPODType(self, p1: ASTType) -> bool: + raise NotImplementedError + + def clang_isPreprocessing(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isReference(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isRestrictQualifiedType(self, p1: ASTType) -> bool: + raise NotImplementedError + + def clang_isStatement(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isTranslationUnit(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isUnexposed(self, p1: CursorKind) -> bool: + raise NotImplementedError + + def clang_isVirtualBase(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_isVolatileQualifiedType(self, p1: ASTType) -> bool: + raise NotImplementedError + + def clang_parseTranslationUnit(self, p1: Index, p2: CInteropStringParam, p3: CPointerParam[c_char_p], p4: CLongParam, p5: CPointerParam[_CXUnsavedFile], p6: CLongParam, p7: CLongParam) -> CObjectP: + raise NotImplementedError + + def clang_reparseTranslationUnit(self, p1: TranslationUnit, p2: CLongParam, p3: CPointerParam[_CXUnsavedFile], p4: CLongParam) -> CLongResult: + raise NotImplementedError + + def clang_saveTranslationUnit(self, p1: TranslationUnit, p2: CInteropStringParam, p3: CUlongParam) -> CLongResult: + raise NotImplementedError + + def clang_tokenize(self, p1: TranslationUnit, p2: SourceRange, p3: CPointerParam[CPointer[Token]], p4: CPointerParam[c_ulong]) -> CLongResult: + raise NotImplementedError + + def clang_visitChildren(self, p1: Cursor, p2: CursorVisitCallback, p3: CPyObject[List[Cursor]]) -> CUlongResult: + raise NotImplementedError + + def clang_Cursor_getNumArguments(self, p1: Cursor) -> CLongResult: + raise NotImplementedError + + def clang_Cursor_getArgument(self, p1: Cursor, p2: CUlongParam) -> CursorNullableResult: + raise NotImplementedError + + def clang_Cursor_getNumTemplateArguments(self, p1: Cursor) -> CLongResult: + raise NotImplementedError + + def clang_Cursor_getTemplateArgumentKind(self, p1: Cursor, p2: CUlongParam) -> TemplateArgumentKindResult: + raise NotImplementedError + + def clang_Cursor_getTemplateArgumentType(self, p1: Cursor, p2: CUlongParam) -> ASTTypeResult: + raise NotImplementedError + + def clang_Cursor_getTemplateArgumentValue(self, p1: Cursor, p2: CUlongParam) -> CLonglongResult: + raise NotImplementedError + + def clang_Cursor_getTemplateArgumentUnsignedValue(self, p1: Cursor, p2: CUlongParam) -> CUlonglongResult: + raise NotImplementedError + + def clang_Cursor_isAnonymous(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_Cursor_isBitField(self, p1: Cursor) -> bool: + raise NotImplementedError + + def clang_Cursor_getBinaryOpcode(self, p1: Cursor) -> CLongResult: + raise NotImplementedError + + def clang_Cursor_getBriefCommentText(self, p1: Cursor) -> CXStringResult: + raise NotImplementedError + + def clang_Cursor_getRawCommentText(self, p1: Cursor) -> CXStringResult: + raise NotImplementedError + + def clang_Cursor_getOffsetOfField(self, p1: Cursor) -> CLonglongResult: + raise NotImplementedError + + def clang_Location_isInSystemHeader(self, p1: SourceLocation) -> bool: + raise NotImplementedError + + def clang_Type_getAlignOf(self, p1: ASTType) -> CLonglongResult: + raise NotImplementedError + + def clang_Type_getClassType(self, p1: ASTType) -> ASTTypeResult: + raise NotImplementedError + + def clang_Type_getNumTemplateArguments(self, p1: ASTType) -> CLongResult: + raise NotImplementedError + + def clang_Type_getTemplateArgumentAsType(self, p1: ASTType, p2: CUlongParam) -> ASTTypeResult: + raise NotImplementedError + + def clang_Type_getOffsetOf(self, p1: ASTType, p2: CInteropStringParam) -> CLonglongResult: + raise NotImplementedError + + def clang_Type_getSizeOf(self, p1: ASTType) -> CLonglongResult: + raise NotImplementedError + + def clang_Type_getCXXRefQualifier(self, p1: ASTType) -> CUlongResult: + raise NotImplementedError + + def clang_Type_getNamedType(self, p1: ASTType) -> ASTTypeResult: + raise NotImplementedError + + def clang_Type_visitFields(self, p1: ASTType, p2: FieldsVisitCallback, p3: CPyObject[List[Cursor]]) -> CUlongResult: + raise NotImplementedError + + +class LibclangError(Exception): + m: str + + def __init__(self, message: str): + self.m = message + + def __str__(self) -> str: + return self.m + + +class Config: + _lib: Optional[LibclangExports] = None + + library_path: Optional[str] = None + library_file: Optional[str] = None + compatibility_check: bool = True + loaded: bool = False + + @staticmethod + def set_library_path(path: StrPath) -> None: + """Set the path in which to search for libclang""" + if Config.loaded: + raise Exception( + "library path must be set before before using " + "any other functionalities in libclang." + ) + + Config.library_path = os.fspath(path) + + @staticmethod + def set_library_file(filename: StrPath) -> None: + """Set the exact location of libclang""" + if Config.loaded: + raise Exception( + "library file must be set before before using " + "any other functionalities in libclang." + ) + + Config.library_file = os.fspath(filename) + + @staticmethod + def set_compatibility_check(check_status: bool) -> None: + """Perform compatibility check when loading libclang + + The python bindings are only tested and evaluated with the version of + libclang they are provided with. To ensure correct behavior a (limited) + compatibility check is performed when loading the bindings. This check + will throw an exception, as soon as it fails. + + In case these bindings are used with an older version of libclang, parts + that have been stable between releases may still work. Users of the + python bindings can disable the compatibility check. This will cause + the python bindings to load, even though they are written for a newer + version of libclang. Failures now arise if unsupported or incompatible + features are accessed. The user is required to test themselves if the + features they are using are available and compatible between different + libclang versions. + """ + if Config.loaded: + raise Exception( + "compatibility_check must be set before before " + "using any other functionalities in libclang." + ) + + Config.compatibility_check = check_status + + @property + def lib(self) -> LibclangExports: + if self._lib is None: + clib = self.get_cindex_library() + load_cindex_types() + exports, missing = load_annotated_library(clib, LibclangExports, globals()) + if Config.compatibility_check and missing: + raise LibclangError( + f"Missing functions: {missing}. Please ensure that your python" + "bindings are compatible with your libclang.so version." + ) + Config.loaded = True + self._lib = exports + return self._lib + + @staticmethod + def cfunc_metadata() -> Dict[str, Dict[str, Any]]: + ''' Generate ctypes metadata for debugging purpose. ''' + load_cindex_types() + return {name: info for name, info in generate_metadata(LibclangExports, globals())} + + def get_filename(self) -> str: + if Config.library_file: + return Config.library_file + + from platform import system as sysname + + name = sysname() + + if name == "Darwin": + file = "libclang.dylib" + elif name == "Windows": + file = "libclang.dll" + else: + file = "libclang.so" + + if Config.library_path: + file = Config.library_path + "/" + file + + return file + + def get_cindex_library(self) -> CDLL: + try: + library = cdll.LoadLibrary(self.get_filename()) + except OSError as e: + msg = ( + str(e) + ". To provide a path to libclang use " + "Config.set_library_path() or " + "Config.set_library_file()." + ) + raise LibclangError(msg) + + return library + + def function_exists(self, name: str) -> bool: + return not hasattr(getattr(self.lib, name), '_missing_') diff --git a/clang/bindings/python/clang/ctyped.py b/clang/bindings/python/clang/ctyped.py new file mode 100644 index 0000000000000..5095e5ae9741a --- /dev/null +++ b/clang/bindings/python/clang/ctyped.py @@ -0,0 +1,433 @@ +# pyright: reportPrivateUsage=false + +import sys +from ctypes import (CFUNCTYPE, POINTER, 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, ForwardRef, Generator, Generic, + List, Optional, Tuple, Type, TypeVar, Union, cast) + +from typing_extensions import Annotated, ParamSpec, TypeAlias + +_T = TypeVar('_T') + +if TYPE_CHECKING: + from ctypes import _CArgObject, _CData + +AnyCData = TypeVar('AnyCData', bound='_CData') + + +if TYPE_CHECKING: + from ctypes import Array as _Array + from ctypes import _FuncPointer as _FuncPointer + from ctypes import _Pointer as _Pointer + + # 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] +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]): ... + + +if sys.platform == "win32": + from ctypes import WINFUNCTYPE +else: + def WINFUNCTYPE( + restype: Type['_CData'] | None, + *argtypes: Type['_CData'], + use_errno: bool = False, + use_last_error: bool = False + ) -> Type[_FuncPointer]: + raise NotImplementedError + + +# 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 +# `c_class`(name of your class) can convert `bytes` parameters. Then use +# `CClass` as parameter type in stub function declaration and you will get what +# you want. + +ANNO_BASIC = object() +ANNO_PARAMETER = ANNO_BASIC +ANNO_RESULT = object() # ANNO_RESULT_ERRCHECK +ANNO_RESULT_CONVERTER = object() # deprecated by ctypes +ANNO_ARRAY = object() +ANNO_POINTER = object() +ANNO_CFUNC = object() +ANNO_WINFUNC = object() +ANNO_PYOBJ = object() + + +# corresponding annotated python types for ctypes +# use C*Param for parameters, C*Result for returns + +CBoolResult = Annotated[bool, ANNO_BASIC, c_bool] +CCharResult = Annotated[bytes, ANNO_BASIC, c_char] +CWcharResult = Annotated[str, ANNO_BASIC, c_wchar] +CByteResult = Annotated[int, ANNO_BASIC, c_byte] +CUbyteResult = Annotated[int, ANNO_BASIC, c_ubyte] +CShortResult = Annotated[int, ANNO_BASIC, c_short] +CUshortResult = Annotated[int, ANNO_BASIC, c_ushort] +CIntResult = Annotated[int, ANNO_BASIC, c_int] +CUintResult = Annotated[int, ANNO_BASIC, c_uint] +CLongResult = Annotated[int, ANNO_BASIC, c_long] +CUlongResult = Annotated[int, ANNO_BASIC, c_ulong] +CLonglongResult = Annotated[int, ANNO_BASIC, c_longlong] +CUlonglongResult = Annotated[int, ANNO_BASIC, c_ulonglong] +CSizeTResult = Annotated[int, ANNO_BASIC, c_size_t] +CSsizeTResult = Annotated[int, ANNO_BASIC, c_ssize_t] +CFloatResult = Annotated[float, ANNO_BASIC, c_float] +CDoubleResult = Annotated[float, ANNO_BASIC, c_double] +CLongdoubleResult = Annotated[float, ANNO_BASIC, c_longdouble] +CCharPResult = Annotated[Optional[bytes], ANNO_BASIC, c_char_p] +CWcharPResult = Annotated[Optional[str], ANNO_BASIC, c_wchar_p] +CVoidPResult = Annotated[Optional[int], ANNO_BASIC, c_void_p] + +CBoolParam = Annotated[Union[c_bool, bool], ANNO_BASIC, c_bool] +CCharParam = Annotated[Union[c_char, bytes], ANNO_BASIC, c_char] +CWcharParam = Annotated[Union[c_wchar, str], ANNO_BASIC, c_wchar] +CByteParam = Annotated[Union[c_byte, int], ANNO_BASIC, c_byte] +CUbyteParam = Annotated[Union[c_ubyte, int], ANNO_BASIC, c_ubyte] +CShortParam = Annotated[Union[c_short, int], ANNO_BASIC, c_short] +CUshortParam = Annotated[Union[c_ushort, int], ANNO_BASIC, c_ushort] +CIntParam = Annotated[Union[c_int, int], ANNO_BASIC, c_int] +CUintParam = Annotated[Union[c_uint, int], ANNO_BASIC, c_uint] +CLongParam = Annotated[Union[c_long, int], ANNO_BASIC, c_long] +CUlongParam = Annotated[Union[c_ulong, int], ANNO_BASIC, c_ulong] +CLonglongParam = Annotated[Union[c_longlong, int], ANNO_BASIC, c_longlong] +CUlonglongParam = Annotated[Union[c_ulonglong, int], ANNO_BASIC, c_ulonglong] +CSizeTParam = Annotated[Union[c_size_t, int], ANNO_BASIC, c_size_t] +CSsizeTParam = Annotated[Union[c_ssize_t, int], ANNO_BASIC, c_ssize_t] +CFloatParam = Annotated[Union[c_float, float], ANNO_BASIC, c_float] +CDoubleParam = Annotated[Union[c_double, float], ANNO_BASIC, c_double] +CLongDoubleParam = Annotated[Union[c_longdouble, float], ANNO_BASIC, c_longdouble] +CCharPParam = Annotated[Union[_Array[c_wchar], c_char_p, bytes, None], ANNO_BASIC, c_char_p] +CWcharPParam = Annotated[Union[_Array[c_wchar], c_wchar_p, str, None], ANNO_BASIC, c_wchar_p] +CVoidPParam = Annotated[Union['_CArgObject', _Pointer[Any], _Array[Any], 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] +CPointerParam = Annotated[_PointerCompatible[AnyCData], ANNO_POINTER] +CFuncPointer = Annotated[_FuncPointer, ANNO_CFUNC] +WinFuncPointer = Annotated[_FuncPointer, ANNO_WINFUNC] +CPyObject = Annotated[_PyObject[_T], ANNO_PYOBJ] + + +# using decorators to declare errcheck and converter is convenient +# but you will need to use ANNO_RESULT instead if you need delayed evaluation + +_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)) + 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, CIntResult]], Callable[_Params, _NewRet]]: + ''' Decorates a stub function with a converter, its return type MUST be `r_int`. ''' + def decorator(wrapped: Callable[_Params, CIntResult]) -> 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)) + 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. ''' + if global_ns is None: + global_ns = globals() + + if isinstance(typ, ForwardRef): + typ = typ.__forward_arg__ + + if isinstance(typ, str): + try: typ = eval(typ, global_ns) + except Exception as exc: + 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_BASIC: + ctyp, = detail + return convert_annotation(ctyp, global_ns=global_ns) + + elif ident is ANNO_RESULT: + ctyp, _ = detail + return convert_annotation(ctyp, global_ns=global_ns) + + elif ident is ANNO_RESULT_CONVERTER: + return c_int + + elif ident is ANNO_ARRAY: + try: count, = detail + except ValueError: + 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 + + elif ident is ANNO_CFUNC: + if not detail: + 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)) + + elif ident is ANNO_PYOBJ: + assert not detail + return py_object + + else: + raise ValueError(f'Unexpected annotated type {typ}') + + +def get_resconv_info(typ: Any, global_ns: Optional[Dict[str, Any]] = None) -> Optional[Tuple[Any, Any]]: + if global_ns is None: + global_ns = globals() + + if isinstance(typ, str): + try: typ = eval(typ, global_ns) + except Exception as exc: + raise ValueError('Evaluation of delayed annotation failed!') from exc + + if not hasattr(typ, '__metadata__'): + return None + # type is Annotated + ident, *detail = typ.__metadata__ + if ident not in (ANNO_RESULT, ANNO_RESULT_CONVERTER): + return None + + if ident is ANNO_RESULT: + _, conv = detail + else: + conv, = detail + if isinstance(conv, str): + conv = eval(conv, global_ns) + if ident is ANNO_RESULT: + return (conv, None) + else: + return (None, conv) + + +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 errcheck is not None or converter is not None: + raise ValueError('duplicate return conversion specifications, burst your legs') + decl, errcheck = getattr(decl, '_decl_errcheck_') + continue + + if hasattr(decl, '_decl_converter_'): + if errcheck is not None or converter is not None: + raise ValueError('duplicate return conversion specifications, burst your legs') + 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'] + 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 + + if sig.return_annotation is not Parameter.empty: + resconv = get_resconv_info(sig.return_annotation, global_ns=global_ns) + if resconv is not None: + if errcheck is not None or converter is not None: + ValueError('duplicate return conversion specifications, burst your legs') + errcheck, converter = resconv + 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 +_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 + value = getattr(decl_cls, name) + 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. + ''' + 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) + except AttributeError: + stub = getattr(result, name) + stub._missing_ = True + missing.append(name) + continue + + for attr, infoval in info.items(): + setattr(func, attr, infoval) + + setattr(result, name, func) + + return result, missing + + +__all__ = [ + 'ANNO_PARAMETER', + 'AnyCData', + + 'c_bool', + 'c_char', + 'c_wchar', + 'c_byte', + 'c_ubyte', + 'c_short', + 'c_ushort', + 'c_int', + 'c_uint', + 'c_long', + 'c_ulong', + 'c_longlong', + 'c_ulonglong', + 'c_size_t', + 'c_ssize_t', + 'c_float', + 'c_double', + 'c_longdouble', + 'c_char_p', + 'c_wchar_p', + 'c_void_p', + 'py_object', + + 'CBoolParam', + 'CCharParam', + 'CWcharParam', + 'CByteParam', + 'CUbyteParam', + 'CShortParam', + 'CUshortParam', + 'CIntParam', + 'CUintParam', + 'CLongParam', + 'CUlongParam', + 'CLonglongParam', + 'CUlonglongParam', + 'CSizeTParam', + 'CSsizeTParam', + 'CFloatParam', + 'CDoubleParam', + 'CLongDoubleParam', + 'CCharPParam', + 'CWcharPParam', + 'CVoidPParam', + + 'CBoolResult', + 'CCharResult', + 'CWcharResult', + 'CByteResult', + 'CUbyteResult', + 'CShortResult', + 'CUshortResult', + 'CIntResult', + 'CUintResult', + 'CLongResult', + 'CUlongResult', + 'CLonglongResult', + 'CUlonglongResult', + 'CSizeTResult', + 'CSsizeTResult', + 'CFloatResult', + 'CDoubleResult', + 'CLongdoubleResult', + 'CCharPResult', + 'CWcharPResult', + 'CVoidPResult', + + 'CArray', + 'CPointer', + 'CPointerParam', + 'CFuncPointer', + 'WinFuncPointer', + 'CPyObject', + + 'convert_annotation', + 'with_errcheck', + 'with_converter', + 'load_annotated_library', +] diff --git a/clang/bindings/python/tests/ctyped/__init__.py b/clang/bindings/python/tests/ctyped/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/clang/bindings/python/tests/ctyped/test_stub_conversion.py b/clang/bindings/python/tests/ctyped/test_stub_conversion.py new file mode 100644 index 0000000000000..1350d6f1987c6 --- /dev/null +++ b/clang/bindings/python/tests/ctyped/test_stub_conversion.py @@ -0,0 +1,357 @@ +import re +import unittest +from ctypes import * +from typing import Any, Dict, List, Set, Tuple + +from dictdiffer import diff as dictdiff + +from clang.binder import Config as BinderConfig +from clang.binder import c_object_p +from clang.cindex import * +from clang.cindex import (CCRStructure, Rewriter, _CXString, c_interop_string, + cursor_visit_callback, fields_visit_callback, + 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. +FUNCTION_LIST: List[Tuple[Any, ...]] = [ + ( + "clang_annotateTokens", + [TranslationUnit, POINTER(Token), c_uint, POINTER(Cursor)], + ), + ("clang_CompilationDatabase_dispose", [c_object_p]), + ( + "clang_CompilationDatabase_fromDirectory", + [c_interop_string, POINTER(c_uint)], + c_object_p, + CompilationDatabase.from_result, + ), + ( + "clang_CompilationDatabase_getAllCompileCommands", + [c_object_p], + c_object_p, + CompileCommands.from_result, + ), + ( + "clang_CompilationDatabase_getCompileCommands", + [c_object_p, c_interop_string], + c_object_p, + CompileCommands.from_result, + ), + ("clang_CompileCommands_dispose", [c_object_p]), + ("clang_CompileCommands_getCommand", [c_object_p, c_uint], c_object_p), + ("clang_CompileCommands_getSize", [c_object_p], c_uint), + ( + "clang_CompileCommand_getArg", + [c_object_p, c_uint], + _CXString, + _CXString.from_result, + ), + ( + "clang_CompileCommand_getDirectory", + [c_object_p], + _CXString, + _CXString.from_result, + ), + ( + "clang_CompileCommand_getFilename", + [c_object_p], + _CXString, + _CXString.from_result, + ), + ("clang_CompileCommand_getNumArgs", [c_object_p], c_uint), + ( + "clang_codeCompleteAt", + [TranslationUnit, c_interop_string, c_int, c_int, c_void_p, c_int, c_int], + POINTER(CCRStructure), + ), + ("clang_codeCompleteGetDiagnostic", [CodeCompletionResults, c_int], Diagnostic), + ("clang_codeCompleteGetNumDiagnostics", [CodeCompletionResults], c_int), + ("clang_createIndex", [c_int, c_int], c_object_p), + ("clang_createTranslationUnit", [Index, c_interop_string], c_object_p), + ("clang_CXRewriter_create", [TranslationUnit], c_object_p), + ("clang_CXRewriter_dispose", [Rewriter]), + ("clang_CXRewriter_insertTextBefore", [Rewriter, SourceLocation, c_interop_string]), + ("clang_CXRewriter_overwriteChangedFiles", [Rewriter], c_int), + ("clang_CXRewriter_removeText", [Rewriter, SourceRange]), + ("clang_CXRewriter_replaceText", [Rewriter, SourceRange, c_interop_string]), + ("clang_CXRewriter_writeMainFileToStdOut", [Rewriter]), + ("clang_CXXConstructor_isConvertingConstructor", [Cursor], bool), + ("clang_CXXConstructor_isCopyConstructor", [Cursor], bool), + ("clang_CXXConstructor_isDefaultConstructor", [Cursor], bool), + ("clang_CXXConstructor_isMoveConstructor", [Cursor], bool), + ("clang_CXXField_isMutable", [Cursor], bool), + ("clang_CXXMethod_isConst", [Cursor], bool), + ("clang_CXXMethod_isDefaulted", [Cursor], bool), + ("clang_CXXMethod_isDeleted", [Cursor], bool), + ("clang_CXXMethod_isCopyAssignmentOperator", [Cursor], bool), + ("clang_CXXMethod_isMoveAssignmentOperator", [Cursor], bool), + ("clang_CXXMethod_isExplicit", [Cursor], bool), + ("clang_CXXMethod_isPureVirtual", [Cursor], bool), + ("clang_CXXMethod_isStatic", [Cursor], bool), + ("clang_CXXMethod_isVirtual", [Cursor], bool), + ("clang_CXXRecord_isAbstract", [Cursor], bool), + ("clang_EnumDecl_isScoped", [Cursor], bool), + ("clang_defaultDiagnosticDisplayOptions", [], c_uint), + ("clang_defaultSaveOptions", [TranslationUnit], c_uint), + ("clang_disposeCodeCompleteResults", [CodeCompletionResults]), + # ("clang_disposeCXTUResourceUsage", + # [CXTUResourceUsage]), + ("clang_disposeDiagnostic", [Diagnostic]), + ("clang_disposeIndex", [Index]), + ("clang_disposeString", [_CXString]), + ("clang_disposeTokens", [TranslationUnit, POINTER(Token), c_uint]), + ("clang_disposeTranslationUnit", [TranslationUnit]), + ("clang_equalCursors", [Cursor, Cursor], bool), + ("clang_equalLocations", [SourceLocation, SourceLocation], bool), + ("clang_equalRanges", [SourceRange, SourceRange], bool), + ("clang_equalTypes", [Type, Type], bool), + ("clang_formatDiagnostic", [Diagnostic, c_uint], _CXString, _CXString.from_result), + ("clang_getArgType", [Type, c_uint], Type, Type.from_result), + ("clang_getArrayElementType", [Type], Type, Type.from_result), + ("clang_getArraySize", [Type], c_longlong), + ("clang_getFieldDeclBitWidth", [Cursor], c_int), + ("clang_getCanonicalCursor", [Cursor], Cursor, Cursor.from_cursor_result), + ("clang_getCanonicalType", [Type], Type, Type.from_result), + ("clang_getChildDiagnostics", [Diagnostic], c_object_p), + ("clang_getCompletionAvailability", [c_void_p], c_int), + ("clang_getCompletionBriefComment", [c_void_p], _CXString, _CXString.from_result), + ("clang_getCompletionChunkCompletionString", [c_void_p, c_int], c_object_p), + ("clang_getCompletionChunkKind", [c_void_p, c_int], c_int), + ( + "clang_getCompletionChunkText", + [c_void_p, c_int], + _CXString, + _CXString.from_result, + ), + ("clang_getCompletionPriority", [c_void_p], c_int), + ( + "clang_getCString", + [_CXString], + c_interop_string, + c_interop_string.to_python_string, + ), + ("clang_getCursor", [TranslationUnit, SourceLocation], Cursor), + ("clang_getCursorAvailability", [Cursor], c_int), + ("clang_getCursorDefinition", [Cursor], Cursor, Cursor.from_result), + ("clang_getCursorDisplayName", [Cursor], _CXString, _CXString.from_result), + ("clang_getCursorExtent", [Cursor], SourceRange), + ("clang_getCursorLexicalParent", [Cursor], Cursor, Cursor.from_cursor_result), + ("clang_getCursorLocation", [Cursor], SourceLocation), + ("clang_getCursorReferenced", [Cursor], Cursor, Cursor.from_result), + ("clang_getCursorReferenceNameRange", [Cursor, c_uint, c_uint], SourceRange), + ("clang_getCursorResultType", [Cursor], Type, Type.from_result), + ("clang_getCursorSemanticParent", [Cursor], Cursor, Cursor.from_cursor_result), + ("clang_getCursorSpelling", [Cursor], _CXString, _CXString.from_result), + ("clang_getCursorType", [Cursor], Type, Type.from_result), + ("clang_getCursorUSR", [Cursor], _CXString, _CXString.from_result), + ("clang_Cursor_getMangling", [Cursor], _CXString, _CXString.from_result), + # ("clang_getCXTUResourceUsage", + # [TranslationUnit], + # CXTUResourceUsage), + ("clang_getCXXAccessSpecifier", [Cursor], c_uint), + ("clang_getDeclObjCTypeEncoding", [Cursor], _CXString, _CXString.from_result), + ("clang_getDiagnostic", [c_object_p, c_uint], c_object_p), + ("clang_getDiagnosticCategory", [Diagnostic], c_uint), + ("clang_getDiagnosticCategoryText", [Diagnostic], _CXString, _CXString.from_result), + ( + "clang_getDiagnosticFixIt", + [Diagnostic, c_uint, POINTER(SourceRange)], + _CXString, + _CXString.from_result, + ), + ("clang_getDiagnosticInSet", [c_object_p, c_uint], c_object_p), + ("clang_getDiagnosticLocation", [Diagnostic], SourceLocation), + ("clang_getDiagnosticNumFixIts", [Diagnostic], c_uint), + ("clang_getDiagnosticNumRanges", [Diagnostic], c_uint), + ( + "clang_getDiagnosticOption", + [Diagnostic, POINTER(_CXString)], + _CXString, + _CXString.from_result, + ), + ("clang_getDiagnosticRange", [Diagnostic, c_uint], SourceRange), + ("clang_getDiagnosticSeverity", [Diagnostic], c_int), + ("clang_getDiagnosticSpelling", [Diagnostic], _CXString, _CXString.from_result), + ("clang_getElementType", [Type], Type, Type.from_result), + ("clang_getEnumConstantDeclUnsignedValue", [Cursor], c_ulonglong), + ("clang_getEnumConstantDeclValue", [Cursor], c_longlong), + ("clang_getEnumDeclIntegerType", [Cursor], Type, Type.from_result), + ("clang_getFile", [TranslationUnit, c_interop_string], c_object_p), + ("clang_getFileName", [File], _CXString, _CXString.from_result), + ("clang_getFileTime", [File], c_uint), + ("clang_getIBOutletCollectionType", [Cursor], Type, Type.from_result), + ("clang_getIncludedFile", [Cursor], c_object_p, File.from_result), + ( + "clang_getInclusions", + [TranslationUnit, translation_unit_includes_callback, py_object], + ), + ( + "clang_getInstantiationLocation", + [ + SourceLocation, + POINTER(c_object_p), + POINTER(c_uint), + POINTER(c_uint), + POINTER(c_uint), + ], + ), + ("clang_getLocation", [TranslationUnit, File, c_uint, c_uint], SourceLocation), + ("clang_getLocationForOffset", [TranslationUnit, File, c_uint], SourceLocation), + ("clang_getNullCursor", None, Cursor), + ("clang_getNumArgTypes", [Type], c_uint), + ("clang_getNumCompletionChunks", [c_void_p], c_int), + ("clang_getNumDiagnostics", [c_object_p], c_uint), + ("clang_getNumDiagnosticsInSet", [c_object_p], c_uint), + ("clang_getNumElements", [Type], c_longlong), + ("clang_getNumOverloadedDecls", [Cursor], c_uint), + ("clang_getOverloadedDecl", [Cursor, c_uint], Cursor, Cursor.from_cursor_result), + ("clang_getPointeeType", [Type], Type, Type.from_result), + ("clang_getRange", [SourceLocation, SourceLocation], SourceRange), + ("clang_getRangeEnd", [SourceRange], SourceLocation), + ("clang_getRangeStart", [SourceRange], SourceLocation), + ("clang_getResultType", [Type], Type, Type.from_result), + ("clang_getSpecializedCursorTemplate", [Cursor], Cursor, Cursor.from_cursor_result), + ("clang_getTemplateCursorKind", [Cursor], c_uint), + ("clang_getTokenExtent", [TranslationUnit, Token], SourceRange), + ("clang_getTokenKind", [Token], c_uint), + ("clang_getTokenLocation", [TranslationUnit, Token], SourceLocation), + ( + "clang_getTokenSpelling", + [TranslationUnit, Token], + _CXString, + _CXString.from_result, + ), + ("clang_getTranslationUnitCursor", [TranslationUnit], Cursor, Cursor.from_result), + ( + "clang_getTranslationUnitSpelling", + [TranslationUnit], + _CXString, + _CXString.from_result, + ), + ( + "clang_getTUResourceUsageName", + [c_uint], + c_interop_string, + c_interop_string.to_python_string, + ), + ("clang_getTypeDeclaration", [Type], Cursor, Cursor.from_result), + ("clang_getTypedefDeclUnderlyingType", [Cursor], Type, Type.from_result), + ("clang_getTypedefName", [Type], _CXString, _CXString.from_result), + ("clang_getTypeKindSpelling", [c_uint], _CXString, _CXString.from_result), + ("clang_getTypeSpelling", [Type], _CXString, _CXString.from_result), + ("clang_hashCursor", [Cursor], c_uint), + ("clang_isAttribute", [CursorKind], bool), + ("clang_isConstQualifiedType", [Type], bool), + ("clang_isCursorDefinition", [Cursor], bool), + ("clang_isDeclaration", [CursorKind], bool), + ("clang_isExpression", [CursorKind], bool), + ("clang_isFileMultipleIncludeGuarded", [TranslationUnit, File], bool), + ("clang_isFunctionTypeVariadic", [Type], bool), + ("clang_isInvalid", [CursorKind], bool), + ("clang_isPODType", [Type], bool), + ("clang_isPreprocessing", [CursorKind], bool), + ("clang_isReference", [CursorKind], bool), + ("clang_isRestrictQualifiedType", [Type], bool), + ("clang_isStatement", [CursorKind], bool), + ("clang_isTranslationUnit", [CursorKind], bool), + ("clang_isUnexposed", [CursorKind], bool), + ("clang_isVirtualBase", [Cursor], bool), + ("clang_isVolatileQualifiedType", [Type], bool), + ( + "clang_parseTranslationUnit", + [Index, c_interop_string, c_void_p, c_int, c_void_p, c_int, c_int], + c_object_p, + ), + ("clang_reparseTranslationUnit", [TranslationUnit, c_int, c_void_p, c_int], c_int), + ("clang_saveTranslationUnit", [TranslationUnit, c_interop_string, c_uint], c_int), + ( + "clang_tokenize", + [TranslationUnit, SourceRange, POINTER(POINTER(Token)), POINTER(c_uint)], + ), + ("clang_visitChildren", [Cursor, cursor_visit_callback, py_object], c_uint), + ("clang_Cursor_getNumArguments", [Cursor], c_int), + ("clang_Cursor_getArgument", [Cursor, c_uint], Cursor, Cursor.from_result), + ("clang_Cursor_getNumTemplateArguments", [Cursor], c_int), + ( + "clang_Cursor_getTemplateArgumentKind", + [Cursor, c_uint], + TemplateArgumentKind.from_id, + ), + ("clang_Cursor_getTemplateArgumentType", [Cursor, c_uint], Type, Type.from_result), + ("clang_Cursor_getTemplateArgumentValue", [Cursor, c_uint], c_longlong), + ("clang_Cursor_getTemplateArgumentUnsignedValue", [Cursor, c_uint], c_ulonglong), + ("clang_Cursor_isAnonymous", [Cursor], bool), + ("clang_Cursor_isBitField", [Cursor], bool), + ("clang_Cursor_getBinaryOpcode", [Cursor], c_int), + ("clang_Cursor_getBriefCommentText", [Cursor], _CXString, _CXString.from_result), + ("clang_Cursor_getRawCommentText", [Cursor], _CXString, _CXString.from_result), + ("clang_Cursor_getOffsetOfField", [Cursor], c_longlong), + ("clang_Location_isInSystemHeader", [SourceLocation], bool), + ("clang_Type_getAlignOf", [Type], c_longlong), + ("clang_Type_getClassType", [Type], Type, Type.from_result), + ("clang_Type_getNumTemplateArguments", [Type], c_int), + ("clang_Type_getTemplateArgumentAsType", [Type, c_uint], Type, Type.from_result), + ("clang_Type_getOffsetOf", [Type, c_interop_string], c_longlong), + ("clang_Type_getSizeOf", [Type], c_longlong), + ("clang_Type_getCXXRefQualifier", [Type], c_uint), + ("clang_Type_getNamedType", [Type], Type, Type.from_result), + ("clang_Type_visitFields", [Type, fields_visit_callback, py_object], c_uint), +] + + +# 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_]+)') + +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 + +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 + return result + + +def is_incompatible_diff(diff: Any): + kind, path, detail = diff + if kind == 'add': return True + old_type, new_type = detail + 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} + new_function_dict = BinderConfig.cfunc_metadata() + + 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_break = [diffset for diffset in type_diff if diffset and any(is_incompatible_diff(diff) for diff in diffset)] + + 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_break}!') _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits