From 1a0e617a8fbf62ffe6f9f3209df8e2fccc53d177 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 02:20:54 +0000 Subject: [PATCH 1/3] Add #[overload] pseudo-attribute for WinRT method overloads in RDL Agent-Logs-Url: https://github.com/microsoft/windows-rs/sessions/98790d2a-1199-4b5c-9110-e2fb5c7512d0 Co-authored-by: kennykerr <9845234+kennykerr@users.noreply.github.com> --- crates/libs/rdl/rdl.md | 27 +++++++++++++++++++ crates/libs/rdl/src/reader/attribute_ref.rs | 20 ++++++++++++++ crates/libs/rdl/src/writer/mod.rs | 9 +++++++ .../rdl/tests/roundtrip/overload-method.rdl | 13 +++++++++ 4 files changed, 69 insertions(+) create mode 100644 crates/tests/libs/rdl/tests/roundtrip/overload-method.rdl diff --git a/crates/libs/rdl/rdl.md b/crates/libs/rdl/rdl.md index e732a0d89a7..f9a2aa16c33 100644 --- a/crates/libs/rdl/rdl.md +++ b/crates/libs/rdl/rdl.md @@ -301,6 +301,33 @@ mod Contoso { } ``` +**Method Overloads (WinRT):** + +WinRT interfaces may contain overloaded methods — multiple methods that share a logical name but have unique WINMD names. The `#[overload("Name")]` pseudo-attribute records the shared logical name (the value of `Windows.Foundation.Metadata.OverloadAttribute` in the binary metadata), while the actual method name is the unique identifier used in the vtable. + +**Syntax:** + +```rust +#[overload("LogicalName")] +fn UniqueName(&self, ...) -> ReturnType; +``` + +**Example:** + +```rust +#[winrt] +mod Contoso { + mod Sprockets { + interface ISprocketFactory { + #[overload("Create")] + fn CreateDefault(&self) -> Sprocket; + #[overload("Create")] + fn CreateWithOptions(&self, options: SprocketOptions) -> Sprocket; + } + } +} +``` + --- #### Delegates diff --git a/crates/libs/rdl/src/reader/attribute_ref.rs b/crates/libs/rdl/src/reader/attribute_ref.rs index c959a8c8fb2..35ade603a3e 100644 --- a/crates/libs/rdl/src/reader/attribute_ref.rs +++ b/crates/libs/rdl/src/reader/attribute_ref.rs @@ -563,6 +563,26 @@ impl Encoder<'_> { continue; } + // `#[overload("Name")]` is a pseudo-attribute that maps to the WinRT metadata + // `Windows.Foundation.Metadata.OverloadAttribute`. + if path.is_ident("overload") { + let name: syn::LitStr = attr.parse_args().map_err(|_| { + self.error( + attr, + "`#[overload]` requires a single string literal argument", + ) + })?; + let attr_ref = AttributeRef { + type_name: metadata::TypeName::named( + "Windows.Foundation.Metadata", + "OverloadAttribute", + ), + args: vec![("".to_string(), metadata::Value::Utf8(name.value()))], + }; + self.encode_named_attribute(has_attribute, &attr_ref); + continue; + } + let attr_ref = self.resolve_attribute_ref(attr)?; self.encode_named_attribute(has_attribute, &attr_ref); } diff --git a/crates/libs/rdl/src/writer/mod.rs b/crates/libs/rdl/src/writer/mod.rs index 4dc53cfb8f4..b9e1d3bbf3b 100644 --- a/crates/libs/rdl/src/writer/mod.rs +++ b/crates/libs/rdl/src/writer/mod.rs @@ -497,6 +497,15 @@ fn write_custom_attributes_except<'a>( return Ok(quote! { #[typedef] }); } + // `OverloadAttribute` is written back as the `#[overload("Name")]` pseudo-attribute. + if attr.namespace() == "Windows.Foundation.Metadata" + && attr.name() == "OverloadAttribute" + { + if let Some((_, metadata::Value::Utf8(name))) = attr.value().first() { + return Ok(quote! { #[overload(#name)] }); + } + } + let attr_ns = attr.namespace(); let attr_short = attr .name() diff --git a/crates/tests/libs/rdl/tests/roundtrip/overload-method.rdl b/crates/tests/libs/rdl/tests/roundtrip/overload-method.rdl new file mode 100644 index 00000000000..0ea5d5b559e --- /dev/null +++ b/crates/tests/libs/rdl/tests/roundtrip/overload-method.rdl @@ -0,0 +1,13 @@ +#[winrt] +mod Test { + interface IFoo { + #[overload("Method")] + fn MethodOne(&self) -> i32; + #[overload("Method")] + fn MethodTwo(&self, a: i32) -> i32; + #[overload("Other")] + fn OtherVariantA(&self, x: f32, y: f32) -> bool; + #[overload("Other")] + fn OtherVariantB(&self, x: f32, y: f32, z: f32) -> bool; + } +} From 912ca1a07293b395fb5dce9035eec7a3211782e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:21:35 +0000 Subject: [PATCH 2/3] Fix #[overload] encoding: MethodDef.Name = common name, OverloadAttribute.value = unique name Agent-Logs-Url: https://github.com/microsoft/windows-rs/sessions/9c771f77-258b-4888-82d6-2b4c3277c389 Co-authored-by: kennykerr <9845234+kennykerr@users.noreply.github.com> --- crates/libs/rdl/src/reader/attribute_ref.rs | 20 ------- crates/libs/rdl/src/reader/interface.rs | 53 ++++++++++++++++-- crates/libs/rdl/src/writer/interface.rs | 37 ++++++++++++- crates/libs/rdl/src/writer/mod.rs | 9 ---- .../tests/libs/rdl/tests/overload_encoding.rs | 54 +++++++++++++++++++ 5 files changed, 139 insertions(+), 34 deletions(-) create mode 100644 crates/tests/libs/rdl/tests/overload_encoding.rs diff --git a/crates/libs/rdl/src/reader/attribute_ref.rs b/crates/libs/rdl/src/reader/attribute_ref.rs index 35ade603a3e..c959a8c8fb2 100644 --- a/crates/libs/rdl/src/reader/attribute_ref.rs +++ b/crates/libs/rdl/src/reader/attribute_ref.rs @@ -563,26 +563,6 @@ impl Encoder<'_> { continue; } - // `#[overload("Name")]` is a pseudo-attribute that maps to the WinRT metadata - // `Windows.Foundation.Metadata.OverloadAttribute`. - if path.is_ident("overload") { - let name: syn::LitStr = attr.parse_args().map_err(|_| { - self.error( - attr, - "`#[overload]` requires a single string literal argument", - ) - })?; - let attr_ref = AttributeRef { - type_name: metadata::TypeName::named( - "Windows.Foundation.Metadata", - "OverloadAttribute", - ), - args: vec![("".to_string(), metadata::Value::Utf8(name.value()))], - }; - self.encode_named_attribute(has_attribute, &attr_ref); - continue; - } - let attr_ref = self.resolve_attribute_ref(attr)?; self.encode_named_attribute(has_attribute, &attr_ref); } diff --git a/crates/libs/rdl/src/reader/interface.rs b/crates/libs/rdl/src/reader/interface.rs index 21236f1b4e2..a54d83e483f 100644 --- a/crates/libs/rdl/src/reader/interface.rs +++ b/crates/libs/rdl/src/reader/interface.rs @@ -1,3 +1,4 @@ +use super::attribute_ref::AttributeRef; use super::guid; use super::*; @@ -236,9 +237,36 @@ impl Encoder<'_> { } } + // Detect `#[overload("CommonName")]`. + // + // In WINMD the MethodDef.Name stores the *common* logical name and + // OverloadAttribute.value stores the *unique* vtable name. RDL inverts + // this so that the `fn` identifier is the unique name and + // `#[overload("common")]` carries the common name. + let overload_common_name: Option = method + .attrs + .iter() + .find(|a| a.path().is_ident("overload")) + .map(|a| { + let lit: syn::LitStr = a.parse_args().map_err(|_| { + self.error( + a, + "`#[overload]` requires a single string literal argument", + ) + })?; + Ok::(lit.value()) + }) + .transpose()?; + + // MethodDef.Name: common logical name when overloaded, fn ident otherwise. + let sig_ident = method.sig.ident.to_string(); + let method_def_name = + overload_common_name.as_deref().unwrap_or(sig_ident.as_str()); + if !already_has_guid { + // GUID derivation uses MethodDef.Name (the common/logical name). method_signatures.push(( - method.sig.ident.to_string(), + method_def_name.to_string(), types.clone(), return_type.clone(), )); @@ -267,18 +295,37 @@ impl Encoder<'_> { } let method_def = self.output.MethodDef( - &method.sig.ident.to_string(), + method_def_name, &signature, flags, Default::default(), ); + // Skip `#[overload]` in encode_attrs — it is handled below. self.encode_attrs( metadata::writer::HasAttribute::MethodDef(method_def), &method.attrs, - &["special"], + &["special", "overload"], )?; + // If overloaded, emit OverloadAttribute with the fn ident (unique name). + if overload_common_name.is_some() { + let attr_ref = AttributeRef { + type_name: metadata::TypeName::named( + "Windows.Foundation.Metadata", + "OverloadAttribute", + ), + args: vec![( + String::new(), + metadata::Value::Utf8(sig_ident.clone()), + )], + }; + self.encode_named_attribute( + metadata::writer::HasAttribute::MethodDef(method_def), + &attr_ref, + ); + } + self.encode_return_attrs(&method.return_attrs)?; self.encode_params(¶ms)?; } diff --git a/crates/libs/rdl/src/writer/interface.rs b/crates/libs/rdl/src/writer/interface.rs index 95fa6497c08..89004051fdc 100644 --- a/crates/libs/rdl/src/writer/interface.rs +++ b/crates/libs/rdl/src/writer/interface.rs @@ -187,7 +187,35 @@ fn write_method( item: &metadata::reader::MethodDef, generics: &[metadata::Type], ) -> Result { - let name = write_ident(item.name()); + // In WINMD, OverloadAttribute.value is the *unique* vtable name and + // MethodDef.Name is the *common* logical name. In RDL we invert this so + // that the `fn` name is the unique name and `#[overload("common")]` carries + // the common name — a more natural reading. + // + // OverloadAttribute has exactly one positional constructor argument: the + // unique method name (a string). `next()` extracts that single value. + let overload_unique_name = item + .find_attribute("OverloadAttribute") + .and_then(|attr| attr.value().into_iter().next()) + .and_then(|(_, v)| { + if let metadata::Value::Utf8(s) = v { + Some(s) + } else { + None + } + }); + + let (fn_name_str, overload_attr_token) = match &overload_unique_name { + Some(unique) => { + // fn name = unique name from OverloadAttribute + // #[overload("common")] = MethodDef.Name (the common logical name) + let common = item.name(); + (unique.as_str(), quote! { #[overload(#common)] }) + } + None => (item.name(), quote! {}), + }; + + let name = write_ident(fn_name_str); let signature = item.signature(generics); let return_type = write_return_type(namespace, item, &signature)?; @@ -199,7 +227,11 @@ fn write_method( ) .collect::, Error>>()?; - let method_attrs = write_custom_attributes(item.attributes(), namespace, item.index())?; + // Exclude OverloadAttribute — it is represented by the #[overload] token above. + let method_attrs = + write_custom_attributes_except(item.attributes(), namespace, item.index(), &[ + "OverloadAttribute", + ])?; // Emit the built-in `#[special]` pseudo-attribute when SpecialName is set, // preserving properties and events on round-trip. @@ -214,6 +246,7 @@ fn write_method( Ok(quote! { #special_attr + #overload_attr_token #(#method_attrs)* fn #name(#(#params),*) #return_type; }) diff --git a/crates/libs/rdl/src/writer/mod.rs b/crates/libs/rdl/src/writer/mod.rs index b9e1d3bbf3b..4dc53cfb8f4 100644 --- a/crates/libs/rdl/src/writer/mod.rs +++ b/crates/libs/rdl/src/writer/mod.rs @@ -497,15 +497,6 @@ fn write_custom_attributes_except<'a>( return Ok(quote! { #[typedef] }); } - // `OverloadAttribute` is written back as the `#[overload("Name")]` pseudo-attribute. - if attr.namespace() == "Windows.Foundation.Metadata" - && attr.name() == "OverloadAttribute" - { - if let Some((_, metadata::Value::Utf8(name))) = attr.value().first() { - return Ok(quote! { #[overload(#name)] }); - } - } - let attr_ns = attr.namespace(); let attr_short = attr .name() diff --git a/crates/tests/libs/rdl/tests/overload_encoding.rs b/crates/tests/libs/rdl/tests/overload_encoding.rs new file mode 100644 index 00000000000..12277a7a296 --- /dev/null +++ b/crates/tests/libs/rdl/tests/overload_encoding.rs @@ -0,0 +1,54 @@ +use windows_metadata::reader::{HasAttributes, TypeIndex}; +use windows_metadata::Value; +use windows_rdl::*; + +/// Verify that `#[overload("Common")] fn Unique(...)` is encoded into WINMD with: +/// - MethodDef.Name = "Common" (the logical/shared name) +/// - OverloadAttribute.value = "Unique" (the vtable-unique name) +/// +/// This matches the WinRT metadata layout described in +/// . +#[test] +fn overload_encoding() { + reader() + .input_str( + r#" +#[winrt] +mod Test { + interface IFoo { + #[overload("Method")] + fn MethodOne(&self) -> i32; + #[overload("Method")] + fn MethodTwo(&self, a: i32) -> i32; + } +} +"#, + ) + .output("tests/overload_encoding.winmd") + .write() + .unwrap(); + + let index = TypeIndex::read("tests/overload_encoding.winmd").unwrap(); + let ifoo = index.expect("Test", "IFoo"); + + let methods: Vec<_> = ifoo.methods().collect(); + assert_eq!(methods.len(), 2); + + // MethodDef.Name = "Method" (common/logical name) + // OverloadAttribute.value = "MethodOne" / "MethodTwo" (unique vtable name) + assert_eq!(methods[0].name(), "Method"); + assert_eq!(overload_unique_name(&methods[0]).as_deref(), Some("MethodOne")); + + assert_eq!(methods[1].name(), "Method"); + assert_eq!(overload_unique_name(&methods[1]).as_deref(), Some("MethodTwo")); +} + +/// Returns the unique vtable name stored in `OverloadAttribute.value`, if present. +fn overload_unique_name(method: &windows_metadata::reader::MethodDef<'_>) -> Option { + method + .find_attribute("OverloadAttribute")? + .value() + .into_iter() + .find(|(name, _)| name.is_empty()) + .and_then(|(_, v)| if let Value::Utf8(s) = v { Some(s) } else { None }) +} From 870a4da6b0631a3014348dbe541aa50e4f81c00e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:51:48 +0000 Subject: [PATCH 3/3] cargo fmt Agent-Logs-Url: https://github.com/microsoft/windows-rs/sessions/a637526a-a754-4904-b3a3-95c59e4129b6 Co-authored-by: kennykerr <9845234+kennykerr@users.noreply.github.com> --- crates/libs/rdl/src/reader/interface.rs | 10 ++++------ crates/libs/rdl/src/writer/interface.rs | 10 ++++++---- .../tests/libs/rdl/tests/overload_encoding.rs | 18 +++++++++++++++--- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/crates/libs/rdl/src/reader/interface.rs b/crates/libs/rdl/src/reader/interface.rs index a54d83e483f..77de3048559 100644 --- a/crates/libs/rdl/src/reader/interface.rs +++ b/crates/libs/rdl/src/reader/interface.rs @@ -260,8 +260,9 @@ impl Encoder<'_> { // MethodDef.Name: common logical name when overloaded, fn ident otherwise. let sig_ident = method.sig.ident.to_string(); - let method_def_name = - overload_common_name.as_deref().unwrap_or(sig_ident.as_str()); + let method_def_name = overload_common_name + .as_deref() + .unwrap_or(sig_ident.as_str()); if !already_has_guid { // GUID derivation uses MethodDef.Name (the common/logical name). @@ -315,10 +316,7 @@ impl Encoder<'_> { "Windows.Foundation.Metadata", "OverloadAttribute", ), - args: vec![( - String::new(), - metadata::Value::Utf8(sig_ident.clone()), - )], + args: vec![(String::new(), metadata::Value::Utf8(sig_ident.clone()))], }; self.encode_named_attribute( metadata::writer::HasAttribute::MethodDef(method_def), diff --git a/crates/libs/rdl/src/writer/interface.rs b/crates/libs/rdl/src/writer/interface.rs index 89004051fdc..7915b03913e 100644 --- a/crates/libs/rdl/src/writer/interface.rs +++ b/crates/libs/rdl/src/writer/interface.rs @@ -228,10 +228,12 @@ fn write_method( .collect::, Error>>()?; // Exclude OverloadAttribute — it is represented by the #[overload] token above. - let method_attrs = - write_custom_attributes_except(item.attributes(), namespace, item.index(), &[ - "OverloadAttribute", - ])?; + let method_attrs = write_custom_attributes_except( + item.attributes(), + namespace, + item.index(), + &["OverloadAttribute"], + )?; // Emit the built-in `#[special]` pseudo-attribute when SpecialName is set, // preserving properties and events on round-trip. diff --git a/crates/tests/libs/rdl/tests/overload_encoding.rs b/crates/tests/libs/rdl/tests/overload_encoding.rs index 12277a7a296..3403a14907f 100644 --- a/crates/tests/libs/rdl/tests/overload_encoding.rs +++ b/crates/tests/libs/rdl/tests/overload_encoding.rs @@ -37,10 +37,16 @@ mod Test { // MethodDef.Name = "Method" (common/logical name) // OverloadAttribute.value = "MethodOne" / "MethodTwo" (unique vtable name) assert_eq!(methods[0].name(), "Method"); - assert_eq!(overload_unique_name(&methods[0]).as_deref(), Some("MethodOne")); + assert_eq!( + overload_unique_name(&methods[0]).as_deref(), + Some("MethodOne") + ); assert_eq!(methods[1].name(), "Method"); - assert_eq!(overload_unique_name(&methods[1]).as_deref(), Some("MethodTwo")); + assert_eq!( + overload_unique_name(&methods[1]).as_deref(), + Some("MethodTwo") + ); } /// Returns the unique vtable name stored in `OverloadAttribute.value`, if present. @@ -50,5 +56,11 @@ fn overload_unique_name(method: &windows_metadata::reader::MethodDef<'_>) -> Opt .value() .into_iter() .find(|(name, _)| name.is_empty()) - .and_then(|(_, v)| if let Value::Utf8(s) = v { Some(s) } else { None }) + .and_then(|(_, v)| { + if let Value::Utf8(s) = v { + Some(s) + } else { + None + } + }) }