Skip to content

feat: wdk_build::DerivesMap for derives collection of bindgen output#652

Open
leon-xd wants to merge 7 commits into
microsoft:mainfrom
leon-xd:derives-library
Open

feat: wdk_build::DerivesMap for derives collection of bindgen output#652
leon-xd wants to merge 7 commits into
microsoft:mainfrom
leon-xd:derives-library

Conversation

@leon-xd
Copy link
Copy Markdown
Contributor

@leon-xd leon-xd commented Apr 29, 2026

This PR introduces wdk_build::DerivesMap, a struct to collect the info of all #[derive()] attributes for each type emitted by bindgen. See #654 for the related PR.

Purpose

These changes are a pre-requisite to solve the current issues with generating bindings for mutually exclusive WDK headers (tracked by #514 , #515 , and #516 ). The solution requires types and constants for subsystems to be generated in independent bindgen runs, and will be published in a follow-up PR. In order to prevent duplicate type generation, bindgen invocation for subsystem types will utilize a blocklist_file for every file traversed in the initial base types run.

bindgen by default assumes very minimal derives when encountering a blocklisted type, so this implementation on its own would leave most subsystem types with a sparse set of automatic derives. This is a regression that is unacceptable. In order to prevent this while still keeping our mutual exclusive headers solution, we need to gather the derive information for all of the generated base types, and pass it off to subsystem bindgen type generators via the callback blocklisted_type_implements_trait.

This PR introduces the mechanism to be able to parse a bindgen generated file using syn and extract the derive information for each individual type. In practice, the future bindings generation pipeline will generate the types for the base layer of the WDK, parse it via DerivesMap::from_file, and then insert the created DerivesMap into each subsystem type bindgen via BaseDerivesCallback.

Changes

  • Cargo.toml
    • Bump bindgen from 0.71.0 to 0.72.1
    • Add bitflags = "2.6.0" workspace dependency
  • crates/wdk-build/Cargo.toml
    • Add bitflags dependency
    • Add syn dependency with the parsing feature enabled
  • crates/wdk-build/src/lib.rs
    • Declare new pub mod derives (gated with #[doc(hidden)] since the module is intended for internal wdk-sys build-script use, not public API)
  • crates/wdk-build/src/bindgen.rs
    • Pin size_t_is_usize(true) on wdk_default. size_t/ssize_t are pointer-width on every supported Windows driver target (x64, ARM64, x86), matching Rust's usize/isize. This is the invariant that derives.rs relies on when seeding stdint integer types into its derive map.
  • crates/wdk-build/src/derives.rs
    • DerivesError: typed error enum for derive-map construction failures.
    • DerivesSet: a bitflags! set over the five derive traits bindgen tracks (Copy, Clone, Debug, Default, PartialEq).
    • DerivesSource: enum distinguishing whether a type's derives are immediately known (from a #[derive()] attribute or from a base type) or come from an aliased type.
    • BaseDerivesCallback: a bindgen::callbacks::ParseCallbacks implementation that answers blocklisted_type_implements_trait from a populated DerivesMap. Plugs into bindgen's chain so types blocklisted in one bindings module can still be referenced from another. Used in follow up PR.
    • DerivesMap: a map from type name to DerivesSet. Builds incrementally from Rust source files via syn, and resolves alias chains.
    • syn helpers:
      • idents_and_derives_for_items: walk a slice of Items, collect (name, DerivesSource) pairs.
      • idents_and_derives_for_mod: recurse into a mod and forward to idents_and_derives_for_items.
      • ident_and_derives_for_use: resolve a use a::b as Foo; re-export to (Foo, DerivesSource::Alias(...)).
      • derives_for_type: given a Type, classify into a DerivesSource (named alias, function pointer, primitive, etc.).
      • derives_from_attrs: extract the derive-trait names from a #[derive(...)] attribute list.
    • Small helpers: inner_is_bare_fn, path_is_option, path_is_core_ffi_type
    • Tests: unit-test coverage for parsing, alias resolution (including cycle detection and stable iteration order), classification of #[derive(...)] attributes, and stdint seeding.

Validation

Tested this with the full implementation of the mutually exclusive header resolution and ensured that all outputted bindings had full #[derive] parity against bindings generated from microsoft/main.

Copilot AI review requested due to automatic review settings April 29, 2026 00:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an internal wdk_build::derives module that can parse bindgen-generated Rust (types.rs-style) and reconstruct which auto-derives apply to each emitted type, enabling future multi-pass bindgen runs (e.g., mutually exclusive headers) to preserve derive parity via blocklisted_type_implements_trait.

Changes:

  • Introduces crates/wdk-build/src/derives.rs with DerivesMap, alias resolution, and a ParseCallbacks implementation (BaseDerivesCallback).
  • Updates the default bindgen builder configuration to force size_t/ssize_t to map to usize/isize.
  • Bumps workspace bindgen and adds workspace bitflags, plus syn parsing support in wdk-build.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Cargo.toml Bumps bindgen and adds workspace bitflags dependency.
Cargo.lock Lockfile updates for new/updated dependencies.
crates/wdk-build/Cargo.toml Adds bitflags and syn(parsing) dependencies for derive parsing.
crates/wdk-build/src/lib.rs Exposes internal derives module (doc-hidden).
crates/wdk-build/src/bindgen.rs Sets size_t_is_usize(true) to match Windows driver targets and derive-map assumptions.
crates/wdk-build/src/derives.rs New derive parsing + alias-resolution logic + unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/wdk-build/src/derives.rs
@leon-xd leon-xd assigned leon-xd and wmmc88 and unassigned wmmc88 Apr 29, 2026
@leon-xd leon-xd requested a review from wmmc88 April 29, 2026 00:35
Copilot AI review requested due to automatic review settings April 29, 2026 00:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/wdk-build/Cargo.toml Outdated
Comment thread crates/wdk-build/src/derives.rs Outdated
Comment thread crates/wdk-build/src/derives.rs
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 93.57602% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.55%. Comparing base (74b1da7) to head (d7ca70a).

Files with missing lines Patch % Lines
crates/wdk-build/src/derives.rs 93.57% 15 Missing and 15 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #652      +/-   ##
==========================================
+ Coverage   79.45%   80.55%   +1.10%     
==========================================
  Files          26       27       +1     
  Lines        5500     5967     +467     
  Branches     5500     5967     +467     
==========================================
+ Hits         4370     4807     +437     
- Misses       1001     1016      +15     
- Partials      129      144      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/wdk-build/src/derives.rs
Comment thread crates/wdk-build/tests/derives.rs
@gurry
Copy link
Copy Markdown
Contributor

gurry commented May 5, 2026

@leon-xd can parsing base types have any measurable impact on build time?

@wmmc88
Copy link
Copy Markdown
Collaborator

wmmc88 commented May 5, 2026

@leon-xd can parsing base types have any measurable impact on build time?

@gurry in the pr description of Leon's other pr which uses this new feature, he says:

I also timed the build process with this change to ensure there was not a significant build time regression. As it stands I am currently able to build wdk_sys with all_features in ~45 seconds, compared to microsoft/main's 60 seconds. I would not trust this number as the build script is subject to change with future revisions for this PR.

So while technically its not attributable to only this change, as a whole the change reduces build time. I think a theory was that bindgen's parsing of header seems to scale non-linearly

Copy link
Copy Markdown
Contributor

@gurry gurry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Some minor suggestions

/// Encountered a top-level [`syn::Item`] variant this parser does not
/// handle.
#[error("unhandled syn node: {node}")]
UnhandledSynCase {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
UnhandledSynCase {
UnsupportedSynItem {

This is clearer IMO

.is_some_and(|&set| set.implements(derive_trait))
}

/// Parses a Rust source file and records the derive set for every
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Parses a Rust source file and records the derive set for every
/// Parses given Rust source file content and records the derive set for every

I feel this is clearer since the method does not operate on physical files

Type::Ptr(_) => Ok(DerivesSource::Direct(DerivesSet::all())),
Type::Array(arr) => derives_for_type(&arr.elem),
Type::Path(tp) => {
if path_is_option(&tp.path) && inner_is_bare_fn(&tp.path) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might wanna add a comment saying that this check is for C callbacks or simply have a function called is_callback_type()

return Ok(DerivesSource::Direct(DerivesSet::all()));
}

Ok(DerivesSource::Alias(last.ident.to_string()))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using only the last segment as key here. Can bindgen output ever have two distinct types with the same last segment and they are both used as alias RHSes?

/// Parses a Rust source file and records the derive set for every
/// top-level `struct`, `union`, `enum`, and type alias. Unknown derive
/// idents are ignored.
///
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be worth calling out in the comment that this method does not work for bindgen output containing function declarations (i.e. output generated with CodegenConfig::FUNCTIONS)

Comment on lines +677 to +678
let ty: Type = parse_str("[u32; 4]").unwrap();
assert_direct_full(derives_for_type(&ty).unwrap());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add an assert for arrays element types that do not implement all traits.

Err(DerivesError::UnhandledSynCase { .. })
));
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test verifying that foreign mods like this are rejected with error:

extern "C" {
    pub fn some_ffi_func(...);
    pub static mut some_const: SOME_TYPE;
}

Comment on lines +685 to +688
fn derives_for_type_primitive_path_gets_all() {
let ty: Type = parse_str("u32").unwrap();
assert_direct_full(derives_for_type(&ty).unwrap());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should check for all primitive types in the PRIMITIVES here just like we do for stdints in stdint_names_all_derive_standard_set()

/// the chain-walking loop detects it when a step revisits a name
/// already in the walked set.
#[test]
fn alias_cycle_terminates() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be useful to test for a cycle with three types. Tests for two type cycles may be too narrow to exercise the full logic.

matches!(err, DerivesError::Parse(_)),
"expected Parse, got {err:?}"
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not super important, but may want to also test for bindgen output containing foreign mods i.e. it gets rejected.

Copilot AI review requested due to automatic review settings May 15, 2026 17:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants