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/interface.rs b/crates/libs/rdl/src/reader/interface.rs index 21236f1b4e2..77de3048559 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,37 @@ 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 +296,34 @@ 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..7915b03913e 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,13 @@ 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 +248,7 @@ fn write_method( Ok(quote! { #special_attr + #overload_attr_token #(#method_attrs)* fn #name(#(#params),*) #return_type; }) 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..3403a14907f --- /dev/null +++ b/crates/tests/libs/rdl/tests/overload_encoding.rs @@ -0,0 +1,66 @@ +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 + } + }) +} 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; + } +}