Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions crates/libs/rdl/rdl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 48 additions & 3 deletions crates/libs/rdl/src/reader/interface.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::attribute_ref::AttributeRef;
use super::guid;
use super::*;

Expand Down Expand Up @@ -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<String> = 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::<String, Error>(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(),
));
Expand Down Expand Up @@ -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(&params)?;
}
Expand Down
39 changes: 37 additions & 2 deletions crates/libs/rdl/src/writer/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,35 @@ fn write_method(
item: &metadata::reader::MethodDef,
generics: &[metadata::Type],
) -> Result<TokenStream, Error> {
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)?;
Expand All @@ -199,7 +227,13 @@ fn write_method(
)
.collect::<Result<Vec<_>, 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.
Expand All @@ -214,6 +248,7 @@ fn write_method(

Ok(quote! {
#special_attr
#overload_attr_token
#(#method_attrs)*
fn #name(#(#params),*) #return_type;
})
Expand Down
66 changes: 66 additions & 0 deletions crates/tests/libs/rdl/tests/overload_encoding.rs
Original file line number Diff line number Diff line change
@@ -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
/// <https://github.com/microsoft/windows-rs/issues/4166>.
#[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<String> {
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
}
})
}
13 changes: 13 additions & 0 deletions crates/tests/libs/rdl/tests/roundtrip/overload-method.rdl
Original file line number Diff line number Diff line change
@@ -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;
Comment on lines +4 to +11
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot this .rdl looks right but the .winmd representation is such that the MethodDef name stores the common name while the attribute stores the unique name. So keep this .rdl representation but make sure the metadata is actually encoded correctly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 912ca1a. The WINMD encoding is now correct:

  • MethodDef.Name = common/logical name (e.g. "Method") — taken from the #[overload("...")] arg
  • OverloadAttribute.value = unique vtable name (e.g. "MethodOne") — taken from the fn identifier

Added overload_encoding.rs which directly reads the produced WINMD and asserts this layout.

}
}
Loading