[Shift-Left] Auto-detect many typedefs and manual remaps by walking the AST before PInvokeGenerator runs#2240
Conversation
Hosts ClangSharp.PInvokeGenerator v17.0.1 as a library (NuGet package references). Parses the translation unit, walks the AST to discover typedef-tag remappings, merges them with manual --remap entries, then runs PInvokeGenerator.GenerateBindings on the same TranslationUnit in a single parse pass. - Win32MetadataScraper: new console app tool - remapExclusions.rsp: exclusion list for unsafe auto-remaps - ScrapeHeaders.cs: launches scraper, collects .remaps, writes auto RSP - sdk.targets: includes auto-remaps RSP in @(ScraperRsp) - Graceful fallback to stock ClangSharpPInvokeGenerator if scraper not found Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… ptr fixups - Rewrite AST walker to collect ALL typedefs per tag, disambiguate multi-typedef tags automatically (no exclusion list needed) - Add UnwrapType helper for ElaboratedType/AttributedType/ParenType sugar types that were hiding typedef-tag and fn-ptr relationships - Auto-discover function pointer typedef pairs from AST: detect FunctionProtoType typedefs and their PointerType aliases, generate --remap, --exclude, and --reducePointerLevel entries automatically - Remove 12,197 of 12,705 manual --remap entries from scraper.settings.rsp (508 genuinely manual entries remain: semantic primitives, pointer remaps, nested qualifieds, uppercase renames) - Remove 160 of 184 entries from functionPointerFixups.json (24 remain: 6 alreadyPointer, 13 name-only, 5 edge cases) - Delete remapExclusions.rsp (no longer needed) - Zero conflicts: auto-discovery never picks wrong name - Winmd output identical to baseline (24,328,704 bytes) - All 36 tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract WalkTranslationUnit, ResolveTagRemaps, FilterTagRemaps, ResolveFunctionPointerFixups, and UnwrapType from Program.cs into a public RemapDiscovery class that can be tested independently. Add Win32MetadataScraperTests project with 22 xUnit tests covering: - Tag-typedef discovery: _Uppercase, _lowercase, tag prefix, lowercase tag, trailing underscore, enum typedef patterns - Multi-typedef disambiguation: stripped prefix match, ambiguous skip, manual hint resolution - Semantic override protection: manual remap takes priority - Built-in type filtering: _GUID excluded - Identity remap filtering: same tag/typedef skipped - Function pointer discovery: two-step LP/P/PFN patterns, direct fn ptr, calling convention (AttributedType unwrap), non-standard naming skip, typedef alias pattern, ambiguous pointer targets skip - UnwrapType: ElaboratedType and AttributedType sugar peeling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b5692ed to
2a5e7cc
Compare
… direction, simplified consistency check Heuristic fixes: - Add case-insensitive match to multi-typedef disambiguator (fixes in6_addr→IN6_ADDR when competing with IPv6Addr typedef) - Fix fn ptr alreadyPointer direction: when proto has P/LP prefix and alias doesn't, remap alias→proto (e.g., EXCEPTION_ROUTINE→PEXCEPTION_ROUTINE, not reverse) - Filter suffix-adding remaps (e.g., GLUnurbs→GLUnurbsObj) since tag is canonical - Skip C++ namespace-qualified tags (Gdiplus::Status, ABI::*) Cross-partition consistency: - Simplified CheckCrossPartitionRemapConsistency: scans declared type names in generated .cs files and checks against discovered remap tags. No regex needed. - Warning-only (PInvokeGenerator handles these internally) - Added 7 partition #include fixes for cross-partition typedef visibility - 5 irreducible manual remaps for types where AST walker can't discover the typedef in certain partition configurations Tests: 30 scraper tests (7 new), all 60 pass. Winmd 24,328,704 bytes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2a5e7cc to
f5e51c6
Compare
- Remove unused FilterRemaps() method from Program.cs (duplicated RemapDiscovery.FilterTagRemaps) - Tighten HasPointerPrefix to require uppercase after single P prefix - Change CheckCrossPartitionRemapConsistency to void (always returned true) - Fix trailing space in LSA_GET_EXTENDED_CALL_FLAGS in functionPointerFixups.json - Add plan docs from win32metadata repo (auto-type-remappings, shift-left, annotations) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore key manual remaps, tighten auto-discovery heuristics, and capture the remaining rename-drift findings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The auto-generated emitter.autoFnPtr.generated.rsp was written to a non-arch-specific path. When the three CI scrape jobs (x64, x86, arm64) upload artifacts and the build-test job merges them, only one arch's version survives. The x86 version is missing entries for x64/arm64-only APIs (e.g. PGET_RUNTIME_FUNCTION_CALLBACK for RtlInstallFunctionTableCallback), causing the NoInvalidPointersToDelegates test to fail. Fix: include the ScanArch in the rsp filename and use conditional includes in sdk.targets - the specific arch file for local builds, a wildcard glob for CI merged-artifact builds (SkipScraping=true). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r types Three improvements to RemapDiscovery.ResolveFunctionPointerFixups: 1. Split FnProtoToPointerTypedefs into pointer-adding aliases (typedef FOO *PFOO) and same-level aliases (typedef FOO BAR) via new FnSameLevelAliases field. This fixes LSA_AP_CALL_PACKAGE which had 1 pointer alias + 1 same-level alias incorrectly counted as 2 aliases (ambiguous). 2. Filter configuredExcludes from alias candidates before the ambiguity check. This fixes DRVCALLBACK which has LPDRVCALLBACK + PDRVCALLBACK, but PDRVCALLBACK is already excluded in scraper.settings.rsp. 3. Handle already-pointer typedefs with pointer-adding aliases (new pass 2). Types like ACMDRIVERPROC (already *) with LPACMDRIVERPROC (adds another *) now auto-generate remap + exclude + reducePointerLevel. Also removes 5 entries from functionPointerFixups.json (now auto-discovered): - FN_CUSTOM_HELP (stale, not in SDK 10.0.26100.0) - DRVCALLBACK, LSA_AP_CALL_PACKAGE, ACMDRIVERPROC, INSTALLUI_HANDLER_RECORD Auto-discovered reducePointerLevel entries: 152 -> 156. Manual entries: 32 -> 27. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| ["_LARGE_INTEGER"] = "long" | ||
| }; | ||
|
|
||
| var remaps = HeaderSnippetParser.ParseAndResolveTagRemaps(@" |
There was a problem hiding this comment.
What is this trying to do? Looks like this isn't legal - https://godbolt.org/z/TEzKEKrWG
There was a problem hiding this comment.
The headers have _LARGE_INTEGER and the existing scraper.settings.rsp remaps _LARGE_INTEGER to "long" in the metadata. "It was like that when I got here", so I'm not questioning why it was done that way.
The new AST walker logic would cause _LARGE_INTEGER usage in the ASTs to show up in the public metadata as "LARGE_INTEGER", but that would be a breaking change.
So we keep the manual _LARGE_INTEGER=long mapping in the .rsp file and then this test validates that .rsp mapping overrides any AST discovered mappings.
There was a problem hiding this comment.
So, to clarify, it's not trying to inject a typedef to long in the .h to be parsed, but rather this is telling the tool that scrapes the AST that LARGE_INTEGER should be output into the winmd as "long".
Documents all 27 remaining entries grouped by category, explains why each can't be auto-discovered yet, and provides concrete steps for a future agent to address them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrites the remaining-function-pointer-fixups plan to align with the goal of eliminating functionPointerFixups.json entirely. For each category: try a simple heuristic first, fall back to w32m: annotations in headers for cases the heuristic can't handle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
This PR introduces automatic discovery of typedef→tag remaps and function pointer fixups by walking the Clang AST before PInvokeGenerator runs. This eliminates 12,176 manual
--remapentries fromscraper.settings.rspand 152 manual entries fromfunctionPointerFixups.json, replacing them with entries that are auto-discovered at scrape time.The net winmd output is identical — the same types, the same metadata, the same binary. What changes is how the toolchain arrives at the correct configuration: instead of a 12,700-line hand-maintained remap list, the scraper discovers most remaps from the headers themselves.
Motivation
The manual remap list in
scraper.settings.rspwas a growing maintenance burden:How it works
New tool:
Win32MetadataScraper— a .NET 8 console app that hosts ClangSharp'sPInvokeGeneratoras a library. For each partition:CXIndexRemapDiscoverywalks the AST to find:typedef struct _FOO { ... } FOO;→--remap _FOO=FOOtypedef void (*PFOO_CALLBACK)(...)whereFOO_CALLBACKis a bare function typedef →--exclude PFOO_CALLBACK+--reducePointerLevel PFOO_CALLBACK--remapentries (manual always wins on conflict)PInvokeGenerator.GenerateBindingson the same TranslationUnit — single parse, no re-parsingScrapeHeadersMSBuild task collects the per-partition discoveries and:scraper.autoRemaps.generated.rsp(fed back to the scraper for cross-partition consistency)emitter.autoFnPtr.{arch}.generated.rsp(consumed by the emitter for--reducePointerLevel)What remained manual (and why)
--remapentrieslong=int, semantic overrides, COM-specific), remaps for types in C++ namespaces, or cases where the auto-heuristic can't disambiguatefunctionPointerFixups.jsonentriesDRVCALLBACK→LPDRVCALLBACK),alreadyPointerentries, callback types without a P-prefixed pointer typedef, or functions not declared via standard typedef patternsPartition header adjustments
Seven partitions needed additional
#includedirectives so that typedef declarations are visible during AST walking. Without these, the auto-discovery can't find the typedef→tag pair because the typedef is declared in a header not included by that partition. These are additive-only changes tomain.cppfiles.CI pipeline fix
The auto-generated
emitter.autoFnPtr.generated.rspwas originally written to a non-arch-specific path. In the CI pipeline (3 parallel scrape jobs for x64/x86/arm64), the artifact merge would overwrite the file — and the x86 version was missing x64/arm64-only entries (e.g.,PGET_RUNTIME_FUNCTION_CALLBACKforRtlInstallFunctionTableCallback). Fixed by making the filename arch-specific and using conditional includes insdk.targets.Other changes
ChangesSinceLastRelease.txt: Empty! (no net winmd changes)Win32MetadataScraperTestsproject with 41 tests coveringRemapDiscoveryheuristics, RSP file parsing, and header snippet parsingDoTests.ps1: Updated to run the new scraper test suiteBuildSdk.nuspec: IncludesWin32MetadataScraperin the SDK package.github/copilot-instructions.mdfor repo-specific contextdocs/copilot/plans/contains research and implementation plan documentsVerification
ChangesSinceLastRelease.txtreducePointerLevelentriesFiles changed (32 files, +4,033 / −14,104)
sources/Win32MetadataScraper/(3 files)Program.cs(scraper host),RemapDiscovery.cs(AST walking + heuristics),.csprojtests/Win32MetadataScraperTests/(4 files)ScrapeHeaders.cs,TaskUtils.cs,sdk.targetsscraper.settings.rsp,functionPointerFixups.jsonmain.cppfiles#includedirectives for typedef visibilityBuildSdk.nuspec,BuildTools.proj,version.jsonDoTests.ps1,.config/dotnet-tools.json,Directory.Build.propsdocs/copilot/plans/(4 files),.github/copilot-instructions.md