From d79f995826586a4294a021748c69f19cabfa2d41 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 00:18:18 +0000 Subject: [PATCH 1/3] feat: implement arch-specific types support (issue #4299) Agent-Logs-Url: https://github.com/microsoft/windows-rs/sessions/2109417e-09ef-4ea0-85cb-b7cd994610ad Co-authored-by: kennykerr <9845234+kennykerr@users.noreply.github.com> --- crates/libs/metadata/src/merge/mod.rs | 155 +++++++++++++++++- crates/libs/rdl/src/clang/mod.rs | 24 ++- crates/libs/rdl/src/reader/attribute_ref.rs | 15 +- crates/libs/rdl/src/reader/fn.rs | 7 + crates/libs/rdl/src/reader/mod.rs | 74 +++++++++ crates/libs/rdl/src/reader/struct.rs | 4 + crates/libs/rdl/src/reader/union.rs | 4 + crates/libs/rdl/src/writer/fn.rs | 9 +- crates/libs/rdl/src/writer/mod.rs | 35 ++++ crates/libs/rdl/src/writer/struct.rs | 45 +---- crates/tests/libs/metadata/tests/merge.rs | 33 ++++ .../libs/metadata/tests/merge_arch_out.rdl | 14 ++ .../libs/metadata/tests/merge_arch_x64.rdl | 9 + .../libs/metadata/tests/merge_arch_x86.rdl | 9 + crates/tests/libs/rdl/tests/error.rs | 57 +++++++ crates/tests/libs/rdl/tests/nested-arches.rdl | 16 +- .../tests/libs/rdl/tests/roundtrip/arches.rdl | 4 +- 17 files changed, 461 insertions(+), 53 deletions(-) create mode 100644 crates/tests/libs/metadata/tests/merge_arch_out.rdl create mode 100644 crates/tests/libs/metadata/tests/merge_arch_x64.rdl create mode 100644 crates/tests/libs/metadata/tests/merge_arch_x86.rdl diff --git a/crates/libs/metadata/src/merge/mod.rs b/crates/libs/metadata/src/merge/mod.rs index 1881199bf7b..0bd9400329b 100644 --- a/crates/libs/metadata/src/merge/mod.rs +++ b/crates/libs/metadata/src/merge/mod.rs @@ -25,6 +25,9 @@ impl std::fmt::Display for Error { #[derive(Default)] pub struct Merger { input: Vec, + /// Arch-tagged inputs: `(path, arch_bits)` where arch_bits is a bitmask + /// (1=X86, 2=X64, 4=Arm64) indicating which architecture the file targets. + arch_inputs: Vec<(String, i32)>, output: String, } @@ -49,6 +52,21 @@ impl Merger { self } + /// Adds an architecture-tagged input winmd file. + /// + /// `arch` is a bitmask indicating which architecture this file was built for: + /// - `1` → X86 + /// - `2` → X64 + /// - `4` → Arm64 + /// + /// When `merge()` is called, types present in **all** arch-tagged inputs get + /// no `SupportedArchitectureAttribute`; types present only in a **subset** get + /// `SupportedArchitectureAttribute(present_arch_mask)`. + pub fn arch_input(&mut self, path: &str, arch: i32) -> &mut Self { + self.arch_inputs.push((path.to_string(), arch)); + self + } + pub fn output(&mut self, output: &str) -> &mut Self { self.output = output.to_string(); self @@ -70,11 +88,73 @@ impl Merger { let mut file = writer::File::new(name); + // Write plain (untagged) inputs as-is. let mut types: Vec> = index.types().collect(); types.sort_by(|a, b| (a.namespace(), a.name()).cmp(&(b.namespace(), b.name()))); for ty in types { - write_type(&mut file, &index, ty, None); + write_type(&mut file, &index, ty, None, None); + } + + // Write arch-tagged inputs with computed SupportedArchitecture annotations. + if !self.arch_inputs.is_empty() { + // Compute the bitmask for "all arches present in this merge run". + let all_arches_mask: i32 = self + .arch_inputs + .iter() + .fold(0, |acc, (_, arch)| acc | arch); + + // Load each arch-tagged file group. + let mut arch_groups: Vec<(reader::TypeIndex, i32)> = + Vec::with_capacity(self.arch_inputs.len()); + for (path, arch_bits) in &self.arch_inputs { + let files = read_inputs(&[path.clone()])?; + arch_groups.push((reader::TypeIndex::new(files), *arch_bits)); + } + + // Compute the union of arch bits for each (namespace, name) pair. + let mut arch_presence: HashMap<(String, String), i32> = HashMap::new(); + for (idx, arch_bits) in &arch_groups { + for ty in idx.types() { + *arch_presence + .entry((ty.namespace().to_string(), ty.name().to_string())) + .or_default() |= arch_bits; + } + } + + // Build a flat list of (TypeIndex ref, TypeDef, arch_bits) sorted by (ns, name). + let mut all_type_refs: Vec<(&reader::TypeIndex, reader::TypeDef<'_>, i32)> = + Vec::new(); + for (idx, arch_bits) in &arch_groups { + for ty in idx.types() { + all_type_refs.push((idx, ty, *arch_bits)); + } + } + all_type_refs.sort_by(|a, b| { + (a.1.namespace(), a.1.name()).cmp(&(b.1.namespace(), b.1.name())) + }); + + // Write each type with the appropriate arch annotation. + // For types present in all arches, deduplicate to a single arch-neutral TypeDef. + // For types present in a subset, write each copy with SupportedArchitecture. + let mut deduped: HashSet<(String, String)> = HashSet::new(); + for (idx, ty, _arch_bits) in &all_type_refs { + let key = (ty.namespace().to_string(), ty.name().to_string()); + let present_mask = arch_presence.get(&key).copied().unwrap_or(0); + + let arch_override = if present_mask == all_arches_mask { + // Present on every arch: write once without SupportedArchitecture. + if !deduped.insert(key) { + continue; // Already written. + } + Some(0) // 0 = suppress arch attribute + } else { + // Present on a subset: annotate with the union of arches that have it. + Some(present_mask) + }; + + write_type(&mut file, idx, *ty, None, arch_override); + } } let bytes = file.into_stream(); @@ -125,11 +205,18 @@ fn read_inputs(inputs: &[String]) -> Result, Error> { Ok(result) } +/// Writes a `TypeDef` (and its nested types) from `index` into `file`. +/// +/// `arch_override` controls the `SupportedArchitectureAttribute` on the TypeDef: +/// - `None` → copy attributes as-is (plain merge, no arch logic) +/// - `Some(0)` → drop any existing `SupportedArchitectureAttribute` +/// - `Some(n)` → drop existing and write `SupportedArchitecture(n)` fn write_type( file: &mut writer::File, index: &reader::TypeIndex, def: reader::TypeDef, outer: Option, + arch_override: Option, ) { let extends = def .extends() @@ -168,7 +255,12 @@ fn write_type( .map(|param| Type::Generic(param.name().to_string(), param.sequence())) .collect(); - write_attributes(file, writer::HasAttribute::TypeDef(type_def), def); + write_attributes_with_arch( + file, + writer::HasAttribute::TypeDef(type_def), + def, + arch_override, + ); for map in def.interface_impls() { let interface_impl = file.InterfaceImpl(type_def, &map.interface(&generics)); @@ -229,7 +321,7 @@ fn write_type( for inner_def in index.nested(def) { debug_assert!(inner_def.namespace().is_empty()); debug_assert!(inner_def.flags().is_nested()); - write_type(file, index, inner_def, Some(type_def)); + write_type(file, index, inner_def, Some(type_def), arch_override); } } @@ -237,11 +329,35 @@ fn write_attributes<'a, R: reader::HasAttributes<'a>>( file: &mut writer::File, parent: writer::HasAttribute, row: R, +) { + write_attributes_with_arch(file, parent, row, None); +} + +/// Like [`write_attributes`] but with optional architecture annotation overriding. +/// +/// When `arch_override` is `Some`: +/// - Any existing `SupportedArchitectureAttribute` on `row` is **dropped**. +/// - If `arch_override` is `Some(bits)` with `bits != 0`, a new +/// `SupportedArchitectureAttribute(bits)` is written. +/// - If `arch_override` is `Some(0)`, no arch attribute is written (arch-neutral). +fn write_attributes_with_arch<'a, R: reader::HasAttributes<'a>>( + file: &mut writer::File, + parent: writer::HasAttribute, + row: R, + arch_override: Option, ) { for attribute in row.attributes() { let ctor = attribute.ctor(); let ty = ctor.parent(); + // Skip the existing SupportedArchitectureAttribute when we're overriding arch. + if arch_override.is_some() + && ty.namespace() == "Windows.Win32.Foundation.Metadata" + && ty.name() == "SupportedArchitectureAttribute" + { + continue; + } + let attribute_ref = writer::MemberRefParent::TypeRef(file.TypeRef(ty.namespace(), ty.name())); @@ -253,4 +369,37 @@ fn write_attributes<'a, R: reader::HasAttributes<'a>>( &attribute.value(), ); } + + // Emit the overriding arch attribute when requested (non-zero bits). + if let Some(arch_bits) = arch_override { + if arch_bits != 0 { + write_supported_architecture_attr(file, parent, arch_bits); + } + } +} + +/// Writes a `SupportedArchitectureAttribute` with the given `arch_bits` bitmask. +fn write_supported_architecture_attr( + file: &mut writer::File, + parent: writer::HasAttribute, + arch_bits: i32, +) { + let ns = "Windows.Win32.Foundation.Metadata"; + let name = "SupportedArchitectureAttribute"; + + let type_ref = writer::MemberRefParent::TypeRef(file.TypeRef(ns, name)); + + let sig = Signature { + flags: MethodCallAttributes::HASTHIS, + return_type: Type::Void, + types: vec![Type::I32], + }; + + let ctor_ref = file.MemberRef(".ctor", &sig, type_ref); + + file.Attribute( + parent, + writer::AttributeType::MemberRef(ctor_ref), + &[("".to_string(), Value::I32(arch_bits))], + ); } diff --git a/crates/libs/rdl/src/clang/mod.rs b/crates/libs/rdl/src/clang/mod.rs index c45cb854039..012f6c69f7f 100644 --- a/crates/libs/rdl/src/clang/mod.rs +++ b/crates/libs/rdl/src/clang/mod.rs @@ -275,6 +275,7 @@ pub struct Clang { args: Vec, library: String, filter: Vec, + target: Option, } impl Clang { @@ -366,6 +367,17 @@ impl Clang { self } + /// Sets the target triple used for all clang invocations, e.g. + /// `"x86_64-pc-windows-msvc"`, `"i686-pc-windows-msvc"`, or + /// `"aarch64-pc-windows-msvc"`. + /// + /// This is equivalent to passing `--target=` as the first argument + /// via [`arg`][Self::arg], but is cleaner for per-arch builds. + pub fn target(&mut self, target: &str) -> &mut Self { + self.target = Some(target.to_string()); + self + } + /// Returns the version string reported by the loaded libclang, e.g. /// `"clang version 18.1.0 (...)"`. Loads libclang on first call. pub fn version() -> Result { @@ -395,7 +407,17 @@ impl Clang { let _library = Library::new()?; let index = Index::new()?; let mut collector = Collector::new(); - let args: Vec<_> = self.args.iter().map(String::as_str).collect(); + + // Build the effective args list: optional --target= first, then user args. + let target_arg: String; + let args: Vec<&str> = if let Some(ref t) = self.target { + target_arg = format!("--target={t}"); + std::iter::once(target_arg.as_str()) + .chain(self.args.iter().map(String::as_str)) + .collect() + } else { + self.args.iter().map(String::as_str).collect() + }; for input in &h_paths { let tu = index.parse(input, &args)?; diff --git a/crates/libs/rdl/src/reader/attribute_ref.rs b/crates/libs/rdl/src/reader/attribute_ref.rs index 354704ab9d6..be06e572650 100644 --- a/crates/libs/rdl/src/reader/attribute_ref.rs +++ b/crates/libs/rdl/src/reader/attribute_ref.rs @@ -497,6 +497,19 @@ impl Encoder<'_> { self.encode_named_attribute(target, &attr_ref); } + /// Emits a `Windows.Win32.Foundation.Metadata.SupportedArchitectureAttribute` on `target` + /// with the given architecture bitmask value (1=X86, 2=X64, 4=Arm64, combinable with `|`). + pub fn emit_arch_attribute(&mut self, target: metadata::writer::HasAttribute, arch_bits: i32) { + let attr_ref = AttributeRef { + type_name: metadata::TypeName::named( + "Windows.Win32.Foundation.Metadata", + "SupportedArchitectureAttribute", + ), + args: vec![("".to_string(), metadata::Value::I32(arch_bits))], + }; + self.encode_named_attribute(target, &attr_ref); + } + pub fn is_guid_attribute(&self, attr: &syn::Attribute) -> bool { self.find_attribute_type(attr.path()) .map(|info| &info.type_name == ("Windows.Foundation.Metadata", "GuidAttribute")) @@ -550,7 +563,7 @@ impl Encoder<'_> { for attr in attrs { let path = attr.path(); - if path.is_ident("win32") || path.is_ident("winrt") { + if path.is_ident("win32") || path.is_ident("winrt") || path.is_ident("arch") { continue; } diff --git a/crates/libs/rdl/src/reader/fn.rs b/crates/libs/rdl/src/reader/fn.rs index 23c0188225a..ec2a057fa5f 100644 --- a/crates/libs/rdl/src/reader/fn.rs +++ b/crates/libs/rdl/src/reader/fn.rs @@ -125,6 +125,13 @@ impl Encoder<'_> { self.output .ImplMap(method_def, flags, &name, library.value().as_str()); + if let Some(arch_bits) = self.read_arch(&item.attrs)? { + self.emit_arch_attribute( + metadata::writer::HasAttribute::MethodDef(method_def), + arch_bits, + ); + } + self.encode_attrs( metadata::writer::HasAttribute::MethodDef(method_def), &item.attrs, diff --git a/crates/libs/rdl/src/reader/mod.rs b/crates/libs/rdl/src/reader/mod.rs index 4b8d26f2adb..547d7479d4a 100644 --- a/crates/libs/rdl/src/reader/mod.rs +++ b/crates/libs/rdl/src/reader/mod.rs @@ -424,6 +424,36 @@ impl Encoder<'_> { Ok(None) } + /// Parse an optional `#[arch(...)]` attribute from `attrs`. Returns `Some(bits)` where + /// `bits` is the architecture bitmask (1=X86, 2=X64, 4=Arm64, combinable with `|`) if the + /// attribute is present, `None` if absent, or an error if the attribute is malformed or uses + /// an unknown architecture name. + fn read_arch(&self, attrs: &[syn::Attribute]) -> Result, Error> { + for attr in attrs { + if !attr.path().is_ident("arch") { + continue; + } + + let expr = attr.parse_args::().map_err(|_| { + self.error( + attr, + "`arch` attribute requires architecture arguments (e.g. `#[arch(X86)]`)", + ) + })?; + + let bits = parse_arch_bitmask(&expr).ok_or_else(|| { + self.error( + attr, + "invalid `arch` value; expected `X86`, `X64`, `Arm64`, or a `|`-combination", + ) + })?; + + return Ok(Some(bits)); + } + + Ok(None) + } + fn encode_type(&self, ty: &syn::Type) -> Result { match ty { syn::Type::Path(ty) => self.encode_type_path(ty), @@ -886,6 +916,50 @@ impl Encoder<'_> { } } +/// Parses a `#[arch(...)]` expression into an architecture bitmask. +/// +/// Accepts: +/// - A single identifier: `X86` → 1, `X64` → 2, `Arm64` → 4. +/// - A bitwise-OR combination: `X86 | X64` → 3, `X64 | Arm64` → 6, etc. +/// +/// Returns `None` when the expression contains an unknown architecture name or an +/// unsupported expression form. +pub(crate) fn parse_arch_bitmask(expr: &syn::Expr) -> Option { + match expr { + syn::Expr::Path(p) + if p.qself.is_none() + && p.path.leading_colon.is_none() + && p.path.segments.len() == 1 => + { + arch_name_to_bits(&p.path.segments[0].ident.to_string()) + } + syn::Expr::Binary(syn::ExprBinary { + left, + op: syn::BinOp::BitOr(_), + right, + .. + }) => { + let l = parse_arch_bitmask(left)?; + let r = parse_arch_bitmask(right)?; + Some(l | r) + } + _ => None, + } +} + +/// Maps a well-known architecture name to its bitmask bit. +/// `X86` → 1 +/// `X64` → 2 +/// `Arm64` → 4 +fn arch_name_to_bits(name: &str) -> Option { + match name { + "X86" => Some(1), + "X64" => Some(2), + "Arm64" => Some(4), + _ => None, + } +} + /// Builds a `syn::Signature` with all the optional fields (`constness`, `asyncness`, /// `unsafety`, `abi`) set to `None`. Shared by `Callback`, `Fn`, `Delegate`, and `Method`. pub(crate) fn make_sig( diff --git a/crates/libs/rdl/src/reader/struct.rs b/crates/libs/rdl/src/reader/struct.rs index 87963ad82ed..66ff4ffd8ea 100644 --- a/crates/libs/rdl/src/reader/struct.rs +++ b/crates/libs/rdl/src/reader/struct.rs @@ -42,6 +42,10 @@ impl Encoder<'_> { self.output.ClassLayout(type_def, packing_size, 0); } + if let Some(arch_bits) = self.read_arch(&item.attrs)? { + self.emit_arch_attribute(metadata::writer::HasAttribute::TypeDef(type_def), arch_bits); + } + self.encode_attrs( metadata::writer::HasAttribute::TypeDef(type_def), &item.attrs, diff --git a/crates/libs/rdl/src/reader/union.rs b/crates/libs/rdl/src/reader/union.rs index f702b600e4e..218ac7bc4a1 100644 --- a/crates/libs/rdl/src/reader/union.rs +++ b/crates/libs/rdl/src/reader/union.rs @@ -38,6 +38,10 @@ impl Encoder<'_> { self.output.ClassLayout(type_def, packing_size, 0); } + if let Some(arch_bits) = self.read_arch(&item.attrs)? { + self.emit_arch_attribute(metadata::writer::HasAttribute::TypeDef(type_def), arch_bits); + } + self.encode_attrs( metadata::writer::HasAttribute::TypeDef(type_def), &item.attrs, diff --git a/crates/libs/rdl/src/writer/fn.rs b/crates/libs/rdl/src/writer/fn.rs index 55bc9d4649b..e69ca7b22ba 100644 --- a/crates/libs/rdl/src/writer/fn.rs +++ b/crates/libs/rdl/src/writer/fn.rs @@ -35,7 +35,13 @@ pub fn write_fn(namespace: &str, item: &metadata::reader::MethodDef) -> Result Result TokenStream { + if arches == 0 { + return quote! {}; + } + + let mut parts: Vec = vec![]; + if arches & 1 != 0 { + parts.push(quote! { X86 }); + } + if arches & 2 != 0 { + parts.push(quote! { X64 }); + } + if arches & 4 != 0 { + parts.push(quote! { Arm64 }); + } + + if parts.is_empty() { + return quote! {}; + } + + let value = parts + .iter() + .skip(1) + .fold(parts[0].clone(), |acc, p| quote! { #acc | #p }); + + quote! { #[arch(#value)] } +} + fn write_type_def(item: &metadata::reader::TypeDef) -> Result { match item.category() { // Structs/unions are handled by write_struct_items (which may return diff --git a/crates/libs/rdl/src/writer/struct.rs b/crates/libs/rdl/src/writer/struct.rs index fa2ab84e426..65ca443d9f3 100644 --- a/crates/libs/rdl/src/writer/struct.rs +++ b/crates/libs/rdl/src/writer/struct.rs @@ -50,10 +50,17 @@ pub fn write_struct_items( let keyword = struct_keyword(item); let packed_attr = write_packed_attr(item); - let custom_attrs = write_custom_attributes(item.attributes(), namespace, item.index())?; + let arch_attr = write_arch_attr(item.arches()); + let custom_attrs = write_custom_attributes_except( + item.attributes(), + namespace, + item.index(), + &["SupportedArchitectureAttribute"], + )?; let main_tokens = quote! { #packed_attr + #arch_attr #(#custom_attrs)* #keyword #name_ident { #(#fields)* @@ -224,39 +231,3 @@ fn write_packed_attr_value(packing: Option) -> TokenStream { } quote! {} } - -/// Emits a `#[Windows::Win32::Foundation::Metadata::SupportedArchitecture(...)]` -/// token stream for the given `arches` bitmask, or an empty token stream when -/// `arches` is zero (meaning "all architectures"). -/// -/// Bit layout (matching the Windows metadata): -/// bit 0 (1) → X86 -/// bit 1 (2) → X64 -/// bit 2 (4) → Arm64 -fn write_arch_attr(arches: i32) -> TokenStream { - if arches == 0 { - return quote! {}; - } - - let mut parts: Vec = vec![]; - if arches & 1 != 0 { - parts.push(quote! { X86 }); - } - if arches & 2 != 0 { - parts.push(quote! { X64 }); - } - if arches & 4 != 0 { - parts.push(quote! { Arm64 }); - } - - if parts.is_empty() { - return quote! {}; - } - - let value = parts - .iter() - .skip(1) - .fold(parts[0].clone(), |acc, p| quote! { #acc | #p }); - - quote! { #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(#value)] } -} diff --git a/crates/tests/libs/metadata/tests/merge.rs b/crates/tests/libs/metadata/tests/merge.rs index f18ef184cff..cd9739c5111 100644 --- a/crates/tests/libs/metadata/tests/merge.rs +++ b/crates/tests/libs/metadata/tests/merge.rs @@ -26,3 +26,36 @@ fn test() { .write() .unwrap(); } + +#[test] +fn test_arch() { + // Build per-arch winmds from plain RDL files (no arch attributes). + windows_rdl::reader() + .input("tests/merge_arch_x86.rdl") + .output("tests/merge_arch_x86.winmd") + .write() + .unwrap(); + + windows_rdl::reader() + .input("tests/merge_arch_x64.rdl") + .output("tests/merge_arch_x64.winmd") + .write() + .unwrap(); + + // Merge with arch awareness: x86=1, x64=2. + windows_metadata::merge() + .arch_input("tests/merge_arch_x86.winmd", 1) + .arch_input("tests/merge_arch_x64.winmd", 2) + .output("tests/merge_arch_merged.winmd") + .merge() + .unwrap(); + + // Write back to RDL for golden-file comparison. + windows_rdl::writer() + .input("tests/merge_arch_merged.winmd") + .output("tests/merge_arch_out.rdl") + .filter("Test") + .write() + .unwrap(); +} + diff --git a/crates/tests/libs/metadata/tests/merge_arch_out.rdl b/crates/tests/libs/metadata/tests/merge_arch_out.rdl new file mode 100644 index 00000000000..0bbfc6eafd7 --- /dev/null +++ b/crates/tests/libs/metadata/tests/merge_arch_out.rdl @@ -0,0 +1,14 @@ +#[win32] +mod Test { + struct CommonStruct { + z: u32, + } + #[arch(X64)] + struct X64OnlyStruct { + y: u16, + } + #[arch(X86)] + struct X86OnlyStruct { + x: u8, + } +} diff --git a/crates/tests/libs/metadata/tests/merge_arch_x64.rdl b/crates/tests/libs/metadata/tests/merge_arch_x64.rdl new file mode 100644 index 00000000000..78b1594d6b0 --- /dev/null +++ b/crates/tests/libs/metadata/tests/merge_arch_x64.rdl @@ -0,0 +1,9 @@ +#[win32] +mod Test { + struct CommonStruct { + z: u32, + } + struct X64OnlyStruct { + y: u16, + } +} diff --git a/crates/tests/libs/metadata/tests/merge_arch_x86.rdl b/crates/tests/libs/metadata/tests/merge_arch_x86.rdl new file mode 100644 index 00000000000..1eb36b22091 --- /dev/null +++ b/crates/tests/libs/metadata/tests/merge_arch_x86.rdl @@ -0,0 +1,9 @@ +#[win32] +mod Test { + struct CommonStruct { + z: u32, + } + struct X86OnlyStruct { + x: u8, + } +} diff --git a/crates/tests/libs/rdl/tests/error.rs b/crates/tests/libs/rdl/tests/error.rs index 42754f31fe4..21fb78e4bd7 100644 --- a/crates/tests/libs/rdl/tests/error.rs +++ b/crates/tests/libs/rdl/tests/error.rs @@ -8,3 +8,60 @@ pub fn error_display() { assert_eq!(s, "\nerror: message\n --> file_name.rdl:2:4"); } + +// ── `#[arch(...)]` error cases ──────────────────────────────────────────────── + +/// Helper: try to compile an RDL string and return the error message if it fails. +fn compile_rdl_err(rdl: &str) -> Option { + let out = std::env::temp_dir().join(format!( + "test_arch_err_{}_{}.winmd", + std::process::id(), + { + use std::sync::atomic::{AtomicU32, Ordering}; + static N: AtomicU32 = AtomicU32::new(0); + N.fetch_add(1, Ordering::Relaxed) + } + )); + let result = reader() + .input_str(rdl) + .output(out.to_str().unwrap()) + .write(); + let _ = std::fs::remove_file(&out); + result.err().map(|e| e.to_string()) +} + +#[test] +fn arch_invalid_name_is_error() { + let err = compile_rdl_err( + r#" +#[win32] +mod Test { + #[arch(X86_32)] + struct A { x: u8, } +} + "#, + ); + let msg = err.expect("expected compile error for unknown arch name"); + assert!( + msg.contains("invalid `arch` value"), + "error should mention `arch` value; got: {msg}" + ); +} + +#[test] +fn arch_missing_args_is_error() { + let err = compile_rdl_err( + r#" +#[win32] +mod Test { + #[arch] + struct A { x: u8, } +} + "#, + ); + let msg = err.expect("expected compile error for `arch` without args"); + assert!( + msg.contains("arch"), + "error should mention `arch`; got: {msg}" + ); +} diff --git a/crates/tests/libs/rdl/tests/nested-arches.rdl b/crates/tests/libs/rdl/tests/nested-arches.rdl index 4b4166addbe..035c50ef3e8 100644 --- a/crates/tests/libs/rdl/tests/nested-arches.rdl +++ b/crates/tests/libs/rdl/tests/nested-arches.rdl @@ -3,38 +3,38 @@ mod Windows { mod Win32 { mod System { mod Kernel { - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(Arm64)] + #[arch(Arm64)] union SLIST_HEADER { Anonymous: SLIST_HEADER_0, HeaderArm64: SLIST_HEADER_1, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X64)] + #[arch(X64)] union SLIST_HEADER { Anonymous: SLIST_HEADER_0, HeaderX64: SLIST_HEADER_1, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X86)] + #[arch(X86)] union SLIST_HEADER { Alignment: u64, Anonymous: SLIST_HEADER_0, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(Arm64)] + #[arch(Arm64)] struct SLIST_HEADER_0 { Alignment: u64, Region: u64, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X64)] + #[arch(X64)] struct SLIST_HEADER_0 { Alignment: u64, Region: u64, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X86)] + #[arch(X86)] struct SLIST_HEADER_0 { Next: SINGLE_LIST_ENTRY, Depth: u16, CpuId: u16, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(Arm64)] + #[arch(Arm64)] struct SLIST_HEADER_1 { #[Windows::Win32::Foundation::Metadata::NativeBitfield("Depth", 0, 16)] #[Windows::Win32::Foundation::Metadata::NativeBitfield("Sequence", 16, 48)] @@ -43,7 +43,7 @@ mod Windows { #[Windows::Win32::Foundation::Metadata::NativeBitfield("NextEntry", 4, 60)] _bitfield2: u64, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X64)] + #[arch(X64)] struct SLIST_HEADER_1 { #[Windows::Win32::Foundation::Metadata::NativeBitfield("Depth", 0, 16)] #[Windows::Win32::Foundation::Metadata::NativeBitfield("Sequence", 16, 48)] diff --git a/crates/tests/libs/rdl/tests/roundtrip/arches.rdl b/crates/tests/libs/rdl/tests/roundtrip/arches.rdl index 83496802b66..f0f51cd24d4 100644 --- a/crates/tests/libs/rdl/tests/roundtrip/arches.rdl +++ b/crates/tests/libs/rdl/tests/roundtrip/arches.rdl @@ -1,10 +1,10 @@ #[win32] mod Test { - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X86)] + #[arch(X86)] struct A { x86: u8, } - #[Windows::Win32::Foundation::Metadata::SupportedArchitecture(Arm64 | X64)] + #[arch(X64 | Arm64)] struct B { arm64: u16, x64: u32, From 38b597b24ee908b20947ec7ea5ace6f2accf1afd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 00:19:30 +0000 Subject: [PATCH 2/3] fix: clean up target_arg scope in Clang::write() Agent-Logs-Url: https://github.com/microsoft/windows-rs/sessions/2109417e-09ef-4ea0-85cb-b7cd994610ad Co-authored-by: kennykerr <9845234+kennykerr@users.noreply.github.com> --- crates/libs/rdl/src/clang/mod.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/libs/rdl/src/clang/mod.rs b/crates/libs/rdl/src/clang/mod.rs index 012f6c69f7f..217a4eb7c33 100644 --- a/crates/libs/rdl/src/clang/mod.rs +++ b/crates/libs/rdl/src/clang/mod.rs @@ -409,15 +409,12 @@ impl Clang { let mut collector = Collector::new(); // Build the effective args list: optional --target= first, then user args. - let target_arg: String; - let args: Vec<&str> = if let Some(ref t) = self.target { - target_arg = format!("--target={t}"); - std::iter::once(target_arg.as_str()) - .chain(self.args.iter().map(String::as_str)) - .collect() - } else { - self.args.iter().map(String::as_str).collect() - }; + let target_arg: Option = self.target.as_ref().map(|t| format!("--target={t}")); + let args: Vec<&str> = target_arg + .iter() + .map(String::as_str) + .chain(self.args.iter().map(String::as_str)) + .collect(); for input in &h_paths { let tu = index.parse(input, &args)?; From d72d4e35fac9247e5b0273fba93814536889eae9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:47:06 +0000 Subject: [PATCH 3/3] style: cargo fmt Agent-Logs-Url: https://github.com/microsoft/windows-rs/sessions/fc6fe9bc-1c8b-4b7e-82c6-fef7cfb4f5fa Co-authored-by: kennykerr <9845234+kennykerr@users.noreply.github.com> --- crates/libs/metadata/src/merge/mod.rs | 13 ++++--------- crates/tests/libs/metadata/tests/merge.rs | 1 - crates/tests/libs/rdl/tests/error.rs | 9 +++------ 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/crates/libs/metadata/src/merge/mod.rs b/crates/libs/metadata/src/merge/mod.rs index 0bd9400329b..aa3ecd925c3 100644 --- a/crates/libs/metadata/src/merge/mod.rs +++ b/crates/libs/metadata/src/merge/mod.rs @@ -99,10 +99,7 @@ impl Merger { // Write arch-tagged inputs with computed SupportedArchitecture annotations. if !self.arch_inputs.is_empty() { // Compute the bitmask for "all arches present in this merge run". - let all_arches_mask: i32 = self - .arch_inputs - .iter() - .fold(0, |acc, (_, arch)| acc | arch); + let all_arches_mask: i32 = self.arch_inputs.iter().fold(0, |acc, (_, arch)| acc | arch); // Load each arch-tagged file group. let mut arch_groups: Vec<(reader::TypeIndex, i32)> = @@ -123,16 +120,14 @@ impl Merger { } // Build a flat list of (TypeIndex ref, TypeDef, arch_bits) sorted by (ns, name). - let mut all_type_refs: Vec<(&reader::TypeIndex, reader::TypeDef<'_>, i32)> = - Vec::new(); + let mut all_type_refs: Vec<(&reader::TypeIndex, reader::TypeDef<'_>, i32)> = Vec::new(); for (idx, arch_bits) in &arch_groups { for ty in idx.types() { all_type_refs.push((idx, ty, *arch_bits)); } } - all_type_refs.sort_by(|a, b| { - (a.1.namespace(), a.1.name()).cmp(&(b.1.namespace(), b.1.name())) - }); + all_type_refs + .sort_by(|a, b| (a.1.namespace(), a.1.name()).cmp(&(b.1.namespace(), b.1.name()))); // Write each type with the appropriate arch annotation. // For types present in all arches, deduplicate to a single arch-neutral TypeDef. diff --git a/crates/tests/libs/metadata/tests/merge.rs b/crates/tests/libs/metadata/tests/merge.rs index cd9739c5111..53e79d2aab6 100644 --- a/crates/tests/libs/metadata/tests/merge.rs +++ b/crates/tests/libs/metadata/tests/merge.rs @@ -58,4 +58,3 @@ fn test_arch() { .write() .unwrap(); } - diff --git a/crates/tests/libs/rdl/tests/error.rs b/crates/tests/libs/rdl/tests/error.rs index 21fb78e4bd7..68798937f25 100644 --- a/crates/tests/libs/rdl/tests/error.rs +++ b/crates/tests/libs/rdl/tests/error.rs @@ -13,15 +13,12 @@ pub fn error_display() { /// Helper: try to compile an RDL string and return the error message if it fails. fn compile_rdl_err(rdl: &str) -> Option { - let out = std::env::temp_dir().join(format!( - "test_arch_err_{}_{}.winmd", - std::process::id(), - { + let out = + std::env::temp_dir().join(format!("test_arch_err_{}_{}.winmd", std::process::id(), { use std::sync::atomic::{AtomicU32, Ordering}; static N: AtomicU32 = AtomicU32::new(0); N.fetch_add(1, Ordering::Relaxed) - } - )); + })); let result = reader() .input_str(rdl) .output(out.to_str().unwrap())