blob: 534c35ecc3990b8353e21409a6caddc95886567a [file] [log] [blame]
use super::*;
use metadata::{AsRow, HasAttributes};
pub fn from_reader(reader: &metadata::Reader, config: std::collections::BTreeMap<&str, &str>, output: &str) -> Result<()> {
let mut writer = Writer::new(output);
// TODO: do we need any configuration values for winmd generation?
// Maybe per-namespace winmd files for namespace-splitting - be sure to use
// the same key as for winmd generation.
if let Some((key, _)) = config.first_key_value() {
return Err(Error::new(&format!("invalid configuration value `{key}`")));
}
// TODO: just use the reader directly since we now have everything in the reader, there's no need to abstract
// away the source format. Few reprs is always better.
for item in reader.items() {
// TODO: cover all variants
let metadata::Item::Type(def) = item else {
continue;
};
let generics = &metadata::type_def_generics(def);
let extends = if let Some(extends) = def.extends() { writer.insert_type_ref(extends.namespace, extends.name) } else { TypeDefOrRef::none() };
writer.tables.TypeDef.push(TypeDef {
Extends: extends,
FieldList: writer.tables.Field.len() as u32,
Flags: def.flags().0,
MethodList: writer.tables.MethodDef.len() as u32,
TypeName: writer.strings.insert(def.name()),
TypeNamespace: writer.strings.insert(def.namespace()),
});
let def_ref = writer.tables.TypeDef.len() as u32 - 1;
for generic in def.generics() {
writer.tables.GenericParam.push(GenericParam {
Number: generic.number(), // TODO: isn't this just going to be incremental?
Flags: 0,
Owner: TypeOrMethodDef::TypeDef(def_ref),
Name: writer.strings.insert(generic.name()),
});
}
for interface in metadata::type_def_interfaces(def, generics) {
let ty = winmd_type(&interface.ty);
let reference = match &ty {
Type::TypeRef(type_name) if type_name.generics.is_empty() => writer.insert_type_ref(&type_name.namespace, &type_name.name),
Type::TypeRef(_) => writer.insert_type_spec(ty),
Type::IUnknown => writer.insert_type_ref("Windows.Win32.System.Com", "IUnknown"),
Type::IInspectable => writer.insert_type_ref("Windows.Win32.System.WinRT", "IInspectable"),
rest => unimplemented!("{rest:?}"),
};
writer.tables.InterfaceImpl.push(InterfaceImpl { Class: def_ref, Interface: reference });
}
// TODO: if the class is "Apis" then should we sort the fields (constants) and methods (functions) for stability
for field in def.fields() {
let ty = winmd_type(&field.ty(Some(def)));
let signature = writer.insert_field_sig(&ty);
writer.tables.Field.push(Field { Flags: field.flags().0, Name: writer.strings.insert(field.name()), Signature: signature });
if let Some(constant) = field.constant() {
writer.tables.Constant.push(Constant { Type: constant.usize(0) as u16, Parent: HasConstant::Field(writer.tables.Field.len() as u32 - 1), Value: writer.blobs.insert(&constant.blob(2)) })
}
}
for method in def.methods() {
let signature = method.signature(generics);
let return_type = winmd_type(&signature.return_type);
let param_types: Vec<Type> = signature.params.iter().map(winmd_type).collect();
let signature = writer.insert_method_sig(signature.call_flags, &return_type, &param_types);
writer.tables.MethodDef.push(MethodDef {
RVA: 0,
ImplFlags: method.impl_flags().0,
Flags: method.flags().0,
Name: writer.strings.insert(method.name()),
Signature: signature,
ParamList: writer.tables.Param.len() as u32,
});
for param in method.params() {
writer.tables.Param.push(Param { Flags: param.flags().0, Sequence: param.sequence(), Name: writer.strings.insert(param.name()) });
}
}
for attribute in def.attributes() {
let metadata::AttributeType::MemberRef(attribute_ctor) = attribute.ty();
assert_eq!(attribute_ctor.name(), ".ctor");
let metadata::MemberRefParent::TypeRef(attribute_type) = attribute_ctor.parent();
let attribute_type_ref = if let TypeDefOrRef::TypeRef(type_ref) = writer.insert_type_ref(attribute_type.namespace(), attribute_type.name()) { MemberRefParent::TypeRef(type_ref) } else { panic!() };
let signature = attribute_ctor.signature();
let return_type = winmd_type(&signature.return_type);
let param_types: Vec<Type> = signature.params.iter().map(winmd_type).collect();
let signature = writer.insert_method_sig(signature.call_flags, &return_type, &param_types);
writer.tables.MemberRef.push(MemberRef { Class: attribute_type_ref, Name: writer.strings.insert(".ctor"), Signature: signature });
let mut values = 1u16.to_le_bytes().to_vec(); // prolog
let args = attribute.args();
let mut named_arg_count = false;
for (index, (name, value)) in args.iter().enumerate() {
value_blob(value, &mut values);
if !named_arg_count && !name.is_empty() {
named_arg_count = true;
let named_arg_count = (args.len() - index) as u16;
values.extend_from_slice(&named_arg_count.to_le_bytes());
break;
}
}
if !named_arg_count {
values.extend_from_slice(&0u16.to_le_bytes());
}
let values = writer.blobs.insert(&values);
writer.tables.CustomAttribute.push(CustomAttribute { Parent: HasAttribute::TypeDef(def_ref), Type: AttributeType::MemberRef(writer.tables.MemberRef.len() as u32 - 1), Value: values });
}
}
// TODO: In theory, `config` could instruct this function to balance the types across a number of winmd files
// like mdmerge supports for namespace-splitting.
write_to_file(output, writer.into_stream()).map_err(|err| err.with_path(output))
}
// TODO: need a Blob type for writing
fn value_blob(value: &metadata::Value, blob: &mut Vec<u8>) {
match value {
metadata::Value::Bool(value) => {
if *value {
blob.push(1)
} else {
blob.push(0)
}
}
metadata::Value::U32(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::I32(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::U16(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::U8(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::EnumDef(_def, value) => value_blob(value, blob),
metadata::Value::TypeName(value) => {
let value = value.to_string();
usize_blob(value.len(), blob);
blob.extend_from_slice(value.as_bytes());
}
metadata::Value::String(value) => {
usize_blob(value.len(), blob);
blob.extend_from_slice(value.as_bytes());
}
rest => unimplemented!("{rest:?}"),
}
}
// TODO: keep the basic type conversion
fn winmd_type(ty: &metadata::Type) -> Type {
match ty {
metadata::Type::Void => Type::Void,
metadata::Type::Bool => Type::Bool,
metadata::Type::Char => Type::Char,
metadata::Type::I8 => Type::I8,
metadata::Type::U8 => Type::U8,
metadata::Type::I16 => Type::I16,
metadata::Type::U16 => Type::U16,
metadata::Type::I32 => Type::I32,
metadata::Type::U32 => Type::U32,
metadata::Type::I64 => Type::I64,
metadata::Type::U64 => Type::U64,
metadata::Type::F32 => Type::F32,
metadata::Type::F64 => Type::F64,
metadata::Type::ISize => Type::ISize,
metadata::Type::USize => Type::USize,
metadata::Type::String => Type::String,
metadata::Type::GUID => Type::GUID,
metadata::Type::IUnknown => Type::IUnknown,
metadata::Type::IInspectable => Type::IInspectable,
metadata::Type::HRESULT => Type::HRESULT,
metadata::Type::PSTR => Type::PSTR,
metadata::Type::PWSTR => Type::PWSTR,
metadata::Type::PCSTR => Type::PCSTR,
metadata::Type::PCWSTR => Type::PCWSTR,
metadata::Type::BSTR => Type::BSTR,
metadata::Type::Type => Type::Type,
metadata::Type::TypeDef(def, generics) => Type::TypeRef(TypeName { namespace: def.namespace().to_string(), name: def.name().to_string(), generics: generics.iter().map(winmd_type).collect() }),
metadata::Type::GenericParam(generic) => Type::GenericParam(generic.number()),
metadata::Type::ConstRef(ty) => Type::ConstRef(Box::new(winmd_type(ty))),
metadata::Type::WinrtArrayRef(ty) => Type::WinrtArrayRef(Box::new(winmd_type(ty))),
metadata::Type::WinrtArray(ty) => Type::WinrtArray(Box::new(winmd_type(ty))),
metadata::Type::MutPtr(ty, pointers) => Type::MutPtr(Box::new(winmd_type(ty)), *pointers),
metadata::Type::ConstPtr(ty, pointers) => Type::ConstPtr(Box::new(winmd_type(ty)), *pointers),
metadata::Type::Win32Array(ty, len) => Type::Win32Array(Box::new(winmd_type(ty)), *len),
rest => unimplemented!("{rest:?}"),
}
}