[clang] #101784 part 1: introduce ctyped in an independent manner (PR #101941)

via cfe-commits cfe-commits at lists.llvm.org
Mon Aug 5 01:35:43 PDT 2024


https://github.com/TsXor created https://github.com/llvm/llvm-project/pull/101941

This is part 1 as described in #101784.
I moved `LibclangExports`, `LibclangError` and modified `Config` class to a new file: `binder.py`. It can now be used by `cindex.py` with a simple `from .binder import Config`.
This PR doesn't modify any existing files, so it is safe to merge. However, @DeinAlptraum will need to manually delete `functionList`, `Config` class and `LibclangError` and add `from .binder import Config` to utilize its typing benefits.

Note: compared to #101784, I added a check that prevents `ctyped` from importing `WINFUNCTYPE` when not on windows, which should fix failing linux tests.

>From 3e12ffd4afa52126e5fd162d62f832626bfb8578 Mon Sep 17 00:00:00 2001
From: TsXor <zhang050525 at 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..89bb70d615ede
--- /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[c_char_p, _Array[c_wchar], bytes, None], ANNO_BASIC, c_char_p]
+CWcharPParam = Annotated[Union[c_wchar_p, _Array[c_wchar], str, None], ANNO_BASIC, c_wchar_p]
+CVoidPParam = 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]
+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}!')



More information about the cfe-commits mailing list