Skip to main content

windows_rdl/writer/
mod.rs

1mod attribute;
2mod callback;
3mod class;
4mod delegate;
5mod r#enum;
6mod r#fn;
7mod interface;
8mod layout;
9mod r#struct;
10
11use super::*;
12use attribute::*;
13use callback::*;
14use class::*;
15use delegate::*;
16use r#enum::*;
17use r#fn::*;
18use interface::*;
19use layout::*;
20use metadata::AsRow;
21use metadata::HasAttributes;
22use r#struct::*;
23
24#[derive(Default)]
25/// Builder that converts `.winmd` metadata into RDL.
26pub struct Writer {
27    input: Vec<PathBuf>,
28    input_default: bool,
29    input_bytes: Vec<Vec<u8>>,
30    filter: Vec<String>,
31    output: PathBuf,
32    split: bool,
33    partition: Option<HashMap<String, String>>,
34}
35
36impl Writer {
37    /// Creates a new builder with default options.
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Adds an input `.winmd` file or directory.
43    pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
44        self.input.push(input.as_ref().to_path_buf());
45        self
46    }
47
48    /// Adds a `.winmd` file from memory.
49    pub fn input_bytes(&mut self, input: &[u8]) -> &mut Self {
50        self.input_bytes.push(input.to_vec());
51        self
52    }
53
54    /// Adds `.winmd` files from memory.
55    pub fn input_byte_sets<I, B>(&mut self, inputs: I) -> &mut Self
56    where
57        I: IntoIterator<Item = B>,
58        B: AsRef<[u8]>,
59    {
60        for input in inputs {
61            self.input_bytes(input.as_ref());
62        }
63        self
64    }
65
66    /// Adds the default Windows metadata inputs.
67    pub fn input_default(&mut self) -> &mut Self {
68        self.input_default = true;
69        self
70    }
71
72    /// Sets the output `.rdl` file or directory path.
73    pub fn output(&mut self, output: impl AsRef<Path>) -> &mut Self {
74        self.output = output.as_ref().to_path_buf();
75        self
76    }
77
78    /// Adds multiple input `.winmd` files.
79    pub fn inputs<I, S>(&mut self, inputs: I) -> &mut Self
80    where
81        I: IntoIterator<Item = S>,
82        S: AsRef<Path>,
83    {
84        for input in inputs {
85            self.input(input);
86        }
87        self
88    }
89
90    /// Adds multiple filter rules. See [`filter`][Self::filter].
91    pub fn filters<I, S>(&mut self, filters: I) -> &mut Self
92    where
93        I: IntoIterator<Item = S>,
94        S: AsRef<str>,
95    {
96        for filter in filters {
97            self.filter.push(filter.as_ref().to_string());
98        }
99
100        self
101    }
102
103    /// Adds an include/exclude filter rule. Prefix with `!` to exclude.
104    pub fn filter(&mut self, filter: &str) -> &mut Self {
105        self.filter.push(filter.to_string());
106        self
107    }
108
109    /// Writes each namespace to a separate file.
110    pub fn split(&mut self) -> &mut Self {
111        self.split = true;
112        self
113    }
114
115    /// Partitions output into one `<stem>.rdl` per defining header.
116    pub fn partition(&mut self, map: HashMap<String, String>) -> &mut Self {
117        self.partition = Some(map);
118        self
119    }
120
121    /// Converts the inputs and writes the RDL to the configured output.
122    pub fn write(&self) -> Result<(), Error> {
123        if self.output.as_os_str().is_empty() {
124            return Err(Error::new("output is required", "", 0, 0));
125        }
126
127        let mut files = vec![];
128
129        for file_name in &expand_input_files(&self.input, "winmd")? {
130            let source = file_name.to_string_lossy();
131            files.push(
132                metadata::reader::File::read(file_name)
133                    .ok_or_else(|| Error::new("invalid input", &source, 0, 0))?,
134            );
135        }
136
137        if self.input_default {
138            files.extend(
139                [windows_default::WINRT, windows_default::WIN32]
140                    .into_iter()
141                    .map(|bytes| metadata::reader::File::new(bytes.to_vec()).unwrap()),
142            );
143        }
144
145        for bytes in &self.input_bytes {
146            files.push(
147                metadata::reader::File::new(bytes.clone())
148                    .ok_or_else(|| Error::new("invalid input", "<memory>", 0, 0))?,
149            );
150        }
151
152        let index = metadata::reader::Index::new(files);
153        let rules = resolve_filter(&self.filter, &index);
154
155        if let Some(map) = &self.partition {
156            if let Ok(entries) = std::fs::read_dir(&self.output) {
157                for entry in entries.flatten() {
158                    let path = entry.path();
159                    if path
160                        .extension()
161                        .is_some_and(|ext| ext.eq_ignore_ascii_case("rdl"))
162                    {
163                        let _ = std::fs::remove_file(path);
164                    }
165                }
166            }
167
168            let mut layouts: BTreeMap<String, Layout> = BTreeMap::new();
169            for namespace in index.namespaces() {
170                if namespace.is_empty() {
171                    continue;
172                }
173                for (name, item) in index.namespace_items(namespace) {
174                    if !item_included(&rules, namespace, name) {
175                        continue;
176                    }
177                    let Some(stem) = map.get(name) else {
178                        continue;
179                    };
180                    let layout = layouts.entry(stem.clone()).or_default();
181                    for (item_name, tokens) in write_items(namespace, item)? {
182                        layout.insert(namespace, &item_name, item_winrt(item), tokens.to_string());
183                    }
184                }
185            }
186
187            for (stem, layout) in &layouts {
188                let output = layout.to_string();
189                if output.is_empty() {
190                    continue;
191                }
192
193                let mut path = PathBuf::new();
194                path.push(&self.output);
195                path.push(format!("{stem}.rdl"));
196
197                write_to_file(path, formatter::format(&output))?;
198            }
199
200            return Ok(());
201        }
202
203        if self.split {
204            if let Ok(entries) = std::fs::read_dir(&self.output) {
205                for entry in entries.flatten() {
206                    let path = entry.path();
207                    if path
208                        .extension()
209                        .is_some_and(|ext| ext.eq_ignore_ascii_case("rdl"))
210                    {
211                        let _ = std::fs::remove_file(path);
212                    }
213                }
214            }
215
216            for namespace in index.namespaces() {
217                if namespace.is_empty() {
218                    continue;
219                }
220
221                let mut layout = Layout::new();
222
223                for (name, item) in index.namespace_items(namespace) {
224                    if !item_included(&rules, namespace, name) {
225                        continue;
226                    }
227                    for (item_name, tokens) in write_items(namespace, item)? {
228                        layout.insert(namespace, &item_name, item_winrt(item), tokens.to_string());
229                    }
230                }
231
232                let output = layout.to_string();
233
234                if output.is_empty() {
235                    continue;
236                }
237
238                let mut path = PathBuf::new();
239                path.push(&self.output);
240                path.push(format!("{namespace}.rdl"));
241
242                write_to_file(path, formatter::format(&output))?;
243            }
244        } else {
245            let mut layout = Layout::new();
246
247            for namespace in index.namespaces() {
248                for (name, item) in index.namespace_items(namespace) {
249                    if !item_included(&rules, namespace, name) {
250                        continue;
251                    }
252                    for (item_name, tokens) in write_items(namespace, item)? {
253                        layout.insert(namespace, &item_name, item_winrt(item), tokens.to_string());
254                    }
255                }
256            }
257
258            let output = layout.to_string();
259            write_to_file(&self.output, formatter::format(&output))?;
260        }
261
262        Ok(())
263    }
264}
265
266fn namespace_starts_with(namespace: &str, starts_with: &str) -> bool {
267    namespace.starts_with(starts_with)
268        && (namespace.len() == starts_with.len()
269            || namespace.as_bytes().get(starts_with.len()) == Some(&b'.'))
270}
271
272enum FilterRule {
273    Namespace(String),
274    Type(String, String),
275}
276
277/// Resolves filter strings as namespace prefixes, qualified names, or unqualified names.
278fn resolve_filter(filter: &[String], index: &metadata::reader::Index) -> Vec<(FilterRule, bool)> {
279    let mut rules = vec![];
280
281    for f in filter {
282        let (rule_str, include) = if let Some(r) = f.strip_prefix('!') {
283            (r, false)
284        } else {
285            (f.as_str(), true)
286        };
287
288        if index
289            .namespaces()
290            .any(|ns| namespace_starts_with(ns, rule_str))
291        {
292            rules.push((FilterRule::Namespace(rule_str.to_string()), include));
293            continue;
294        }
295
296        if let Some((namespace, name)) = rule_str.rsplit_once('.')
297            && index.get_item(namespace, name).next().is_some()
298        {
299            rules.push((
300                FilterRule::Type(namespace.to_string(), name.to_string()),
301                include,
302            ));
303            continue;
304        }
305
306        let mut found = false;
307        for ns in index.namespaces() {
308            if index.get_item(ns, rule_str).next().is_some() {
309                rules.push((
310                    FilterRule::Type(ns.to_string(), rule_str.to_string()),
311                    include,
312                ));
313                found = true;
314            }
315        }
316        if found {
317            continue;
318        }
319
320        rules.push((FilterRule::Namespace(rule_str.to_string()), include));
321    }
322
323    rules
324}
325
326fn item_included(rules: &[(FilterRule, bool)], namespace: &str, name: &str) -> bool {
327    if rules.is_empty() {
328        return true;
329    }
330
331    let mut matched_include = false;
332
333    for (rule, include) in rules {
334        let matches = match rule {
335            FilterRule::Namespace(prefix) => namespace_starts_with(namespace, prefix),
336            FilterRule::Type(ns, n) => ns == namespace && n == name,
337        };
338
339        if matches {
340            if !include {
341                return false;
342            }
343            matched_include = true;
344        }
345    }
346
347    matched_include
348}
349
350fn item_winrt(item: metadata::reader::Item) -> bool {
351    match item {
352        metadata::reader::Item::Type(item) => item
353            .flags()
354            .contains(metadata::TypeAttributes::WindowsRuntime),
355        _ => false,
356    }
357}
358
359fn write_items(
360    namespace: &str,
361    item: metadata::reader::Item,
362) -> Result<Vec<(String, TokenStream)>, Error> {
363    match item {
364        metadata::reader::Item::Type(ty) => write_type_def_items(namespace, &ty),
365        metadata::reader::Item::Fn(ty) => {
366            Ok(vec![(ty.name().to_string(), write_fn(namespace, &ty)?)])
367        }
368        metadata::reader::Item::Const(ty) => {
369            Ok(vec![(ty.name().to_string(), write_const(namespace, &ty)?)])
370        }
371    }
372}
373
374fn write_type_def_items(
375    namespace: &str,
376    item: &metadata::reader::TypeDef,
377) -> Result<Vec<(String, TokenStream)>, Error> {
378    if item.category() == metadata::reader::TypeCategory::Struct {
379        // Native typedefs round-trip as `type NAME = TYPE;`.
380        if item.attributes().any(|attr| {
381            attr.namespace() == METADATA_NAMESPACE && attr.name() == "NativeTypedefAttribute"
382        }) {
383            let name = write_ident(item.name());
384            let field = item
385                .fields()
386                .next()
387                .ok_or_else(|| writer_err!("typedef `{}` has no field", item.name()))?;
388            let ty = write_type(namespace, &field.ty());
389            let arch_attr = write_arch_attr(item.arches());
390            let tokens = quote! { #arch_attr type #name = #ty; };
391            return Ok(vec![(item.name().to_string(), tokens)]);
392        }
393        write_struct_items(item)
394    } else {
395        let tokens = write_type_def(item)?;
396        if tokens.is_empty() {
397            Ok(vec![])
398        } else {
399            Ok(vec![(item.name().to_string(), tokens)])
400        }
401    }
402}
403
404fn write_const(namespace: &str, item: &metadata::reader::Field) -> Result<TokenStream, Error> {
405    // GUID constants render as inline `0x...` literals, not raw attributes.
406    let is_guid = match item.ty() {
407        metadata::Type::ValueName(tn) => &tn == ("System", "Guid") || tn.name == "GUID",
408        _ => false,
409    };
410    if is_guid && item.find_attribute("GuidAttribute").is_some() {
411        write_const_guid(namespace, item)
412    } else if item.find_attribute("GuidAttribute").is_some() {
413        // Property-key constants split `fmtid` into GuidAttribute and `pid` into Constant.
414        write_const_property_key(namespace, item)
415    } else {
416        write_const_value(namespace, item)
417    }
418}
419
420fn write_const_value(
421    namespace: &str,
422    item: &metadata::reader::Field,
423) -> Result<TokenStream, Error> {
424    let name = write_ident(item.name());
425    let constant = item.constant();
426    let ty = write_type(namespace, &item.ty());
427    let arch_attr = write_arch_attr(item.arches());
428    let custom_attrs = write_custom_attributes_except(
429        item.attributes(),
430        namespace,
431        item.index(),
432        &["SupportedArchitectureAttribute"],
433    )?;
434
435    Ok(if let Some(constant) = constant {
436        let value = write_typed_value(namespace, &item.ty(), &constant.value());
437        quote! {
438            #arch_attr
439            #(#custom_attrs)*
440            const #name: #ty = #value;
441        }
442    } else {
443        quote! {
444            #arch_attr
445            #(#custom_attrs)*
446            const #name: #ty;
447        }
448    })
449}
450
451fn write_const_guid(
452    _namespace: &str,
453    item: &metadata::reader::Field,
454) -> Result<TokenStream, Error> {
455    let name = write_ident(item.name());
456    let arch_attr = write_arch_attr(item.arches());
457    let literal = guid_attribute_literal(item)?;
458    Ok(quote! { #arch_attr const #name: GUID = #literal; })
459}
460
461/// Recombines a property-key constant's `fmtid` and `pid` into RDL form.
462fn write_const_property_key(
463    namespace: &str,
464    item: &metadata::reader::Field,
465) -> Result<TokenStream, Error> {
466    let name = write_ident(item.name());
467    let ty = write_type(namespace, &item.ty());
468    let arch_attr = write_arch_attr(item.arches());
469    let guid = guid_attribute_literal(item)?;
470    let constant = item
471        .constant()
472        .ok_or_else(|| writer_err!("property key constant `{}` has no `pid` value", item.name()))?;
473    let pid = write_value(namespace, &constant.value());
474    Ok(quote! { #arch_attr #[guid(#guid)] const #name: #ty = #pid; })
475}
476
477/// Folds the 11-argument `GuidAttribute` into RDL's u128 GUID literal.
478fn guid_attribute_literal(item: &metadata::reader::Field) -> Result<syn::LitInt, Error> {
479    let attribute = item
480        .find_attribute("GuidAttribute")
481        .ok_or_else(|| writer_err!("GUID constant `{}` has no `GuidAttribute`", item.name()))?;
482
483    let value: u128 = attribute
484        .value()
485        .into_iter()
486        .try_fold(0u128, |acc, (_, val)| match val {
487            metadata::Value::U8(x) => Ok((acc << 8) | x as u128),
488            metadata::Value::U16(x) => Ok((acc << 16) | x as u128),
489            metadata::Value::U32(x) => Ok((acc << 32) | x as u128),
490            metadata::Value::U64(x) => Ok((acc << 64) | x as u128),
491            _ => Err(writer_err!(
492                "unexpected value type in `GuidAttribute` for `{}`",
493                item.name()
494            )),
495        })?;
496
497    let value = format!(
498        "0x{:08x}_{:04x}_{:04x}_{:04x}_{:012x}",
499        (value >> 96) as u32,
500        (value >> 80) as u16,
501        (value >> 64) as u16,
502        (value >> 48) as u16,
503        value as u64 & 0xffffffffffff,
504    );
505
506    Ok(syn::LitInt::new(&value, Span::call_site()))
507}
508
509fn write_params(
510    namespace: &str,
511    method: &metadata::reader::MethodDef,
512    signature_types: Vec<metadata::Type>,
513) -> Result<Vec<TokenStream>, Error> {
514    let params = method
515        .params_by_sequence(signature_types.len())
516        .map_err(|error| {
517            writer_err!(
518                "method `{}` has invalid parameter metadata: {error}",
519                method.name()
520            )
521        })?;
522
523    signature_types
524        .into_iter()
525        .enumerate()
526        .map(|(position, ty)| {
527            let param = params.params()[position];
528            let is_mutable = matches!(ty, metadata::Type::RefMut(_) | metadata::Type::PtrMut(..));
529            let direction = param.map_or_else(
530                || {
531                    if is_mutable {
532                        metadata::reader::ParamDirection::Output
533                    } else {
534                        metadata::reader::ParamDirection::Input
535                    }
536                },
537                |param| param.direction(),
538            );
539            let (effective_in, has_out) = match direction {
540                metadata::reader::ParamDirection::Unspecified
541                | metadata::reader::ParamDirection::Input => (true, false),
542                metadata::reader::ParamDirection::Output => (false, true),
543                metadata::reader::ParamDirection::InputOutput => (true, true),
544            };
545            let in_attr = if effective_in && (has_out || is_mutable) {
546                quote! { #[r#in] }
547            } else {
548                quote! {}
549            };
550            let out_attr = if has_out && (effective_in || !is_mutable) {
551                quote! { #[out] }
552            } else {
553                quote! {}
554            };
555            let opt_attr = if param.is_some_and(|param| param.is_optional()) {
556                quote! { #[opt] }
557            } else {
558                quote! {}
559            };
560            let name = param.map_or_else(
561                || write_ident(&format!("p{position}")),
562                |param| write_ident(param.name()),
563            );
564            let param_attrs = match param {
565                Some(param) => write_custom_attributes_except(
566                    param.attributes(),
567                    namespace,
568                    method.index(),
569                    &[],
570                )?,
571                None => Vec::new(),
572            };
573            let ty = write_type(namespace, &ty);
574            Ok(quote! { #(#param_attrs)* #in_attr #out_attr #opt_attr #name: #ty })
575        })
576        .collect()
577}
578
579fn write_return_type(
580    namespace: &str,
581    method: &metadata::reader::MethodDef,
582    signature: &metadata::Signature,
583) -> Result<TokenStream, Error> {
584    let params = method
585        .params_by_sequence(signature.types.len())
586        .map_err(|error| {
587            writer_err!(
588                "method `{}` has invalid parameter metadata: {error}",
589                method.name()
590            )
591        })?;
592    let return_attrs: Vec<TokenStream> = params
593        .return_param()
594        .map(|p| write_custom_attributes(p.attributes(), namespace, method.index()))
595        .transpose()?
596        .unwrap_or_default();
597
598    Ok(match &signature.return_type {
599        metadata::Type::Void => quote! {},
600        ty => {
601            let ty = write_type(namespace, ty);
602            quote! { -> #(#return_attrs)* #ty }
603        }
604    })
605}
606
607fn write_custom_attributes<'a>(
608    attributes: impl Iterator<Item = windows_metadata::reader::Attribute<'a>>,
609    item_namespace: &str,
610    index: &windows_metadata::reader::Index,
611) -> Result<Vec<TokenStream>, Error> {
612    write_custom_attributes_except(attributes, item_namespace, index, &[])
613}
614
615fn write_custom_attributes_except<'a>(
616    attributes: impl Iterator<Item = windows_metadata::reader::Attribute<'a>>,
617    item_namespace: &str,
618    index: &windows_metadata::reader::Index,
619    exclude: &[&str],
620) -> Result<Vec<TokenStream>, Error> {
621    let mut rendered = attributes
622        .filter(|attr| {
623            !(namespace_starts_with(attr.namespace(), "System")
624                || exclude.contains(&attr.name())
625                || (attr.namespace() == METADATA_NAMESPACE
626                    && attr.name() == "NativeTypedefAttribute"))
627        })
628        .map(|attr| {
629            let attr_ns = attr.namespace();
630            let values = attr.value();
631
632            // Naturalized metadata attributes render as short RDL spellings when possible.
633            let pseudo = if attr_ns == METADATA_NAMESPACE {
634                let arg_names: Vec<String> = values.iter().map(|(n, _)| n.clone()).collect();
635                pseudo_for_metadata(attr.name(), &arg_names)
636            } else {
637                None
638            };
639
640            let name_ts = if let Some(pseudo) = pseudo {
641                write_ident(pseudo.short)
642            } else {
643                let attr_short = attr
644                    .name()
645                    .strip_suffix("Attribute")
646                    .unwrap_or_else(|| attr.name());
647
648                if attr_ns.is_empty() || attr_ns == item_namespace {
649                    write_ident(attr_short)
650                } else {
651                    let mut tokens = TokenStream::new();
652                    for part in attr_ns.split('.') {
653                        let ident = write_ident(part);
654                        tokens = quote! { #tokens #ident :: };
655                    }
656                    let short = write_ident(attr_short);
657                    quote! { #tokens #short }
658                }
659            };
660
661            // Property-bound pseudos emit their single property value positionally.
662            let drop_names = pseudo.and_then(|p| p.prop).is_some();
663            let args: Vec<TokenStream> = values
664                .into_iter()
665                .map(|(name, v)| {
666                    let value_ts = match &v {
667                        metadata::Value::EnumValue(tn, inner) => {
668                            write_enum_value(item_namespace, tn, inner, index)?
669                        }
670                        _ => write_value(item_namespace, &v),
671                    };
672                    let ts = if name.is_empty() || drop_names {
673                        value_ts
674                    } else {
675                        let name_ident = write_ident(&name);
676                        quote! { #name_ident = #value_ts }
677                    };
678                    Ok(ts)
679                })
680                .collect::<Result<Vec<_>, Error>>()?;
681
682            Ok(if args.is_empty() {
683                quote! { #[#name_ts] }
684            } else {
685                quote! { #[#name_ts(#(#args),*)] }
686            })
687        })
688        .collect::<Result<Vec<TokenStream>, Error>>()?;
689
690    // Attribute table order varies between metadata builds; sort rendered text for stable output.
691    rendered.sort_by_key(|ts| ts.to_string());
692    Ok(rendered)
693}
694
695/// Renders enum-valued attribute arguments as variants, decomposing flags when needed.
696fn write_enum_value(
697    namespace: &str,
698    tn: &metadata::TypeName,
699    inner: &metadata::Value,
700    index: &metadata::reader::Index,
701) -> Result<TokenStream, Error> {
702    let inner_i32 = match inner {
703        metadata::Value::I32(n) => *n,
704        _ => return Ok(write_value(namespace, inner)),
705    };
706
707    let mut found_in_index = false;
708    for typedef in index.get(&tn.namespace, &tn.name) {
709        found_in_index = true;
710        if typedef.category() == metadata::reader::TypeCategory::Enum {
711            for field in typedef.fields() {
712                if field.flags().contains(metadata::FieldAttributes::Literal)
713                    && let Some(constant) = field.constant()
714                {
715                    let matches = match constant.value() {
716                        metadata::Value::I32(v) => v == inner_i32,
717                        // Attribute blobs carry enum values as signed integers.
718                        metadata::Value::U32(v) => v == inner_i32 as u32,
719                        _ => false,
720                    };
721                    if matches {
722                        let variant = write_ident(field.name());
723                        return Ok(quote! { #variant });
724                    }
725                }
726            }
727
728            let has_flags = typedef.attributes().any(|attr| {
729                attr.name() == "FlagsAttribute" && attr.ctor().parent().namespace() == "System"
730            });
731
732            if has_flags
733                && let Some(flags_ts) = write_flags_combination(namespace, &typedef, inner_i32)
734            {
735                return Ok(flags_ts);
736            }
737        }
738    }
739
740    if !found_in_index {
741        return Err(writer_err!(
742            "enum type `{}::{}` not found in the metadata index; ensure the winmd file that defines it is included",
743            tn.namespace,
744            tn.name
745        ));
746    }
747
748    Ok(write_value(namespace, inner))
749}
750
751fn write_flags_combination(
752    _namespace: &str,
753    typedef: &metadata::reader::TypeDef,
754    value: i32,
755) -> Option<TokenStream> {
756    let mut fields: Vec<(String, i32)> = typedef
757        .fields()
758        .filter_map(|field| {
759            if !field.flags().contains(metadata::FieldAttributes::Literal) {
760                return None;
761            }
762            let constant = field.constant()?;
763            let v = match constant.value() {
764                metadata::Value::I32(v) => v,
765                metadata::Value::U32(v) => v as i32,
766                _ => return None,
767            };
768            if v == 0 {
769                None
770            } else {
771                Some((field.name().to_string(), v))
772            }
773        })
774        .collect();
775
776    // Prefer composite flags such as `All = 0xFFFFFFFF` over individual bits.
777    fields.sort_by_key(|b| std::cmp::Reverse(b.1 as u32));
778
779    let mut remaining = value;
780    let mut components: Vec<String> = Vec::new();
781
782    for (name, v) in &fields {
783        if remaining == 0 {
784            break;
785        }
786        if (remaining & v) == *v {
787            remaining &= !v;
788            components.push(name.clone());
789        }
790    }
791
792    if remaining != 0 || components.is_empty() {
793        return None;
794    }
795
796    let mut iter = components.iter();
797    let first = write_ident(iter.next().unwrap());
798    let result = iter.fold(first, |acc, name| {
799        let variant = write_ident(name);
800        quote! { #acc | #variant }
801    });
802
803    Some(result)
804}
805
806/// Emits `#[arch(...)]` for a non-zero X86/X64/Arm64 bitmask.
807pub(super) fn write_arch_attr(arches: i32) -> TokenStream {
808    if arches == 0 {
809        return quote! {};
810    }
811
812    let mut parts: Vec<TokenStream> = vec![];
813    if arches & 1 != 0 {
814        parts.push(quote! { X86 });
815    }
816    if arches & 2 != 0 {
817        parts.push(quote! { X64 });
818    }
819    if arches & 4 != 0 {
820        parts.push(quote! { Arm64 });
821    }
822
823    if parts.is_empty() {
824        return quote! {};
825    }
826
827    let value = parts
828        .iter()
829        .skip(1)
830        .fold(parts[0].clone(), |acc, p| quote! { #acc | #p });
831
832    quote! { #[arch(#value)] }
833}
834
835fn write_type_def(item: &metadata::reader::TypeDef) -> Result<TokenStream, Error> {
836    match item.category() {
837        metadata::reader::TypeCategory::Struct => Ok(quote! {}),
838        metadata::reader::TypeCategory::Enum => write_enum(item),
839        metadata::reader::TypeCategory::Interface => write_interface(item),
840        metadata::reader::TypeCategory::Class => write_class(item),
841        metadata::reader::TypeCategory::Delegate => {
842            if item
843                .flags()
844                .contains(metadata::TypeAttributes::WindowsRuntime)
845            {
846                write_delegate(item)
847            } else {
848                write_callback(item)
849            }
850        }
851        metadata::reader::TypeCategory::Attribute => write_attribute(item),
852    }
853}
854
855fn write_type_ref(namespace: &str, item: &metadata::reader::TypeDefOrRef) -> TokenStream {
856    write_type(
857        namespace,
858        &metadata::Type::class_named(item.namespace(), item.name()),
859    )
860}
861
862fn extract_guid_from_attribute(
863    attr: metadata::reader::Attribute,
864) -> Result<(u32, u16, u16, [u8; 8]), Error> {
865    let values: Vec<_> = attr.value().into_iter().map(|(_, v)| v).collect();
866    if values.len() != 11 {
867        return Err(writer_err!(
868            "GuidAttribute must have exactly 11 arguments, got {}",
869            values.len()
870        ));
871    }
872    let d1 = match values[0] {
873        metadata::Value::U32(v) => v,
874        ref v => return Err(writer_err!("GuidAttribute d1: expected U32, got {v:?}")),
875    };
876    let d2 = match values[1] {
877        metadata::Value::U16(v) => v,
878        ref v => return Err(writer_err!("GuidAttribute d2: expected U16, got {v:?}")),
879    };
880    let d3 = match values[2] {
881        metadata::Value::U16(v) => v,
882        ref v => return Err(writer_err!("GuidAttribute d3: expected U16, got {v:?}")),
883    };
884    let mut d4 = [0u8; 8];
885    for i in 0..8 {
886        d4[i] = match values[3 + i] {
887            metadata::Value::U8(v) => v,
888            ref v => return Err(writer_err!("GuidAttribute d4[{i}]: expected U8, got {v:?}")),
889        };
890    }
891    Ok((d1, d2, d3, d4))
892}
893
894enum GuidOutput {
895    Omit,
896    Explicit(u32, u16, u16, [u8; 8]),
897    None,
898}
899
900/// Omits derived GUIDs while preserving explicit or absent GUID state.
901fn guid_output(
902    item: &metadata::reader::TypeDef,
903    methods: &[(&str, &[metadata::Type], &metadata::Type)],
904) -> Result<GuidOutput, Error> {
905    let Some(attr) = item.find_attribute("GuidAttribute") else {
906        return Ok(GuidOutput::None);
907    };
908    let stored = extract_guid_from_attribute(attr)?;
909    let s = reader::guid::build_interface_string(
910        item.namespace(),
911        metadata::trim_tick(item.name()),
912        methods,
913    );
914    let derived = reader::guid::guid_from_interface_string(&s);
915    if stored == derived {
916        Ok(GuidOutput::Omit)
917    } else {
918        Ok(GuidOutput::Explicit(stored.0, stored.1, stored.2, stored.3))
919    }
920}
921
922fn interface_guid_output(
923    item: &metadata::reader::TypeDef,
924    generics: &[metadata::Type],
925) -> Result<GuidOutput, Error> {
926    let sigs: Vec<(String, Vec<metadata::Type>, metadata::Type)> = item
927        .methods()
928        .map(|m| {
929            let sig = m.signature(generics);
930            (m.name().to_string(), sig.types, sig.return_type)
931        })
932        .collect();
933    let methods: Vec<(&str, &[metadata::Type], &metadata::Type)> = sigs
934        .iter()
935        .map(|(n, t, r)| (n.as_str(), t.as_slice(), r))
936        .collect();
937    guid_output(item, &methods)
938}
939
940fn delegate_guid_output(
941    item: &metadata::reader::TypeDef,
942    generics: &[metadata::Type],
943) -> Result<GuidOutput, Error> {
944    let (types, return_type) = item.methods().find(|m| m.name() == "Invoke").map_or_else(
945        || (vec![], metadata::Type::Void),
946        |invoke| {
947            let sig = invoke.signature(generics);
948            (sig.types, sig.return_type)
949        },
950    );
951    guid_output(item, &[("Invoke", types.as_slice(), &return_type)])
952}
953
954/// Reads the raw Win32 callback ABI value.
955fn read_unmanaged_abi(item: &metadata::reader::TypeDef) -> Option<i32> {
956    item.find_attribute("UnmanagedFunctionPointerAttribute")
957        .and_then(|attribute| attribute.value().into_iter().next())
958        .and_then(|(_, v)| {
959            if let metadata::Value::EnumValue(_, value) = v
960                && let metadata::Value::I32(n) = *value
961            {
962                return Some(n);
963            }
964            None
965        })
966}
967
968fn write_generic_params(item: &metadata::reader::TypeDef) -> (Vec<metadata::Type>, TokenStream) {
969    let types: Vec<_> = item
970        .generic_params()
971        .map(|param| metadata::Type::Generic(param.name().to_string(), param.sequence()))
972        .collect();
973    let tokens = if types.is_empty() {
974        quote! {}
975    } else {
976        let names = item.generic_params().map(|param| write_ident(param.name()));
977        quote! { <#(#names),*> }
978    };
979    (types, tokens)
980}