Skip to content
Draft
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
45 changes: 45 additions & 0 deletions crates/libs/rdl/rdl.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,51 @@ union SprocketHandle {

---

#### Nested Types

Some Windows metadata types contain anonymous inner structs or unions (nested types). The `#[nested(OuterTypeName)]` pseudo-attribute marks a struct or union as a nested type within the named enclosing type, enabling faithful roundtripping of nested types from Windows metadata.

**Rules:**
- The outer type must be declared **before** the nested type in the file (in alphabetical name order, as the RDL writer produces).
- `#[nested]` can be combined with `#[packed(N)]` and other attributes.
- Multiple levels of nesting are supported by chaining: `A` contains `A_0`, which contains `A_0_0`, and so on.

**Syntax:**

```rust
struct OuterName {
AnonymousField: InnerName,
}
#[nested(OuterName)]
union InnerName {
FieldA: u32,
FieldB: u16,
}
```

**Example:**

```rust
#[win32]
mod Contoso {
mod Sprockets {
struct SprocketBuffer {
size: u32,
Anonymous: SprocketBuffer_0,
}
#[nested(SprocketBuffer)]
union SprocketBuffer_0 {
data: u32,
raw: [u8; 4],
}
}
}
```

The `#[nested]` attribute is a pseudo-attribute: it is not stored as a custom metadata attribute. Instead it causes the RDL reader to set the `NestedPublic` flag on the inner TypeDef and emit a `NestedClass` metadata record linking the inner type to the outer type.

---

#### Interfaces

Interfaces define contracts for method implementations.
Expand Down
26 changes: 26 additions & 0 deletions crates/libs/rdl/src/reader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use r#const::*;
use r#enum::*;
use r#fn::*;
use r#struct::*;
use std::collections::HashMap;
use union::*;
use windows_metadata as metadata;

Expand Down Expand Up @@ -113,6 +114,8 @@ impl Reader {
let mut output = metadata::writer::File::new(assembly_name);
output.set_reference(reference);

let mut typedef_ids: HashMap<String, metadata::writer::TypeDef> = HashMap::new();

for (namespace, members) in &index.namespaces {
for variants in members.types.values() {
for (file, item) in variants {
Expand All @@ -124,6 +127,7 @@ impl Reader {
namespace,
name: &name,
generics: vec![],
typedef_ids: &mut typedef_ids,
};
match item {
Item::Attribute(ty) => encoder.encode_attribute(ty),
Expand Down Expand Up @@ -166,6 +170,7 @@ impl Reader {
namespace,
name,
generics: vec![],
typedef_ids: &mut typedef_ids,
}
.encode_fn(ty)?;
}
Expand All @@ -183,6 +188,7 @@ impl Reader {
namespace,
name,
generics: vec![],
typedef_ids: &mut typedef_ids,
}
.encode_const(ty)?;
}
Expand Down Expand Up @@ -385,6 +391,7 @@ struct Encoder<'a> {
namespace: &'a str,
name: &'a str,
generics: Vec<String>,
typedef_ids: &'a mut HashMap<String, metadata::writer::TypeDef>,
}

impl Encoder<'_> {
Expand Down Expand Up @@ -421,6 +428,25 @@ impl Encoder<'_> {
Ok(None)
}

/// Parse an optional `#[nested(TypeName)]` attribute from `attrs`. Returns
/// `Some(ident)` with the outer type's name if the attribute is present and
/// well-formed, `None` if absent, or an error if the attribute is malformed.
fn read_nested(&self, attrs: &[syn::Attribute]) -> Result<Option<syn::Ident>, Error> {
for attr in attrs {
if !attr.path().is_ident("nested") {
continue;
}

let Ok(ident) = attr.parse_args::<syn::Ident>() else {
return self.err(attr, "`nested` attribute requires a type name");
};

return Ok(Some(ident));
}

Ok(None)
}

fn encode_type(&self, ty: &syn::Type) -> Result<metadata::Type, Error> {
match ty {
syn::Type::Path(ty) => self.encode_type_path(ty),
Expand Down
48 changes: 43 additions & 5 deletions crates/libs/rdl/src/reader/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,41 @@ impl syn::parse::Parse for Struct {

impl Encoder<'_> {
pub fn encode_struct(&mut self, item: &Struct) -> Result<(), Error> {
let type_def =
self.encode_struct_or_union(&item.name.to_string(), item.winrt, false, &item.fields)?;
let nested_in = self.read_nested(&item.attrs)?;
let type_def = self.encode_struct_or_union(
&item.name.to_string(),
item.winrt,
false,
&item.fields,
nested_in.as_ref().map(|id| id.to_string()).as_deref(),
)?;

if let Some(packing_size) = self.read_packed(&item.attrs)? {
self.output.ClassLayout(type_def, packing_size, 0);
}

if let Some(outer_ident) = &nested_in {
let outer_name = outer_ident.to_string();
match self.typedef_ids.get(&outer_name).copied() {
Some(outer_id) => self.output.NestedClass(type_def, outer_id),
None => {
return self.err(
outer_ident,
&format!(
"`nested` outer type `{outer_name}` not found; \
ensure the outer type appears before the nested type"
),
);
}
}
}

self.typedef_ids.insert(item.name.to_string(), type_def);

self.encode_attrs(
metadata::writer::HasAttribute::TypeDef(type_def),
&item.attrs,
&["packed"],
&["packed", "nested"],
)
}

Expand All @@ -55,6 +79,7 @@ impl Encoder<'_> {
winrt: bool,
is_union: bool,
fields: &[Field],
nested_in: Option<&str>,
) -> Result<metadata::writer::TypeDef, Error> {
let value_type = self.output.TypeRef("System", "ValueType");

Expand All @@ -64,17 +89,30 @@ impl Encoder<'_> {
metadata::TypeAttributes::SequentialLayout
};

let visibility_flag = if nested_in.is_some() {
metadata::TypeAttributes::NestedPublic
} else {
metadata::TypeAttributes::Public
};

let flags = layout_flag
| metadata::TypeAttributes::Sealed
| metadata::TypeAttributes::Public
| visibility_flag
| if winrt {
metadata::TypeAttributes::WindowsRuntime
} else {
metadata::TypeAttributes::default()
};

// Nested types are stored with an empty namespace in ECMA-335 metadata.
let namespace = if nested_in.is_some() {
""
} else {
self.namespace
};

let type_def = self.output.TypeDef(
self.namespace,
namespace,
item_name,
metadata::writer::TypeDefOrRef::TypeRef(value_type),
flags,
Expand Down
30 changes: 27 additions & 3 deletions crates/libs/rdl/src/reader/union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,41 @@ impl syn::parse::Parse for Union {

impl Encoder<'_> {
pub fn encode_union(&mut self, item: &Union) -> Result<(), Error> {
let type_def =
self.encode_struct_or_union(&item.name.to_string(), false, true, &item.fields)?;
let nested_in = self.read_nested(&item.attrs)?;
let type_def = self.encode_struct_or_union(
&item.name.to_string(),
false,
true,
&item.fields,
nested_in.as_ref().map(|id| id.to_string()).as_deref(),
)?;

if let Some(packing_size) = self.read_packed(&item.attrs)? {
self.output.ClassLayout(type_def, packing_size, 0);
}

if let Some(outer_ident) = &nested_in {
let outer_name = outer_ident.to_string();
match self.typedef_ids.get(&outer_name).copied() {
Some(outer_id) => self.output.NestedClass(type_def, outer_id),
None => {
return self.err(
outer_ident,
&format!(
"`nested` outer type `{outer_name}` not found; \
ensure the outer type appears before the nested type"
),
);
}
}
}

self.typedef_ids.insert(item.name.to_string(), type_def);

self.encode_attrs(
metadata::writer::HasAttribute::TypeDef(type_def),
&item.attrs,
&["packed"],
&["packed", "nested"],
)
}
}
3 changes: 2 additions & 1 deletion crates/libs/rdl/src/writer/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ fn collect_nested(
// so we don't emit it twice when the nested type already has one).
let arch_attr = write_arch_attr(effective_arches);
let packed_attr = write_packed_attr_value(effective_packing);
let outer_ident = write_ident(outer_flat_name);
let custom_attrs = write_custom_attributes_except(
nested.attributes(),
namespace,
Expand All @@ -141,7 +142,7 @@ fn collect_nested(

output.push((
flat_name,
quote! { #packed_attr #arch_attr #(#custom_attrs)* #keyword #name_ident { #(#fields)* } },
quote! { #packed_attr #arch_attr #[nested(#outer_ident)] #(#custom_attrs)* #keyword #name_ident { #(#fields)* } },
));
}

Expand Down
5 changes: 5 additions & 0 deletions crates/tests/libs/rdl/tests/nested-arches.rdl
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,26 @@ mod Windows {
Anonymous: SLIST_HEADER_0,
}
#[Windows::Win32::Foundation::Metadata::SupportedArchitecture(Arm64)]
#[nested(SLIST_HEADER)]
struct SLIST_HEADER_0 {
Alignment: u64,
Region: u64,
}
#[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X64)]
#[nested(SLIST_HEADER)]
struct SLIST_HEADER_0 {
Alignment: u64,
Region: u64,
}
#[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X86)]
#[nested(SLIST_HEADER)]
struct SLIST_HEADER_0 {
Next: SINGLE_LIST_ENTRY,
Depth: u16,
CpuId: u16,
}
#[Windows::Win32::Foundation::Metadata::SupportedArchitecture(Arm64)]
#[nested(SLIST_HEADER)]
struct SLIST_HEADER_1 {
#[Windows::Win32::Foundation::Metadata::NativeBitfield("Depth", 0, 16)]
#[Windows::Win32::Foundation::Metadata::NativeBitfield("Sequence", 16, 48)]
Expand All @@ -44,6 +48,7 @@ mod Windows {
_bitfield2: u64,
}
#[Windows::Win32::Foundation::Metadata::SupportedArchitecture(X64)]
#[nested(SLIST_HEADER)]
struct SLIST_HEADER_1 {
#[Windows::Win32::Foundation::Metadata::NativeBitfield("Depth", 0, 16)]
#[Windows::Win32::Foundation::Metadata::NativeBitfield("Sequence", 16, 48)]
Expand Down
1 change: 1 addition & 0 deletions crates/tests/libs/rdl/tests/nested-packing.rdl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod Windows {
Anonymous: BTH_INFO_RSP_0,
}
#[packed(1)]
#[nested(BTH_INFO_RSP)]
union BTH_INFO_RSP_0 {
connectionlessMTU: u16,
data: [u8; 44],
Expand Down
39 changes: 39 additions & 0 deletions crates/tests/libs/rdl/tests/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -872,3 +872,42 @@ mod Test {
"#,
)
}

#[test]
#[should_panic(expected = "error: `nested` attribute requires a type name\n --> .rdl:4:5")]
fn nested_no_args_errors() {
should_panic(
r#"
#[win32]
mod Test {
#[nested]
union Foo {
a: u32,
}
}
"#,
)
}

#[test]
#[should_panic(
expected = "error: `nested` outer type `OuterStruct` not found; ensure the outer type appears before the nested type\n --> .rdl:4:14"
)]
fn nested_outer_not_found_errors() {
// "AnonymousUnion" sorts before "OuterStruct", so the inner type is encoded
// before the outer — this should produce a clear error.
should_panic(
r#"
#[win32]
mod Test {
#[nested(OuterStruct)]
union AnonymousUnion {
a: u32,
}
struct OuterStruct {
Anonymous: AnonymousUnion,
}
}
"#,
)
}
17 changes: 17 additions & 0 deletions crates/tests/libs/rdl/tests/roundtrip/nested-flat.rdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#[win32]
mod Test {
struct OUTER {
Anonymous: OUTER_0,
value: u32,
}
#[nested(OUTER)]
union OUTER_0 {
a: OUTER_0_0,
b: u16,
}
#[nested(OUTER_0)]
struct OUTER_0_0 {
x: u32,
y: u32,
}
}
Loading