Skip to main content

windows_metadata/merge/
mod.rs

1use super::*;
2use std::path::{Path, PathBuf};
3
4mod remap;
5pub use remap::Remapper;
6
7/// An error encountered while reading, combining, or writing metadata.
8pub struct Error(String);
9
10impl Error {
11    fn new(message: impl Into<String>) -> Self {
12        Self(message.into())
13    }
14}
15
16impl std::error::Error for Error {}
17
18impl std::fmt::Debug for Error {
19    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
20        std::fmt::Display::fmt(self, f)
21    }
22}
23
24impl std::fmt::Display for Error {
25    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26        write!(f, "\nerror: {}", self.0)
27    }
28}
29
30/// A builder for combining winmd files.
31#[derive(Default)]
32pub struct Merger {
33    input: Vec<PathBuf>,
34    /// `(path, arch_bits)` where bits are 1=X86, 2=X64, 4=Arm64.
35    arch_inputs: Vec<(PathBuf, i32)>,
36    output: PathBuf,
37    union_enums: bool,
38}
39
40impl Merger {
41    /// Creates an empty merge configuration.
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Adds an input winmd file.
47    pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
48        self.input.push(input.as_ref().to_path_buf());
49        self
50    }
51
52    /// Adds input winmd files.
53    pub fn inputs<I, S>(&mut self, inputs: I) -> &mut Self
54    where
55        I: IntoIterator<Item = S>,
56        S: AsRef<Path>,
57    {
58        for input in inputs {
59            self.input(input);
60        }
61        self
62    }
63
64    /// Adds an architecture-tagged input winmd file.
65    pub fn arch_input(&mut self, path: impl AsRef<Path>, arch: i32) -> &mut Self {
66        self.arch_inputs.push((path.as_ref().to_path_buf(), arch));
67        self
68    }
69
70    /// Unions same-named enums across inputs into a single enum, deduplicating members.
71    ///
72    /// Without this, two inputs that each define an enum with the same namespace and name
73    /// produce two `TypeDef` rows. `tool_win32` uses this to reconcile a value type an `um`
74    /// header truncates (for example `FILE_INFORMATION_CLASS`) with the complete `km`
75    /// definition, yielding one enum carrying every member.
76    pub fn union_enums(&mut self) -> &mut Self {
77        self.union_enums = true;
78        self
79    }
80
81    /// Sets the output winmd path.
82    pub fn output(&mut self, output: impl AsRef<Path>) -> &mut Self {
83        self.output = output.as_ref().to_path_buf();
84        self
85    }
86
87    /// Combines the configured inputs and writes the output winmd.
88    ///
89    /// Returns an error when the output is missing, an input cannot be read, or the metadata
90    /// cannot be merged.
91    pub fn merge(&self) -> Result<(), Error> {
92        if self.output.as_os_str().is_empty() {
93            return Err(Error::new("output is required"));
94        }
95
96        let name = self
97            .output
98            .file_stem()
99            .and_then(|s| s.to_str())
100            .ok_or_else(|| {
101                Error::new(format!("invalid output path `{}`", self.output.display()))
102            })?;
103
104        let files = read_inputs(&self.input)?;
105        let index = reader::Index::new(files);
106
107        let mut file = writer::File::new(name);
108
109        if self.union_enums {
110            let mut groups: BTreeMap<(String, String), Vec<reader::TypeDef<'_>>> = BTreeMap::new();
111            for ty in index.types() {
112                groups
113                    .entry((ty.namespace().to_string(), ty.name().to_string()))
114                    .or_default()
115                    .push(ty);
116            }
117
118            for copies in groups.values() {
119                // The per-namespace `Apis` container is defined by both the `um` and `km` inputs;
120                // union its fields and methods so both function/constant surfaces survive. Each
121                // member keeps its own arch tag, so no arch sub-grouping applies here.
122                if copies
123                    .iter()
124                    .all(|ty| ty.category() == reader::TypeCategory::Class)
125                {
126                    write_class_union(&mut file, &index, copies);
127                    continue;
128                }
129
130                // Sub-group by architecture so arch-specific variants (an enum whose members
131                // differ per arch, like `INTERLOCKED_RESULT`) are never unioned across arches;
132                // each arch keeps its own copy, tagged as the inputs had it.
133                let mut by_arch: BTreeMap<i32, Vec<reader::TypeDef<'_>>> = BTreeMap::new();
134                for copy in copies {
135                    by_arch
136                        .entry(type_arch_bits(*copy))
137                        .or_default()
138                        .push(*copy);
139                }
140
141                for arch_copies in by_arch.values() {
142                    if arch_copies
143                        .iter()
144                        .all(|ty| ty.category() == reader::TypeCategory::Enum)
145                    {
146                        write_enum_union(&mut file, arch_copies)?;
147                    } else {
148                        // Remaining non-enum collisions are not expected within one arch (the `km`
149                        // scrape excludes reference types other than extended enums); keep the
150                        // first deterministically.
151                        write_type(&mut file, &index, arch_copies[0], None, None);
152                    }
153                }
154            }
155        } else {
156            let mut types: Vec<reader::TypeDef<'_>> = index.types().collect();
157            types.sort_by(|a, b| (a.namespace(), a.name()).cmp(&(b.namespace(), b.name())));
158
159            for ty in types {
160                write_type(&mut file, &index, ty, None, None);
161            }
162        }
163
164        if !self.arch_inputs.is_empty() {
165            let all_arches_mask: i32 = self.arch_inputs.iter().fold(0, |acc, (_, arch)| acc | arch);
166
167            let mut arch_groups: Vec<(reader::Index, i32)> =
168                Vec::with_capacity(self.arch_inputs.len());
169            for (path, arch_bits) in &self.arch_inputs {
170                let files = read_inputs(std::slice::from_ref(path))?;
171                arch_groups.push((reader::Index::new(files), *arch_bits));
172            }
173
174            let mut groups: BTreeMap<
175                (String, String),
176                Vec<(&reader::Index, reader::TypeDef<'_>, i32)>,
177            > = BTreeMap::new();
178            for (idx, arch_bits) in &arch_groups {
179                for ty in idx.types() {
180                    groups
181                        .entry((ty.namespace().to_string(), ty.name().to_string()))
182                        .or_default()
183                        .push((idx, ty, *arch_bits));
184                }
185            }
186
187            for copies in groups.values() {
188                let (idx, ty, _) = copies[0];
189                if ty.category() == reader::TypeCategory::Class {
190                    // Apis members can diverge by arch; union them instead of taking one copy.
191                    write_type_arch_merged(&mut file, idx, ty, copies, all_arches_mask);
192                } else if let Some(signature) = merge_native_sized_callback(copies) {
193                    let bits = copies.iter().fold(0, |acc, (_, _, bits)| acc | bits);
194                    let arch = if bits == all_arches_mask { 0 } else { bits };
195                    write_type_with_signature(
196                        &mut file,
197                        idx,
198                        ty,
199                        None,
200                        Some(arch),
201                        Some(&signature),
202                    );
203                } else {
204                    // Split value types by shape so arch-specific layouts are not lost.
205                    let mut by_sig: Vec<(String, &reader::Index, reader::TypeDef, i32)> = vec![];
206                    for (cidx, c, bits) in copies {
207                        let sig = type_sig(cidx, *c);
208                        if let Some(entry) = by_sig.iter_mut().find(|(s, ..)| *s == sig) {
209                            entry.3 |= *bits;
210                        } else {
211                            by_sig.push((sig, cidx, *c, *bits));
212                        }
213                    }
214                    for (_, cidx, c, bits) in &by_sig {
215                        let arch = if *bits == all_arches_mask { 0 } else { *bits };
216                        write_type(&mut file, cidx, *c, None, Some(arch));
217                    }
218                }
219            }
220        }
221
222        let bytes = file.into_stream();
223        std::fs::write(&self.output, bytes)
224            .map_err(|e| Error::new(format!("failed to write `{}`: {e}", self.output.display())))
225    }
226}
227
228fn read_inputs(inputs: &[PathBuf]) -> Result<Vec<reader::File>, Error> {
229    let mut result = vec![];
230
231    for input in inputs {
232        if input.is_dir() {
233            let prev_len = result.len();
234
235            let entries = std::fs::read_dir(input).map_err(|e| {
236                Error::new(format!(
237                    "failed to read directory `{}`: {e}",
238                    input.display()
239                ))
240            })?;
241
242            for entry in entries.flatten() {
243                let entry_path = entry.path();
244
245                if entry_path.is_file()
246                    && entry_path
247                        .extension()
248                        .is_some_and(|ext| ext.eq_ignore_ascii_case("winmd"))
249                {
250                    let file = reader::File::read(&entry_path).ok_or_else(|| {
251                        Error::new(format!("failed to read `{}`", entry_path.display()))
252                    })?;
253                    result.push(file);
254                }
255            }
256
257            if result.len() == prev_len {
258                return Err(Error::new(format!(
259                    "no .winmd files found in directory `{}`",
260                    input.display()
261                )));
262            }
263        } else {
264            let file = reader::File::read(input)
265                .ok_or_else(|| Error::new(format!("failed to read `{}`", input.display())))?;
266            result.push(file);
267        }
268    }
269
270    Ok(result)
271}
272
273/// Writes a `TypeDef`, using `arch_override` to replace any existing arch attribute.
274fn write_type(
275    file: &mut writer::File,
276    index: &reader::Index,
277    def: reader::TypeDef,
278    outer: Option<writer::TypeDef>,
279    arch_override: Option<i32>,
280) {
281    write_type_with_signature(file, index, def, outer, arch_override, None);
282}
283
284fn write_type_with_signature(
285    file: &mut writer::File,
286    index: &reader::Index,
287    def: reader::TypeDef,
288    outer: Option<writer::TypeDef>,
289    arch_override: Option<i32>,
290    signature_override: Option<&Signature>,
291) {
292    let extends = def
293        .extends()
294        .map(|extends| {
295            writer::TypeDefOrRef::TypeRef(file.TypeRef(extends.namespace(), extends.name()))
296        })
297        .unwrap_or_default();
298
299    debug_assert!(
300        !def.flags().is_nested() || def.namespace().is_empty(),
301        "nested type should have empty namespace"
302    );
303    debug_assert!(
304        def.flags().is_nested() || !def.namespace().is_empty(),
305        "non-nested type should have non-empty namespace"
306    );
307
308    let type_def = file.TypeDef(def.namespace(), def.name(), extends, def.flags());
309
310    if let Some(outer) = outer {
311        file.NestedClass(type_def, outer);
312    }
313
314    for field in def.fields() {
315        write_field(file, field, None);
316    }
317
318    let generics: Vec<_> = def
319        .generic_params()
320        .map(|param| Type::Generic(param.name().to_string(), param.sequence()))
321        .collect();
322
323    write_attributes_with_arch(
324        file,
325        writer::HasAttribute::TypeDef(type_def),
326        def,
327        arch_override,
328    );
329
330    for map in def.interface_impls() {
331        let interface_impl = file.InterfaceImpl(type_def, &map.interface(&generics));
332        write_attributes(
333            file,
334            writer::HasAttribute::InterfaceImpl(interface_impl),
335            map,
336        );
337    }
338
339    for generic in def.generic_params() {
340        file.GenericParam(
341            generic.name(),
342            writer::TypeOrMethodDef::TypeDef(type_def),
343            generic.sequence(),
344            generic.flags(),
345        );
346    }
347
348    let is_winrt_class = def.category() == reader::TypeCategory::Class
349        && def.flags().contains(TypeAttributes::WindowsRuntime);
350
351    if !is_winrt_class {
352        for method in def.methods() {
353            write_method_with_signature(
354                file,
355                method,
356                &generics,
357                None,
358                signature_override.filter(|_| method.name() == "Invoke"),
359            );
360        }
361    }
362
363    if let Some(class_layout) = def.class_layout() {
364        file.ClassLayout(
365            type_def,
366            class_layout.packing_size(),
367            class_layout.class_size(),
368        );
369    }
370
371    for inner_def in index.nested(def) {
372        debug_assert!(inner_def.namespace().is_empty());
373        debug_assert!(inner_def.flags().is_nested());
374        write_type(file, index, inner_def, Some(type_def), arch_override);
375    }
376}
377
378fn write_field(file: &mut writer::File, field: reader::Field, arch_override: Option<i32>) {
379    let field_def = file.Field(field.name(), &field.ty(), field.flags());
380    if let Some(constant) = field.constant() {
381        file.Constant(writer::HasConstant::Field(field_def), &constant.value());
382    }
383    write_attributes_with_arch(
384        file,
385        writer::HasAttribute::Field(field_def),
386        field,
387        arch_override,
388    );
389}
390
391/// Returns the `SupportedArchitectureAttribute` bits on a type, or 0 (arch-neutral) if absent.
392fn type_arch_bits(def: reader::TypeDef) -> i32 {
393    for attribute in def.attributes() {
394        let ty = attribute.ctor().parent();
395        if ty.namespace() == "Windows.Win32.Metadata"
396            && ty.name() == "SupportedArchitectureAttribute"
397            && let Some((_, Value::I32(bits))) = attribute.value().first()
398        {
399            return *bits;
400        }
401    }
402    0
403}
404
405/// Extracts the integer value of an enum member for comparison, or `None` for non-integer values.
406fn enum_member_i64(value: &Value) -> Option<i64> {
407    match value {
408        Value::U8(v) => Some(*v as i64),
409        Value::I8(v) => Some(*v as i64),
410        Value::U16(v) => Some(*v as i64),
411        Value::I16(v) => Some(*v as i64),
412        Value::U32(v) => Some(*v as i64),
413        Value::I32(v) => Some(*v as i64),
414        Value::U64(v) => Some(*v as i64),
415        Value::I64(v) => Some(*v),
416        _ => None,
417    }
418}
419
420/// Unions same-named class copies (the per-namespace `Apis` container) into one, combining every
421/// copy's fields and methods. Each member keeps its own attributes, including any arch tag the
422/// input winmd already applied, so the `um` and `km` function/constant surfaces both survive.
423fn write_class_union(file: &mut writer::File, index: &reader::Index, copies: &[reader::TypeDef]) {
424    let def = copies[0];
425
426    let extends = def
427        .extends()
428        .map(|extends| {
429            writer::TypeDefOrRef::TypeRef(file.TypeRef(extends.namespace(), extends.name()))
430        })
431        .unwrap_or_default();
432    let type_def = file.TypeDef(def.namespace(), def.name(), extends, def.flags());
433
434    write_attributes_with_arch(file, writer::HasAttribute::TypeDef(type_def), def, None);
435
436    let generics: Vec<_> = def
437        .generic_params()
438        .map(|param| Type::Generic(param.name().to_string(), param.sequence()))
439        .collect();
440
441    let mut seen_fields: HashSet<String> = HashSet::new();
442    for copy in copies {
443        for field in copy.fields() {
444            let value = field
445                .constant()
446                .map(|c| format!("{:?}", c.value()))
447                .unwrap_or_default();
448            let key = format!("{}|{:?}|{value}", field.name(), field.ty());
449            if seen_fields.insert(key) {
450                write_field(file, field, None);
451            }
452        }
453    }
454
455    let mut seen_methods: HashSet<String> = HashSet::new();
456    for copy in copies {
457        for method in copy.methods() {
458            let key = format!("{}|{:?}", method.name(), method.signature(&generics));
459            if seen_methods.insert(key) {
460                write_method(file, method, &generics, None);
461            }
462        }
463    }
464
465    for inner_def in index.nested(def) {
466        write_type(file, index, inner_def, Some(type_def), None);
467    }
468}
469
470/// Returns `true` if the member name marks a trailing count sentinel in the NT naming style.
471///
472/// These enums terminate with a member whose value equals the member count, not a real value.
473/// A truncated projection carries a smaller sentinel; the fuller definition carries a larger
474/// one. The sentinel is the only member allowed to disagree across copies of the same enum.
475///
476/// The match is limited to the NT sentinel spellings - a `Max` prefix (`MaxKeySetInfoClass`,
477/// `MaximumInterfaceType`) or a PascalCase `Maximum` suffix (`PowerSystemMaximum`,
478/// `FileMaximumInformation`). A broader `contains("Max")` would also treat the many real enum
479/// values that merely contain `MAX`/`_MAX` (`IPPROTO_MAX`, `WBEM_MAX_PATH`, `MaxPayload128Bytes`)
480/// as tolerable, silently masking a genuine value conflict.
481fn is_max_sentinel(name: &str) -> bool {
482    name.starts_with("Max") || name.ends_with("Maximum") || name.ends_with("MaximumInformation")
483}
484
485/// Unions same-named enum copies into one enum carrying every member.
486///
487/// A `um` header often projects a value type in truncated or partial form while the `km` scrape
488/// emits more of it; neither is guaranteed to be a superset (for example `THREADINFOCLASS`, where
489/// `um` contributes `ThreadNameInformation` that `km` omits). The fullest copy sets the member
490/// order, the type flags, and the attributes; members other copies add are appended. A member
491/// shared by two copies must agree on its value, except for the trailing `Max*` count sentinel,
492/// whose value legitimately grows with the member count. Any other disagreement is a real
493/// metadata conflict and is rejected.
494fn write_enum_union(file: &mut writer::File, copies: &[reader::TypeDef]) -> Result<(), Error> {
495    let base = *copies
496        .iter()
497        .max_by_key(|copy| copy.fields().count())
498        .unwrap();
499
500    let base_members: HashMap<String, Value> = base
501        .fields()
502        .filter_map(|field| {
503            field
504                .constant()
505                .map(|c| (field.name().to_string(), c.value()))
506        })
507        .collect();
508
509    // Members present in some copy but not the fullest one, kept in first-appearance order.
510    let mut extra_order: Vec<String> = Vec::new();
511    let mut extras: HashMap<String, reader::Field> = HashMap::new();
512
513    let conflict = |field: reader::Field| {
514        Error::new(format!(
515            "enum `{}.{}` member `{}` has conflicting values across inputs",
516            base.namespace(),
517            base.name(),
518            field.name()
519        ))
520    };
521
522    for copy in copies {
523        for field in copy.fields() {
524            let Some(constant) = field.constant() else {
525                continue;
526            };
527            let value = constant.value();
528
529            if let Some(base_value) = base_members.get(field.name()) {
530                if *base_value == value {
531                    continue;
532                }
533                // The fullest copy's sentinel is authoritative (its count is the largest); a
534                // smaller sentinel from a truncated copy is discarded.
535                let tolerated = is_max_sentinel(field.name())
536                    && matches!(
537                        (enum_member_i64(base_value), enum_member_i64(&value)),
538                        (Some(b), Some(v)) if b >= v
539                    );
540                if !tolerated {
541                    return Err(conflict(field));
542                }
543                continue;
544            }
545
546            match extras.get(field.name()) {
547                None => {
548                    extra_order.push(field.name().to_string());
549                    extras.insert(field.name().to_string(), field);
550                }
551                Some(existing) => {
552                    let existing_value = existing.constant().unwrap().value();
553                    if existing_value == value {
554                        continue;
555                    }
556                    let keep_larger = is_max_sentinel(field.name())
557                        && matches!(
558                            (enum_member_i64(&existing_value), enum_member_i64(&value)),
559                            (Some(a), Some(b)) if a != b
560                        );
561                    if !keep_larger {
562                        return Err(conflict(field));
563                    }
564                    if enum_member_i64(&value) > enum_member_i64(&existing_value) {
565                        extras.insert(field.name().to_string(), field);
566                    }
567                }
568            }
569        }
570    }
571
572    let extends = base
573        .extends()
574        .map(|extends| {
575            writer::TypeDefOrRef::TypeRef(file.TypeRef(extends.namespace(), extends.name()))
576        })
577        .unwrap_or_default();
578
579    let type_def = file.TypeDef(base.namespace(), base.name(), extends, base.flags());
580    write_attributes_with_arch(file, writer::HasAttribute::TypeDef(type_def), base, None);
581
582    for field in base.fields() {
583        write_field(file, field, None);
584    }
585    for name in &extra_order {
586        write_field(file, extras[name], None);
587    }
588
589    Ok(())
590}
591
592fn write_method(
593    file: &mut writer::File,
594    method: reader::MethodDef,
595    generics: &[Type],
596    arch_override: Option<i32>,
597) {
598    write_method_with_signature(file, method, generics, arch_override, None);
599}
600
601fn write_method_with_signature(
602    file: &mut writer::File,
603    method: reader::MethodDef,
604    generics: &[Type],
605    arch_override: Option<i32>,
606    signature_override: Option<&Signature>,
607) {
608    let signature;
609    let signature = if let Some(signature) = signature_override {
610        signature
611    } else {
612        signature = method.signature(generics);
613        &signature
614    };
615    let method_def = file.MethodDef(
616        method.name(),
617        signature,
618        method.flags(),
619        method.impl_flags(),
620    );
621    for param_def in method.params() {
622        let param = file.Param(param_def.name(), param_def.sequence(), param_def.flags());
623        write_attributes(file, writer::HasAttribute::Param(param), param_def);
624    }
625    write_attributes_with_arch(
626        file,
627        writer::HasAttribute::MethodDef(method_def),
628        method,
629        arch_override,
630    );
631    if let Some(impl_map) = method.impl_map() {
632        file.ImplMap(
633            method_def,
634            impl_map.flags(),
635            impl_map.import_name(),
636            impl_map.import_scope().name(),
637        );
638    }
639}
640
641/// Reconciles an unmanaged callback whose SDK signature explicitly uses a native-sized integer on
642/// at least one architecture and the same-width fixed integer on the others.
643///
644/// This is intentionally not a general `i32`/`i64` merge heuristic. The `isize`/`usize` spelling
645/// supplies the semantic evidence, and each fixed integer must match that input's pointer width.
646fn merge_native_sized_callback(
647    copies: &[(&reader::Index, reader::TypeDef, i32)],
648) -> Option<Signature> {
649    if copies.len() < 2
650        || copies
651            .iter()
652            .any(|(_, def, _)| !is_unmanaged_callback(*def))
653    {
654        return None;
655    }
656
657    let first_def = copies[0].1;
658    if copies.iter().any(|(_, def, _)| {
659        def.flags() != first_def.flags()
660            || callback_attributes(*def) != callback_attributes(first_def)
661    }) {
662        return None;
663    }
664
665    let methods: Vec<_> = copies
666        .iter()
667        .map(|(_, def, bits)| {
668            let mut methods = def.methods();
669            let method = methods.next()?;
670            (method.name() == "Invoke" && methods.next().is_none()).then_some((method, *bits))
671        })
672        .collect::<Option<_>>()?;
673
674    let first = methods[0].0;
675    if methods.iter().any(|(method, _)| {
676        method.flags() != first.flags()
677            || method.impl_flags() != first.impl_flags()
678            || callback_attributes(*method) != callback_attributes(first)
679            || callback_params(*method) != callback_params(first)
680    }) {
681        return None;
682    }
683
684    let signatures: Vec<_> = methods
685        .iter()
686        .map(|(method, bits)| (method.signature(&[]), *bits))
687        .collect();
688    let flags = signatures[0].0.flags;
689    if signatures.iter().any(|(signature, _)| {
690        signature.flags != flags || signature.types.len() != signatures[0].0.types.len()
691    }) {
692        return None;
693    }
694
695    let (return_type, mut changed) = merge_native_sized_type(
696        &signatures
697            .iter()
698            .map(|(signature, bits)| (&signature.return_type, *bits))
699            .collect::<Vec<_>>(),
700    )?;
701
702    let mut types = Vec::with_capacity(signatures[0].0.types.len());
703    for index in 0..signatures[0].0.types.len() {
704        let (ty, position_changed) = merge_native_sized_type(
705            &signatures
706                .iter()
707                .map(|(signature, bits)| (&signature.types[index], *bits))
708                .collect::<Vec<_>>(),
709        )?;
710        changed |= position_changed;
711        types.push(ty);
712    }
713
714    changed.then_some(Signature {
715        flags,
716        return_type,
717        types,
718    })
719}
720
721fn is_unmanaged_callback(def: reader::TypeDef) -> bool {
722    def.category() == reader::TypeCategory::Delegate
723        && def.attributes().any(|attribute| {
724            let ty = attribute.ctor().parent();
725            ty.namespace() == "System.Runtime.InteropServices"
726                && ty.name() == "UnmanagedFunctionPointerAttribute"
727        })
728}
729
730fn callback_params(
731    method: reader::MethodDef,
732) -> Vec<(
733    String,
734    u16,
735    ParamAttributes,
736    Vec<(String, String, Vec<(String, Value)>)>,
737)> {
738    method
739        .params()
740        .map(|param| {
741            (
742                param.name().to_string(),
743                param.sequence(),
744                param.flags(),
745                callback_attributes(param),
746            )
747        })
748        .collect()
749}
750
751fn callback_attributes<'a, R: HasAttributes<'a>>(
752    row: R,
753) -> Vec<(String, String, Vec<(String, Value)>)> {
754    row.attributes()
755        .filter_map(|attribute| {
756            let ty = attribute.ctor().parent();
757            (!(ty.namespace() == "Windows.Win32.Metadata"
758                && ty.name() == "SupportedArchitectureAttribute"))
759                .then(|| {
760                    (
761                        ty.namespace().to_string(),
762                        ty.name().to_string(),
763                        attribute.value(),
764                    )
765                })
766        })
767        .collect()
768}
769
770fn merge_native_sized_type(copies: &[(&Type, i32)]) -> Option<(Type, bool)> {
771    let first = copies.first()?.0;
772    if copies.iter().all(|(ty, _)| *ty == first) {
773        return Some((first.clone(), false));
774    }
775
776    if copies.iter().any(|(ty, _)| **ty == Type::ISize)
777        && copies
778            .iter()
779            .all(|(ty, bits)| native_signed_compatible(ty, *bits))
780    {
781        return Some((Type::ISize, true));
782    }
783    if copies.iter().any(|(ty, _)| **ty == Type::USize)
784        && copies
785            .iter()
786            .all(|(ty, bits)| native_unsigned_compatible(ty, *bits))
787    {
788        return Some((Type::USize, true));
789    }
790    None
791}
792
793fn native_signed_compatible(ty: &Type, bits: i32) -> bool {
794    matches!(ty, Type::ISize)
795        || matches!(
796            (ty, pointer_width(bits)),
797            (Type::I32, Some(32)) | (Type::I64, Some(64))
798        )
799}
800
801fn native_unsigned_compatible(ty: &Type, bits: i32) -> bool {
802    matches!(ty, Type::USize)
803        || matches!(
804            (ty, pointer_width(bits)),
805            (Type::U32, Some(32)) | (Type::U64, Some(64))
806        )
807}
808
809fn pointer_width(bits: i32) -> Option<u8> {
810    match bits {
811        1 => Some(32),
812        2 | 4 => Some(64),
813        _ => None,
814    }
815}
816
817/// Unions arch-specific Apis members and tags members absent from some arches.
818fn write_type_arch_merged(
819    file: &mut writer::File,
820    index: &reader::Index,
821    def: reader::TypeDef,
822    copies: &[(&reader::Index, reader::TypeDef, i32)],
823    all_mask: i32,
824) {
825    let extends = def
826        .extends()
827        .map(|e| writer::TypeDefOrRef::TypeRef(file.TypeRef(e.namespace(), e.name())))
828        .unwrap_or_default();
829    let type_def = file.TypeDef(def.namespace(), def.name(), extends, def.flags());
830
831    let generics: Vec<_> = def
832        .generic_params()
833        .map(|p| Type::Generic(p.name().to_string(), p.sequence()))
834        .collect();
835
836    write_attributes_with_arch(file, writer::HasAttribute::TypeDef(type_def), def, Some(0));
837    for map in def.interface_impls() {
838        let interface_impl = file.InterfaceImpl(type_def, &map.interface(&generics));
839        write_attributes(
840            file,
841            writer::HasAttribute::InterfaceImpl(interface_impl),
842            map,
843        );
844    }
845    for generic in def.generic_params() {
846        file.GenericParam(
847            generic.name(),
848            writer::TypeOrMethodDef::TypeDef(type_def),
849            generic.sequence(),
850            generic.flags(),
851        );
852    }
853
854    // Include constant values in the key so divergent constants survive.
855    let mut fields: BTreeMap<String, (reader::Field, i32)> = BTreeMap::new();
856    for (_, ty, bits) in copies {
857        for field in ty.fields() {
858            let val = field
859                .constant()
860                .map(|c| format!("{:?}", c.value()))
861                .unwrap_or_default();
862            let key = format!("{}|{:?}|{val}", field.name(), field.ty());
863            fields.entry(key).or_insert((field, 0)).1 |= bits;
864        }
865    }
866    for (field, bits) in fields.into_values() {
867        write_field(file, field, Some(if bits == all_mask { 0 } else { bits }));
868    }
869
870    let is_winrt_class = def.category() == reader::TypeCategory::Class
871        && def.flags().contains(TypeAttributes::WindowsRuntime);
872    if !is_winrt_class {
873        let mut methods: BTreeMap<String, (reader::MethodDef, i32)> = BTreeMap::new();
874        for (_, ty, bits) in copies {
875            for method in ty.methods() {
876                let key = format!("{}|{:?}", method.name(), method.signature(&generics));
877                methods.entry(key).or_insert((method, 0)).1 |= bits;
878            }
879        }
880        for (method, bits) in methods.into_values() {
881            write_method(
882                file,
883                method,
884                &generics,
885                Some(if bits == all_mask { 0 } else { bits }),
886            );
887        }
888    }
889
890    if let Some(class_layout) = def.class_layout() {
891        file.ClassLayout(
892            type_def,
893            class_layout.packing_size(),
894            class_layout.class_size(),
895        );
896    }
897
898    for inner_def in index.nested(def) {
899        write_type(file, index, inner_def, Some(type_def), Some(0));
900    }
901}
902
903/// Signatures include layout, methods, constants, alignment, and nested shapes so arch-specific
904/// value types do not collapse into one neutral definition.
905fn type_sig(index: &reader::Index, def: reader::TypeDef) -> String {
906    let fields: Vec<String> = def
907        .fields()
908        .map(|f| {
909            let val = f
910                .constant()
911                .map(|c| format!("{:?}", c.value()))
912                .unwrap_or_default();
913            format!("{}:{:?}={val}", f.name(), f.ty())
914        })
915        .collect();
916    let methods: Vec<String> = def
917        .methods()
918        .map(|m| format!("{}:{:?}", m.name(), m.signature(&[])))
919        .collect();
920    let layout = def
921        .class_layout()
922        .map(|l| (l.packing_size(), l.class_size()));
923    let align = def
924        .find_attribute("AlignmentAttribute")
925        .map(|a| format!("{:?}", a.value()));
926    // Recurse into nested shapes; outer fields reference only invariant nested leaf names.
927    let nested: Vec<String> = index
928        .nested(def)
929        .map(|inner| format!("{}={}", inner.name(), type_sig(index, inner)))
930        .collect();
931    format!(
932        "{fields:?}|{methods:?}|{layout:?}|{align:?}|{:?}|{nested:?}",
933        def.flags()
934    )
935}
936
937fn write_attributes<'a, R: HasAttributes<'a>>(
938    file: &mut writer::File,
939    parent: writer::HasAttribute,
940    row: R,
941) {
942    write_attributes_with_arch(file, parent, row, None);
943}
944
945/// Copies attributes, optionally replacing `SupportedArchitectureAttribute`.
946fn write_attributes_with_arch<'a, R: HasAttributes<'a>>(
947    file: &mut writer::File,
948    parent: writer::HasAttribute,
949    row: R,
950    arch_override: Option<i32>,
951) {
952    for attribute in row.attributes() {
953        let ctor = attribute.ctor();
954        let ty = ctor.parent();
955
956        if arch_override.is_some()
957            && ty.namespace() == "Windows.Win32.Metadata"
958            && ty.name() == "SupportedArchitectureAttribute"
959        {
960            continue;
961        }
962
963        let attribute_ref =
964            writer::MemberRefParent::TypeRef(file.TypeRef(ty.namespace(), ty.name()));
965
966        let ctor_ref = file.MemberRef(".ctor", &ctor.signature(&[]), attribute_ref);
967
968        file.Attribute(
969            parent,
970            writer::AttributeType::MemberRef(ctor_ref),
971            &attribute.value(),
972        );
973    }
974
975    if let Some(arch_bits) = arch_override
976        && arch_bits != 0
977    {
978        write_supported_architecture_attr(file, parent, arch_bits);
979    }
980}
981
982fn write_supported_architecture_attr(
983    file: &mut writer::File,
984    parent: writer::HasAttribute,
985    arch_bits: i32,
986) {
987    let ns = "Windows.Win32.Metadata";
988    let name = "SupportedArchitectureAttribute";
989
990    let type_ref = writer::MemberRefParent::TypeRef(file.TypeRef(ns, name));
991
992    let sig = Signature {
993        flags: MethodCallAttributes::HASTHIS,
994        return_type: Type::Void,
995        types: vec![Type::I32],
996    };
997
998    let ctor_ref = file.MemberRef(".ctor", &sig, type_ref);
999
1000    file.Attribute(
1001        parent,
1002        writer::AttributeType::MemberRef(ctor_ref),
1003        &[(String::new(), Value::I32(arch_bits))],
1004    );
1005}