use super::*;
use std::path::{Path, PathBuf};
mod remap;
pub use remap::Remapper;
pub struct Error(String);
impl Error {
fn new(message: impl Into<String>) -> Self {
Self(message.into())
}
}
impl std::error::Error for Error {}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "\nerror: {}", self.0)
}
}
#[derive(Default)]
pub struct Merger {
input: Vec<PathBuf>,
arch_inputs: Vec<(PathBuf, i32)>,
output: PathBuf,
union_enums: bool,
}
impl Merger {
pub fn new() -> Self {
Self::default()
}
pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
self.input.push(input.as_ref().to_path_buf());
self
}
pub fn inputs<I, S>(&mut self, inputs: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<Path>,
{
for input in inputs {
self.input(input);
}
self
}
pub fn arch_input(&mut self, path: impl AsRef<Path>, arch: i32) -> &mut Self {
self.arch_inputs.push((path.as_ref().to_path_buf(), arch));
self
}
pub fn union_enums(&mut self) -> &mut Self {
self.union_enums = true;
self
}
pub fn output(&mut self, output: impl AsRef<Path>) -> &mut Self {
self.output = output.as_ref().to_path_buf();
self
}
pub fn merge(&self) -> Result<(), Error> {
if self.output.as_os_str().is_empty() {
return Err(Error::new("output is required"));
}
let name = self
.output
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| {
Error::new(format!("invalid output path `{}`", self.output.display()))
})?;
let files = read_inputs(&self.input)?;
let index = reader::Index::new(files);
let mut file = writer::File::new(name);
if self.union_enums {
let mut groups: BTreeMap<(String, String), Vec<reader::TypeDef<'_>>> = BTreeMap::new();
for ty in index.types() {
groups
.entry((ty.namespace().to_string(), ty.name().to_string()))
.or_default()
.push(ty);
}
for copies in groups.values() {
if copies
.iter()
.all(|ty| ty.category() == reader::TypeCategory::Class)
{
write_class_union(&mut file, &index, copies);
continue;
}
let mut by_arch: BTreeMap<i32, Vec<reader::TypeDef<'_>>> = BTreeMap::new();
for copy in copies {
by_arch
.entry(type_arch_bits(*copy))
.or_default()
.push(*copy);
}
for arch_copies in by_arch.values() {
if arch_copies
.iter()
.all(|ty| ty.category() == reader::TypeCategory::Enum)
{
write_enum_union(&mut file, arch_copies)?;
} else {
write_type(&mut file, &index, arch_copies[0], None, None);
}
}
}
} else {
let mut types: Vec<reader::TypeDef<'_>> = index.types().collect();
types.sort_by(|a, b| (a.namespace(), a.name()).cmp(&(b.namespace(), b.name())));
for ty in types {
write_type(&mut file, &index, ty, None, None);
}
}
if !self.arch_inputs.is_empty() {
let all_arches_mask: i32 = self.arch_inputs.iter().fold(0, |acc, (_, arch)| acc | arch);
let mut arch_groups: Vec<(reader::Index, i32)> =
Vec::with_capacity(self.arch_inputs.len());
for (path, arch_bits) in &self.arch_inputs {
let files = read_inputs(std::slice::from_ref(path))?;
arch_groups.push((reader::Index::new(files), *arch_bits));
}
let mut groups: BTreeMap<
(String, String),
Vec<(&reader::Index, reader::TypeDef<'_>, i32)>,
> = BTreeMap::new();
for (idx, arch_bits) in &arch_groups {
for ty in idx.types() {
groups
.entry((ty.namespace().to_string(), ty.name().to_string()))
.or_default()
.push((idx, ty, *arch_bits));
}
}
for copies in groups.values() {
let (idx, ty, _) = copies[0];
if ty.category() == reader::TypeCategory::Class {
write_type_arch_merged(&mut file, idx, ty, copies, all_arches_mask);
} else if let Some(signature) = merge_native_sized_callback(copies) {
let bits = copies.iter().fold(0, |acc, (_, _, bits)| acc | bits);
let arch = if bits == all_arches_mask { 0 } else { bits };
write_type_with_signature(
&mut file,
idx,
ty,
None,
Some(arch),
Some(&signature),
);
} else {
let mut by_sig: Vec<(String, &reader::Index, reader::TypeDef, i32)> = vec![];
for (cidx, c, bits) in copies {
let sig = type_sig(cidx, *c);
if let Some(entry) = by_sig.iter_mut().find(|(s, ..)| *s == sig) {
entry.3 |= *bits;
} else {
by_sig.push((sig, cidx, *c, *bits));
}
}
for (_, cidx, c, bits) in &by_sig {
let arch = if *bits == all_arches_mask { 0 } else { *bits };
write_type(&mut file, cidx, *c, None, Some(arch));
}
}
}
}
let bytes = file.into_stream();
std::fs::write(&self.output, bytes)
.map_err(|e| Error::new(format!("failed to write `{}`: {e}", self.output.display())))
}
}
fn read_inputs(inputs: &[PathBuf]) -> Result<Vec<reader::File>, Error> {
let mut result = vec![];
for input in inputs {
if input.is_dir() {
let prev_len = result.len();
let entries = std::fs::read_dir(input).map_err(|e| {
Error::new(format!(
"failed to read directory `{}`: {e}",
input.display()
))
})?;
for entry in entries.flatten() {
let entry_path = entry.path();
if entry_path.is_file()
&& entry_path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("winmd"))
{
let file = reader::File::read(&entry_path).ok_or_else(|| {
Error::new(format!("failed to read `{}`", entry_path.display()))
})?;
result.push(file);
}
}
if result.len() == prev_len {
return Err(Error::new(format!(
"no .winmd files found in directory `{}`",
input.display()
)));
}
} else {
let file = reader::File::read(input)
.ok_or_else(|| Error::new(format!("failed to read `{}`", input.display())))?;
result.push(file);
}
}
Ok(result)
}
fn write_type(
file: &mut writer::File,
index: &reader::Index,
def: reader::TypeDef,
outer: Option<writer::TypeDef>,
arch_override: Option<i32>,
) {
write_type_with_signature(file, index, def, outer, arch_override, None);
}
fn write_type_with_signature(
file: &mut writer::File,
index: &reader::Index,
def: reader::TypeDef,
outer: Option<writer::TypeDef>,
arch_override: Option<i32>,
signature_override: Option<&Signature>,
) {
let extends = def
.extends()
.map(|extends| {
writer::TypeDefOrRef::TypeRef(file.TypeRef(extends.namespace(), extends.name()))
})
.unwrap_or_default();
debug_assert!(
!def.flags().is_nested() || def.namespace().is_empty(),
"nested type should have empty namespace"
);
debug_assert!(
def.flags().is_nested() || !def.namespace().is_empty(),
"non-nested type should have non-empty namespace"
);
let type_def = file.TypeDef(def.namespace(), def.name(), extends, def.flags());
if let Some(outer) = outer {
file.NestedClass(type_def, outer);
}
for field in def.fields() {
write_field(file, field, None);
}
let generics: Vec<_> = def
.generic_params()
.map(|param| Type::Generic(param.name().to_string(), param.sequence()))
.collect();
write_attributes_with_arch(
file,
writer::HasAttribute::TypeDef(type_def),
def,
arch_override,
);
for map in def.interface_impls() {
let interface_impl = file.InterfaceImpl(type_def, &map.interface(&generics));
write_attributes(
file,
writer::HasAttribute::InterfaceImpl(interface_impl),
map,
);
}
for generic in def.generic_params() {
file.GenericParam(
generic.name(),
writer::TypeOrMethodDef::TypeDef(type_def),
generic.sequence(),
generic.flags(),
);
}
let is_winrt_class = def.category() == reader::TypeCategory::Class
&& def.flags().contains(TypeAttributes::WindowsRuntime);
if !is_winrt_class {
for method in def.methods() {
write_method_with_signature(
file,
method,
&generics,
None,
signature_override.filter(|_| method.name() == "Invoke"),
);
}
}
if let Some(class_layout) = def.class_layout() {
file.ClassLayout(
type_def,
class_layout.packing_size(),
class_layout.class_size(),
);
}
for inner_def in index.nested(def) {
debug_assert!(inner_def.namespace().is_empty());
debug_assert!(inner_def.flags().is_nested());
write_type(file, index, inner_def, Some(type_def), arch_override);
}
}
fn write_field(file: &mut writer::File, field: reader::Field, arch_override: Option<i32>) {
let field_def = file.Field(field.name(), &field.ty(), field.flags());
if let Some(constant) = field.constant() {
file.Constant(writer::HasConstant::Field(field_def), &constant.value());
}
write_attributes_with_arch(
file,
writer::HasAttribute::Field(field_def),
field,
arch_override,
);
}
fn type_arch_bits(def: reader::TypeDef) -> i32 {
for attribute in def.attributes() {
let ty = attribute.ctor().parent();
if ty.namespace() == "Windows.Win32.Metadata"
&& ty.name() == "SupportedArchitectureAttribute"
&& let Some((_, Value::I32(bits))) = attribute.value().first()
{
return *bits;
}
}
0
}
fn enum_member_i64(value: &Value) -> Option<i64> {
match value {
Value::U8(v) => Some(*v as i64),
Value::I8(v) => Some(*v as i64),
Value::U16(v) => Some(*v as i64),
Value::I16(v) => Some(*v as i64),
Value::U32(v) => Some(*v as i64),
Value::I32(v) => Some(*v as i64),
Value::U64(v) => Some(*v as i64),
Value::I64(v) => Some(*v),
_ => None,
}
}
fn write_class_union(file: &mut writer::File, index: &reader::Index, copies: &[reader::TypeDef]) {
let def = copies[0];
let extends = def
.extends()
.map(|extends| {
writer::TypeDefOrRef::TypeRef(file.TypeRef(extends.namespace(), extends.name()))
})
.unwrap_or_default();
let type_def = file.TypeDef(def.namespace(), def.name(), extends, def.flags());
write_attributes_with_arch(file, writer::HasAttribute::TypeDef(type_def), def, None);
let generics: Vec<_> = def
.generic_params()
.map(|param| Type::Generic(param.name().to_string(), param.sequence()))
.collect();
let mut seen_fields: HashSet<String> = HashSet::new();
for copy in copies {
for field in copy.fields() {
let value = field
.constant()
.map(|c| format!("{:?}", c.value()))
.unwrap_or_default();
let key = format!("{}|{:?}|{value}", field.name(), field.ty());
if seen_fields.insert(key) {
write_field(file, field, None);
}
}
}
let mut seen_methods: HashSet<String> = HashSet::new();
for copy in copies {
for method in copy.methods() {
let key = format!("{}|{:?}", method.name(), method.signature(&generics));
if seen_methods.insert(key) {
write_method(file, method, &generics, None);
}
}
}
for inner_def in index.nested(def) {
write_type(file, index, inner_def, Some(type_def), None);
}
}
fn is_max_sentinel(name: &str) -> bool {
name.starts_with("Max") || name.ends_with("Maximum") || name.ends_with("MaximumInformation")
}
fn write_enum_union(file: &mut writer::File, copies: &[reader::TypeDef]) -> Result<(), Error> {
let base = *copies
.iter()
.max_by_key(|copy| copy.fields().count())
.unwrap();
let base_members: HashMap<String, Value> = base
.fields()
.filter_map(|field| {
field
.constant()
.map(|c| (field.name().to_string(), c.value()))
})
.collect();
let mut extra_order: Vec<String> = Vec::new();
let mut extras: HashMap<String, reader::Field> = HashMap::new();
let conflict = |field: reader::Field| {
Error::new(format!(
"enum `{}.{}` member `{}` has conflicting values across inputs",
base.namespace(),
base.name(),
field.name()
))
};
for copy in copies {
for field in copy.fields() {
let Some(constant) = field.constant() else {
continue;
};
let value = constant.value();
if let Some(base_value) = base_members.get(field.name()) {
if *base_value == value {
continue;
}
let tolerated = is_max_sentinel(field.name())
&& matches!(
(enum_member_i64(base_value), enum_member_i64(&value)),
(Some(b), Some(v)) if b >= v
);
if !tolerated {
return Err(conflict(field));
}
continue;
}
match extras.get(field.name()) {
None => {
extra_order.push(field.name().to_string());
extras.insert(field.name().to_string(), field);
}
Some(existing) => {
let existing_value = existing.constant().unwrap().value();
if existing_value == value {
continue;
}
let keep_larger = is_max_sentinel(field.name())
&& matches!(
(enum_member_i64(&existing_value), enum_member_i64(&value)),
(Some(a), Some(b)) if a != b
);
if !keep_larger {
return Err(conflict(field));
}
if enum_member_i64(&value) > enum_member_i64(&existing_value) {
extras.insert(field.name().to_string(), field);
}
}
}
}
}
let extends = base
.extends()
.map(|extends| {
writer::TypeDefOrRef::TypeRef(file.TypeRef(extends.namespace(), extends.name()))
})
.unwrap_or_default();
let type_def = file.TypeDef(base.namespace(), base.name(), extends, base.flags());
write_attributes_with_arch(file, writer::HasAttribute::TypeDef(type_def), base, None);
for field in base.fields() {
write_field(file, field, None);
}
for name in &extra_order {
write_field(file, extras[name], None);
}
Ok(())
}
fn write_method(
file: &mut writer::File,
method: reader::MethodDef,
generics: &[Type],
arch_override: Option<i32>,
) {
write_method_with_signature(file, method, generics, arch_override, None);
}
fn write_method_with_signature(
file: &mut writer::File,
method: reader::MethodDef,
generics: &[Type],
arch_override: Option<i32>,
signature_override: Option<&Signature>,
) {
let signature;
let signature = if let Some(signature) = signature_override {
signature
} else {
signature = method.signature(generics);
&signature
};
let method_def = file.MethodDef(
method.name(),
signature,
method.flags(),
method.impl_flags(),
);
for param_def in method.params() {
let param = file.Param(param_def.name(), param_def.sequence(), param_def.flags());
write_attributes(file, writer::HasAttribute::Param(param), param_def);
}
write_attributes_with_arch(
file,
writer::HasAttribute::MethodDef(method_def),
method,
arch_override,
);
if let Some(impl_map) = method.impl_map() {
file.ImplMap(
method_def,
impl_map.flags(),
impl_map.import_name(),
impl_map.import_scope().name(),
);
}
}
fn merge_native_sized_callback(
copies: &[(&reader::Index, reader::TypeDef, i32)],
) -> Option<Signature> {
if copies.len() < 2
|| copies
.iter()
.any(|(_, def, _)| !is_unmanaged_callback(*def))
{
return None;
}
let first_def = copies[0].1;
if copies.iter().any(|(_, def, _)| {
def.flags() != first_def.flags()
|| callback_attributes(*def) != callback_attributes(first_def)
}) {
return None;
}
let methods: Vec<_> = copies
.iter()
.map(|(_, def, bits)| {
let mut methods = def.methods();
let method = methods.next()?;
(method.name() == "Invoke" && methods.next().is_none()).then_some((method, *bits))
})
.collect::<Option<_>>()?;
let first = methods[0].0;
if methods.iter().any(|(method, _)| {
method.flags() != first.flags()
|| method.impl_flags() != first.impl_flags()
|| callback_attributes(*method) != callback_attributes(first)
|| callback_params(*method) != callback_params(first)
}) {
return None;
}
let signatures: Vec<_> = methods
.iter()
.map(|(method, bits)| (method.signature(&[]), *bits))
.collect();
let flags = signatures[0].0.flags;
if signatures.iter().any(|(signature, _)| {
signature.flags != flags || signature.types.len() != signatures[0].0.types.len()
}) {
return None;
}
let (return_type, mut changed) = merge_native_sized_type(
&signatures
.iter()
.map(|(signature, bits)| (&signature.return_type, *bits))
.collect::<Vec<_>>(),
)?;
let mut types = Vec::with_capacity(signatures[0].0.types.len());
for index in 0..signatures[0].0.types.len() {
let (ty, position_changed) = merge_native_sized_type(
&signatures
.iter()
.map(|(signature, bits)| (&signature.types[index], *bits))
.collect::<Vec<_>>(),
)?;
changed |= position_changed;
types.push(ty);
}
changed.then_some(Signature {
flags,
return_type,
types,
})
}
fn is_unmanaged_callback(def: reader::TypeDef) -> bool {
def.category() == reader::TypeCategory::Delegate
&& def.attributes().any(|attribute| {
let ty = attribute.ctor().parent();
ty.namespace() == "System.Runtime.InteropServices"
&& ty.name() == "UnmanagedFunctionPointerAttribute"
})
}
fn callback_params(
method: reader::MethodDef,
) -> Vec<(
String,
u16,
ParamAttributes,
Vec<(String, String, Vec<(String, Value)>)>,
)> {
method
.params()
.map(|param| {
(
param.name().to_string(),
param.sequence(),
param.flags(),
callback_attributes(param),
)
})
.collect()
}
fn callback_attributes<'a, R: HasAttributes<'a>>(
row: R,
) -> Vec<(String, String, Vec<(String, Value)>)> {
row.attributes()
.filter_map(|attribute| {
let ty = attribute.ctor().parent();
(!(ty.namespace() == "Windows.Win32.Metadata"
&& ty.name() == "SupportedArchitectureAttribute"))
.then(|| {
(
ty.namespace().to_string(),
ty.name().to_string(),
attribute.value(),
)
})
})
.collect()
}
fn merge_native_sized_type(copies: &[(&Type, i32)]) -> Option<(Type, bool)> {
let first = copies.first()?.0;
if copies.iter().all(|(ty, _)| *ty == first) {
return Some((first.clone(), false));
}
if copies.iter().any(|(ty, _)| **ty == Type::ISize)
&& copies
.iter()
.all(|(ty, bits)| native_signed_compatible(ty, *bits))
{
return Some((Type::ISize, true));
}
if copies.iter().any(|(ty, _)| **ty == Type::USize)
&& copies
.iter()
.all(|(ty, bits)| native_unsigned_compatible(ty, *bits))
{
return Some((Type::USize, true));
}
None
}
fn native_signed_compatible(ty: &Type, bits: i32) -> bool {
matches!(ty, Type::ISize)
|| matches!(
(ty, pointer_width(bits)),
(Type::I32, Some(32)) | (Type::I64, Some(64))
)
}
fn native_unsigned_compatible(ty: &Type, bits: i32) -> bool {
matches!(ty, Type::USize)
|| matches!(
(ty, pointer_width(bits)),
(Type::U32, Some(32)) | (Type::U64, Some(64))
)
}
fn pointer_width(bits: i32) -> Option<u8> {
match bits {
1 => Some(32),
2 | 4 => Some(64),
_ => None,
}
}
fn write_type_arch_merged(
file: &mut writer::File,
index: &reader::Index,
def: reader::TypeDef,
copies: &[(&reader::Index, reader::TypeDef, i32)],
all_mask: i32,
) {
let extends = def
.extends()
.map(|e| writer::TypeDefOrRef::TypeRef(file.TypeRef(e.namespace(), e.name())))
.unwrap_or_default();
let type_def = file.TypeDef(def.namespace(), def.name(), extends, def.flags());
let generics: Vec<_> = def
.generic_params()
.map(|p| Type::Generic(p.name().to_string(), p.sequence()))
.collect();
write_attributes_with_arch(file, writer::HasAttribute::TypeDef(type_def), def, Some(0));
for map in def.interface_impls() {
let interface_impl = file.InterfaceImpl(type_def, &map.interface(&generics));
write_attributes(
file,
writer::HasAttribute::InterfaceImpl(interface_impl),
map,
);
}
for generic in def.generic_params() {
file.GenericParam(
generic.name(),
writer::TypeOrMethodDef::TypeDef(type_def),
generic.sequence(),
generic.flags(),
);
}
let mut fields: BTreeMap<String, (reader::Field, i32)> = BTreeMap::new();
for (_, ty, bits) in copies {
for field in ty.fields() {
let val = field
.constant()
.map(|c| format!("{:?}", c.value()))
.unwrap_or_default();
let key = format!("{}|{:?}|{val}", field.name(), field.ty());
fields.entry(key).or_insert((field, 0)).1 |= bits;
}
}
for (field, bits) in fields.into_values() {
write_field(file, field, Some(if bits == all_mask { 0 } else { bits }));
}
let is_winrt_class = def.category() == reader::TypeCategory::Class
&& def.flags().contains(TypeAttributes::WindowsRuntime);
if !is_winrt_class {
let mut methods: BTreeMap<String, (reader::MethodDef, i32)> = BTreeMap::new();
for (_, ty, bits) in copies {
for method in ty.methods() {
let key = format!("{}|{:?}", method.name(), method.signature(&generics));
methods.entry(key).or_insert((method, 0)).1 |= bits;
}
}
for (method, bits) in methods.into_values() {
write_method(
file,
method,
&generics,
Some(if bits == all_mask { 0 } else { bits }),
);
}
}
if let Some(class_layout) = def.class_layout() {
file.ClassLayout(
type_def,
class_layout.packing_size(),
class_layout.class_size(),
);
}
for inner_def in index.nested(def) {
write_type(file, index, inner_def, Some(type_def), Some(0));
}
}
fn type_sig(index: &reader::Index, def: reader::TypeDef) -> String {
let fields: Vec<String> = def
.fields()
.map(|f| {
let val = f
.constant()
.map(|c| format!("{:?}", c.value()))
.unwrap_or_default();
format!("{}:{:?}={val}", f.name(), f.ty())
})
.collect();
let methods: Vec<String> = def
.methods()
.map(|m| format!("{}:{:?}", m.name(), m.signature(&[])))
.collect();
let layout = def
.class_layout()
.map(|l| (l.packing_size(), l.class_size()));
let align = def
.find_attribute("AlignmentAttribute")
.map(|a| format!("{:?}", a.value()));
let nested: Vec<String> = index
.nested(def)
.map(|inner| format!("{}={}", inner.name(), type_sig(index, inner)))
.collect();
format!(
"{fields:?}|{methods:?}|{layout:?}|{align:?}|{:?}|{nested:?}",
def.flags()
)
}
fn write_attributes<'a, R: HasAttributes<'a>>(
file: &mut writer::File,
parent: writer::HasAttribute,
row: R,
) {
write_attributes_with_arch(file, parent, row, None);
}
fn write_attributes_with_arch<'a, R: HasAttributes<'a>>(
file: &mut writer::File,
parent: writer::HasAttribute,
row: R,
arch_override: Option<i32>,
) {
for attribute in row.attributes() {
let ctor = attribute.ctor();
let ty = ctor.parent();
if arch_override.is_some()
&& ty.namespace() == "Windows.Win32.Metadata"
&& ty.name() == "SupportedArchitectureAttribute"
{
continue;
}
let attribute_ref =
writer::MemberRefParent::TypeRef(file.TypeRef(ty.namespace(), ty.name()));
let ctor_ref = file.MemberRef(".ctor", &ctor.signature(&[]), attribute_ref);
file.Attribute(
parent,
writer::AttributeType::MemberRef(ctor_ref),
&attribute.value(),
);
}
if let Some(arch_bits) = arch_override
&& arch_bits != 0
{
write_supported_architecture_attr(file, parent, arch_bits);
}
}
fn write_supported_architecture_attr(
file: &mut writer::File,
parent: writer::HasAttribute,
arch_bits: i32,
) {
let ns = "Windows.Win32.Metadata";
let name = "SupportedArchitectureAttribute";
let type_ref = writer::MemberRefParent::TypeRef(file.TypeRef(ns, name));
let sig = Signature {
flags: MethodCallAttributes::HASTHIS,
return_type: Type::Void,
types: vec![Type::I32],
};
let ctor_ref = file.MemberRef(".ctor", &sig, type_ref);
file.Attribute(
parent,
writer::AttributeType::MemberRef(ctor_ref),
&[(String::new(), Value::I32(arch_bits))],
);
}