diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3603e4e956..9827eb2b1f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -89,7 +89,7 @@ jobs: if: contains(matrix.other, 'windows-gnu') - name: Check (${{ matrix.os }}) - run: cargo check + run: cargo check --target ${{ matrix.other }} if: contains(matrix.other, 'windows-gnu') - name: Test (${{ matrix.os }}) diff --git a/crates/deps/gen2/Cargo.toml b/crates/deps/gen2/Cargo.toml new file mode 100644 index 0000000000..1ea89e3c47 --- /dev/null +++ b/crates/deps/gen2/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "windows_gen2" +version = "0.28.0" +authors = ["Microsoft"] +edition = "2018" +license = "MIT OR Apache-2.0" +description = "Code gen support for the windows crate" + +[dependencies] +quote = { package = "windows_quote", path = "../quote", version = "0.28.0" } +reader = { package = "windows_reader", path = "../reader", version = "0.28.0" } diff --git a/crates/deps/gen2/src/callback.rs b/crates/deps/gen2/src/callback.rs new file mode 100644 index 0000000000..8ece5a2306 --- /dev/null +++ b/crates/deps/gen2/src/callback.rs @@ -0,0 +1,22 @@ +use super::*; + +pub fn gen_callback(def: &TypeDef, gen: &Gen) -> TokenStream { + let name = gen_type_name(def, gen); + let method = def.invoke_method(); + let signature = method.signature(&[]); + let return_sig = gen_return_sig(&signature, gen); + let arch_cfg = gen.arch_cfg(def.attributes()); + let feature_cfg = gen.method_cfg(&method).0; + + let params = signature.params.iter().map(|p| { + let name = gen_param_name(&p.param); + let tokens = gen_abi_param_sig(p, gen); + quote! { #name: #tokens } + }); + + quote! { + #arch_cfg + #feature_cfg + pub type #name = ::core::option::Option; + } +} diff --git a/crates/deps/gen2/src/class.rs b/crates/deps/gen2/src/class.rs new file mode 100644 index 0000000000..6610479e98 --- /dev/null +++ b/crates/deps/gen2/src/class.rs @@ -0,0 +1,19 @@ +use super::*; + +pub fn gen_class(def: &TypeDef, gen: &Gen) -> TokenStream { + if gen.sys { + let has_default = def.interface_impls().any(|interface| interface.is_default()); + + if has_default { + let name: TokenStream = def.name().into(); + + quote! { + pub type #name = *mut ::core::ffi::c_void; + } + } else { + quote! {} + } + } else { + quote! {} + } +} diff --git a/crates/deps/gen2/src/com_interface.rs b/crates/deps/gen2/src/com_interface.rs new file mode 100644 index 0000000000..dd13e8b7ed --- /dev/null +++ b/crates/deps/gen2/src/com_interface.rs @@ -0,0 +1,15 @@ +use super::*; + +// TODO: add support (even for windows-sys) behind implement feature to implement COM/WinRT interfaces +// TODO: add skeleton support for calling COM/WinRT interfaces for windows-sys and providing their GUIDs +pub fn gen_com_interface(def: &TypeDef, gen: &Gen) -> TokenStream { + if gen.sys { + let name: TokenStream = def.name().into(); + + quote! { + pub type #name = *mut ::core::ffi::c_void; + } + } else { + quote! {} + } +} diff --git a/crates/deps/gen2/src/constant.rs b/crates/deps/gen2/src/constant.rs new file mode 100644 index 0000000000..285e199536 --- /dev/null +++ b/crates/deps/gen2/src/constant.rs @@ -0,0 +1,126 @@ +#![allow(clippy::many_single_char_names)] + +use super::*; + +pub fn gen_constant(def: &Field, gen: &Gen) -> TokenStream { + let name = def.name(); + let name = gen_ident(name); + let signature = def.signature(None); + + let cfg = gen.field_cfg(def); + + if let Some(constant) = def.constant() { + if signature.kind == constant.value_type() { + let value = gen_constant_type_value(&constant.value()); + quote! { + pub const #name: #value; + } + } else { + let kind = gen_sig(&signature, gen); + let value = gen_constant_value(&constant.value()); + + let value = if signature.kind.underlying_type() == constant.value_type() { + value + } else { + quote! { #value as _ } + }; + + if gen.sys && (signature.kind == constant.value_type() || signature.kind.is_handle() || signature.kind == ElementType::HRESULT) { + quote! { + #cfg + pub const #name: #kind = #value; + } + } else { + quote! { + #cfg + pub const #name: #kind = #kind(#value); + } + } + } + } else if let Some(guid) = GUID::from_attributes(def.attributes()) { + let value = gen_guid(&guid, gen); + let guid = gen_element_name(&ElementType::GUID, gen); + quote! { pub const #name: #guid = #value; } + } else if let Some((guid, id)) = get_property_key(def.attributes()) { + let kind = gen_sig(&signature, gen); + let guid = gen_guid(&guid, gen); + quote! { + #cfg + pub const #name: #kind = #kind { + fmtid: #guid, + pid: #id, + }; + } + } else { + quote! {} + } +} + +pub fn gen_constant_type_value(value: &ConstantValue) -> TokenStream { + match value { + ConstantValue::Bool(value) => quote! { bool = #value }, + ConstantValue::U8(value) => quote! { u8 = #value }, + ConstantValue::I8(value) => quote! { i8 = #value }, + ConstantValue::U16(value) => quote! { u16 = #value }, + ConstantValue::I16(value) => quote! { i16 = #value }, + ConstantValue::U32(value) => quote! { u32 = #value }, + ConstantValue::I32(value) => quote! { i32 = #value }, + ConstantValue::U64(value) => quote! { u64 = #value }, + ConstantValue::I64(value) => quote! { i64 = #value }, + ConstantValue::F32(value) => quote! { f32 = #value }, + ConstantValue::F64(value) => quote! { f64 = #value }, + ConstantValue::String(value) => quote! { &'static str = #value }, + _ => unimplemented!(), + } +} + +pub fn gen_guid(value: &GUID, gen: &Gen) -> TokenStream { + let guid = gen_element_name(&ElementType::GUID, gen); + + if gen.sys { + let a = Literal::u32_unsuffixed(value.0); + let b = Literal::u16_unsuffixed(value.1); + let c = Literal::u16_unsuffixed(value.2); + let d = Literal::u8_unsuffixed(value.3); + let e = Literal::u8_unsuffixed(value.4); + let f = Literal::u8_unsuffixed(value.5); + let g = Literal::u8_unsuffixed(value.6); + let h = Literal::u8_unsuffixed(value.7); + let i = Literal::u8_unsuffixed(value.8); + let j = Literal::u8_unsuffixed(value.9); + let k = Literal::u8_unsuffixed(value.10); + + // TODO: once code complete measure how much longer it takes if-any to use from_u128 to produce a more compact package + + quote! { + #guid { data1:#a, data2:#b, data3:#c, data4:[#d, #e, #f, #g, #h, #i, #j, #k] } + } + } else { + format!("{}::from_u128(0x{:08x?}_{:04x?}_{:04x?}_{:02x?}{:02x?}_{:02x?}{:02x?}{:02x?}{:02x?}{:02x?}{:02x?})", guid.into_string(), value.0, value.1, value.2, value.3, value.4, value.5, value.6, value.7, value.8, value.9, value.10).into() + } +} + +pub fn gen_constant_value(value: &ConstantValue) -> TokenStream { + match value { + ConstantValue::Bool(value) => quote! { #value }, + ConstantValue::U8(value) => quote! { #value }, + ConstantValue::I8(value) => quote! { #value }, + ConstantValue::U16(value) => quote! { #value }, + ConstantValue::I16(value) => quote! { #value }, + ConstantValue::U32(value) => quote! { #value }, + ConstantValue::I32(value) => quote! { #value }, + ConstantValue::U64(value) => quote! { #value }, + ConstantValue::I64(value) => quote! { #value }, + ConstantValue::F32(value) => quote! { #value }, + ConstantValue::F64(value) => quote! { #value }, + ConstantValue::String(value) => quote! { #value }, + _ => unimplemented!(), + } +} + +fn get_property_key(attributes: impl Iterator) -> Option<(GUID, u32)> { + attributes.into_iter().find(|attribute| attribute.name() == "PropertyKeyAttribute").map(|attribute| { + let args = attribute.args(); + (GUID::from_args(&args), args[11].1.unwrap_u32()) + }) +} diff --git a/crates/deps/gen2/src/delegate.rs b/crates/deps/gen2/src/delegate.rs new file mode 100644 index 0000000000..5013f8c3f1 --- /dev/null +++ b/crates/deps/gen2/src/delegate.rs @@ -0,0 +1,13 @@ +use super::*; + +pub fn gen_delegate(def: &TypeDef, gen: &Gen) -> TokenStream { + if gen.sys { + let name = gen_generic_ident(def.name()); + + quote! { + pub type #name = *mut ::core::ffi::c_void; + } + } else { + quote! {} + } +} diff --git a/crates/deps/gen2/src/enum.rs b/crates/deps/gen2/src/enum.rs new file mode 100644 index 0000000000..aed75e1893 --- /dev/null +++ b/crates/deps/gen2/src/enum.rs @@ -0,0 +1,61 @@ +use super::*; + +pub fn gen_enum(def: &TypeDef, gen: &Gen) -> TokenStream { + // TODO: use same representation for unscoped enums + if gen.sys { + gen_sys_enum(def, gen) + } else { + quote! {} + } +} + +fn gen_sys_enum(def: &TypeDef, gen: &Gen) -> TokenStream { + let name = gen_ident(def.name()); + let underlying_type = def.underlying_type(); + let underlying_type = gen_element_name(&underlying_type, gen); + + let fields = def.fields().filter_map(|field| { + if field.is_literal() { + let field_name = gen_ident(field.name()); + let constant = field.constant().unwrap(); + let value = gen_constant_value(&constant.value()); + + Some((field_name, value)) + } else { + None + } + }); + + if def.is_scoped() { + let fields = fields.map(|(field_name, value)| { + quote! { + pub const #field_name: Self = Self(#value); + } + }); + + quote! { + #[repr(transparent)] + pub struct #name(pub #underlying_type); + impl #name { + #(#fields)* + } + impl ::core::marker::Copy for #name {} + impl ::core::clone::Clone for #name { + fn clone(&self) -> Self { + *self + } + } + } + } else { + let fields = fields.map(|(field_name, value)| { + quote! { + pub const #field_name: #name = #value; + } + }); + + quote! { + pub type #name = #underlying_type; + #(#fields)* + } + } +} diff --git a/crates/deps/gen2/src/function.rs b/crates/deps/gen2/src/function.rs new file mode 100644 index 0000000000..48c81498ef --- /dev/null +++ b/crates/deps/gen2/src/function.rs @@ -0,0 +1,63 @@ +use super::*; + +pub fn gen_functions(tree: &TypeTree, gen: &Gen) -> TokenStream { + let mut functions = tree.types.values().map(|entry| gen_function_if(entry, gen)).peekable(); + + if functions.peek().is_some() { + quote! { + #[link(name = "windows")] + extern "system" { + #(#functions)* + } + } + } else { + quote! {} + } +} + +pub fn gen_function(def: &MethodDef, gen: &Gen) -> TokenStream { + if gen.sys { + let function = gen_function_decl(def, gen); + + quote! { + #[link(name = "windows")] + extern "system" { + #function + } + } + } else { + quote! {} + } +} + +fn gen_function_if(entry: &TypeEntry, gen: &Gen) -> TokenStream { + let mut tokens = TokenStream::new(); + + for def in &entry.def { + if let ElementType::MethodDef(def) = def { + tokens.combine(&gen_function_decl(def, gen)); + } + } + + tokens +} + +fn gen_function_decl(def: &MethodDef, gen: &Gen) -> TokenStream { + let name = gen_ident(def.name()); + let signature = def.signature(&[]); + let return_type = gen_return_sig(&signature, gen); + let arch_cfg = gen.arch_cfg(def.attributes()); + let feature_cfg = gen.method_cfg(def).0; + + let params = signature.params.iter().map(|p| { + let name = gen_param_name(&p.param); + let tokens = gen_param_sig(p, gen); + quote! { #name: #tokens } + }); + + quote! { + #arch_cfg + #feature_cfg + pub fn #name(#(#params),*) #return_type; + } +} diff --git a/crates/deps/gen2/src/gen.rs b/crates/deps/gen2/src/gen.rs new file mode 100644 index 0000000000..901ed8e27a --- /dev/null +++ b/crates/deps/gen2/src/gen.rs @@ -0,0 +1,205 @@ +use super::*; +use std::collections::*; + +#[derive(Default)] +pub struct Gen<'a> { + pub namespace: &'a str, + pub inherit: bool, // generated inherited methods + pub sys: bool, + pub flatten: bool, + pub cfg: bool, +} + +impl Gen<'_> { + pub(crate) fn namespace(&self, namespace: &str) -> TokenStream { + if self.flatten || namespace == self.namespace { + quote! {} + } else { + let mut relative = self.namespace.split('.').peekable(); + let mut namespace = namespace.split('.').peekable(); + + while relative.peek() == namespace.peek() { + if relative.next().is_none() { + break; + } + + namespace.next(); + } + + let mut tokens = TokenStream::with_capacity(); + + for _ in 0..relative.count() { + tokens.push_str("super::"); + } + + for namespace in namespace { + tokens.push_str(namespace); + tokens.push_str("::"); + } + + tokens + } + } + + pub(crate) fn arch_cfg(&self, attributes: impl Iterator) -> TokenStream { + for attribute in attributes { + if attribute.name() == "SupportedArchitectureAttribute" { + if let Some((_, ConstantValue::I32(value))) = attribute.args().get(0) { + let mut cfg = "#[cfg(any(".to_string(); + if value & 1 == 1 { + cfg.push_str(r#"target_arch = "x86", "#); + } + if value & 2 == 2 { + cfg.push_str(r#"target_arch = "x86_64", "#); + } + if value & 4 == 4 { + cfg.push_str(r#"target_arch = "aarch64", "#); + } + cfg.push_str("))]"); + return cfg.into(); + } + } + } + + quote! {} + } + + pub(crate) fn type_cfg(&self, def: &TypeDef) -> TokenStream { + if !self.cfg { + quote! {} + } else { + let mut namespaces = BTreeSet::new(); + let mut keys = HashSet::new(); + self.type_requirements(def, &mut namespaces, &mut keys); + cfg(&namespaces) + } + } + + pub(crate) fn field_cfg(&self, def: &Field) -> TokenStream { + if !self.cfg { + quote! {} + } else { + let mut namespaces = BTreeSet::new(); + let mut keys = HashSet::new(); + self.field_requirements(def, None, &mut namespaces, &mut keys); + cfg(&namespaces) + } + } + + pub(crate) fn method_cfg(&self, def: &MethodDef) -> (TokenStream, TokenStream) { + if !self.cfg { + (quote! {}, quote! {}) + } else { + let mut namespaces = BTreeSet::new(); + let mut keys = HashSet::new(); + self.method_requirements(&def.signature(&[]), &mut namespaces, &mut keys); + (cfg(&namespaces), cfg_not(&namespaces)) + } + } + + fn element_requirements(&self, def: &ElementType, namespaces: &mut BTreeSet<&'static str>, keys: &mut HashSet) { + match def { + ElementType::TypeDef(def) => self.type_requirements(def, namespaces, keys), + ElementType::Array((signature, _)) => self.element_requirements(&signature.kind, namespaces, keys), + _ => {} + } + } + + fn type_requirements(&self, def: &TypeDef, namespaces: &mut BTreeSet<&'static str>, keys: &mut HashSet) { + if !keys.insert(def.row.clone()) { + return; + } + + fn add_namespace(namespace: &'static str, namespaces: &mut BTreeSet<&'static str>, gen: &Gen) { + if !namespace.is_empty() && namespace != gen.namespace { + //namespaces.insert(namespace); + + // TODO: use the above instead to iclude parent dependencies + if !gen.namespace.starts_with(format!("{}.", namespace).as_str()) { + namespaces.insert(namespace); + } + } + } + + add_namespace(def.namespace(), namespaces, self); + + for generic in &def.generics { + self.element_requirements(generic, namespaces, keys); + } + + match def.kind() { + TypeKind::Class => { + if let Some(def) = def.default_interface() { + add_namespace(def.namespace(), namespaces, self); + } + } + TypeKind::Struct => { + def.fields().for_each(|field| self.field_requirements(&field, Some(def), namespaces, keys)); + + // TODO: needed? + if let Some(def) = def.is_convertible_to() { + add_namespace(def.type_name().namespace, namespaces, self); + } + } + TypeKind::Delegate => self.method_requirements(&def.invoke_method().signature(&[]), namespaces, keys), + _ => {} + } + + if let Some(entry) = TypeReader::get().get_type_entry(def.type_name()) { + for def in &entry.def { + if let ElementType::TypeDef(def) = def { + self.type_requirements(def, namespaces, keys); + } + } + } + } + + fn method_requirements(&self, def: &MethodSignature, namespaces: &mut BTreeSet<&'static str>, keys: &mut HashSet) { + def.return_sig.iter().for_each(|def| self.element_requirements(&def.kind, namespaces, keys)); + def.params.iter().for_each(|def| self.element_requirements(&def.signature.kind, namespaces, keys)); + } + + fn field_requirements(&self, def: &Field, enclosing: Option<&TypeDef>, namespaces: &mut BTreeSet<&'static str>, keys: &mut HashSet) { + self.element_requirements(&def.signature(enclosing).kind, namespaces, keys); + } +} + +fn cfg(namespaces: &BTreeSet<&'static str>) -> TokenStream { + if namespaces.is_empty() { + quote! {} + } else { + format!("#[cfg({})]", namespaces_to_features(namespaces)).into() + } +} + +fn cfg_not(namespaces: &BTreeSet<&'static str>) -> TokenStream { + if namespaces.is_empty() { + quote! {} + } else { + format!("#[cfg(not({}))]", namespaces_to_features(namespaces)).into() + } +} + +fn namespaces_to_features(namespaces: &BTreeSet<&'static str>) -> String { + let mut features = String::new(); + + for namespace in namespaces { + features.push_str("feature = \""); + + for namespace in namespace.split('.').skip(1) { + features.push_str(namespace); + features.push('_'); + } + + features.truncate(features.len() - 1); + features.push_str("\",") + } + + features.truncate(features.len() - 1); + + if namespaces.len() > 1 { + format!("all({})", features) + } else { + features + } +} diff --git a/crates/deps/gen2/src/lib.rs b/crates/deps/gen2/src/lib.rs new file mode 100644 index 0000000000..e41aa78ab1 --- /dev/null +++ b/crates/deps/gen2/src/lib.rs @@ -0,0 +1,110 @@ +mod callback; +mod class; +mod com_interface; +mod constant; +mod delegate; +mod r#enum; +mod function; +mod gen; +mod name; +mod sig; +mod r#struct; +mod winrt_interface; + +use callback::*; +use class::*; +use com_interface::*; +use constant::*; +use delegate::*; +use function::*; +pub use gen::*; +use name::*; +use r#enum::*; +use r#struct::*; +use sig::*; +use winrt_interface::*; + +use quote::*; +use reader::*; + +pub fn gen_type(name: &str, gen: &Gen) -> String { + let reader = TypeReader::get(); + let mut tokens = String::new(); + + for def in reader.get_type_entry(TypeName::parse(name)).iter().flat_map(|entry| entry.def.iter()) { + tokens.push_str(gen_element_type(def, gen).as_str()); + } + + tokens +} + +pub fn gen_namespace(gen: &Gen) -> String { + let tree = TypeReader::get().get_namespace(gen.namespace).expect("Namespace not found"); + + let namespaces = tree.namespaces.iter().map(move |(name, tree)| { + if tree.namespace == "Windows.Win32.Interop" { + return quote! {}; + } + + let name = gen_ident(name); + let namespace = tree.namespace[tree.namespace.find('.').unwrap() + 1..].replace('.', "_"); + quote! { + #[cfg(feature = #namespace)] pub mod #name; + } + }); + + let functions = gen_functions(tree, gen); + let types = gen_non_function_types(tree, gen); + + let tokens = quote! { + #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals, clashing_extern_declarations, clippy::all)] + #(#namespaces)* + #functions + #types + }; + + tokens.into_string() +} + +fn gen_non_function_types(tree: &TypeTree, gen: &Gen) -> TokenStream { + let mut tokens = TokenStream::new(); + + for entry in tree.types.values() { + for def in &entry.def { + match def { + ElementType::MethodDef(_) => {} + ElementType::Field(_) | ElementType::TypeDef(_) => tokens.combine(&gen_element_type(def, gen)), + _ => {} + } + } + } + + tokens +} + +fn gen_element_type(def: &ElementType, gen: &Gen) -> TokenStream { + match def { + ElementType::Field(def) => gen_constant(def, gen), + ElementType::TypeDef(def) => match def.kind() { + TypeKind::Class => gen_class(def, gen), + TypeKind::Enum => gen_enum(def, gen), + TypeKind::Struct => gen_struct(def, gen), + TypeKind::Interface => { + if def.is_winrt() { + gen_winrt_interface(def, gen) + } else { + gen_com_interface(def, gen) + } + } + TypeKind::Delegate => { + if def.is_winrt() { + gen_delegate(def, gen) + } else { + gen_callback(def, gen) + } + } + }, + ElementType::MethodDef(def) => gen_function(def, gen), + _ => quote! {}, + } +} diff --git a/crates/deps/gen2/src/name.rs b/crates/deps/gen2/src/name.rs new file mode 100644 index 0000000000..755c0c2d1d --- /dev/null +++ b/crates/deps/gen2/src/name.rs @@ -0,0 +1,125 @@ +use super::*; + +pub fn gen_ident(name: &str) -> TokenStream { + // keywords list based on https://doc.rust-lang.org/reference/keywords.html + match name { + "abstract" | "as" | "become" | "box" | "break" | "const" | "continue" | "crate" | "do" | "else" | "enum" | "extern" | "false" | "final" | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut" | "override" | "priv" | "pub" | "ref" | "return" | "static" | "struct" | "super" | "trait" | "true" | "type" | "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" | "while" | "yield" | "try" | "async" | "await" | "dyn" => format!("r#{}", name).into(), + "Self" | "self" => format!("{}_", name).into(), + "_" => "unused".into(), + _ => name.into(), + } +} + +pub fn gen_generic_ident(name: &str) -> TokenStream { + let len = name.len(); + let len = name.as_bytes().get(len - 2).map_or_else(|| len, |c| if *c == b'`' { len - 2 } else { len }); + gen_ident(&name[..len]) +} + +pub fn gen_param_name(param: &Param) -> TokenStream { + gen_ident(¶m.name().to_lowercase()) +} + +pub fn gen_element_name(def: &ElementType, gen: &Gen) -> TokenStream { + match def { + ElementType::Void => quote! { ::core::ffi::c_void }, + ElementType::Bool => quote! { bool }, + ElementType::Char => quote! { u16 }, + ElementType::I8 => quote! { i8 }, + ElementType::U8 => quote! { u8 }, + ElementType::I16 => quote! { i16 }, + ElementType::U16 => quote! { u16 }, + ElementType::I32 => quote! { i32 }, + ElementType::U32 => quote! { u32 }, + ElementType::I64 => quote! { i64 }, + ElementType::U64 => quote! { u64 }, + ElementType::F32 => quote! { f32 }, + ElementType::F64 => quote! { f64 }, + ElementType::ISize => quote! { isize }, + ElementType::USize => quote! { usize }, + ElementType::String => { + let crate_name = gen_crate_name(gen); + quote! { ::#crate_name::core::HSTRING } + } + ElementType::IInspectable => { + let crate_name = gen_crate_name(gen); + quote! { ::#crate_name::core::IInspectable } + } + ElementType::GUID => { + let crate_name = gen_crate_name(gen); + quote! { ::#crate_name::core::GUID } + } + ElementType::IUnknown => { + let crate_name = gen_crate_name(gen); + quote! { ::#crate_name::core::IUnknown } + } + ElementType::HRESULT => { + let crate_name = gen_crate_name(gen); + quote! { ::#crate_name::core::HRESULT } + } + ElementType::Array((kind, len)) => { + let name = gen_sig(kind, gen); + let len = Literal::u32_unsuffixed(*len); + quote! { [#name; #len] } + } + ElementType::GenericParam(generic) => generic.into(), + ElementType::MethodDef(def) => def.name().into(), + ElementType::Field(field) => field.name().into(), + ElementType::TypeDef(t) => gen_type_name(t, gen), + _ => unimplemented!(), + } +} + +pub fn gen_crate_name(gen: &Gen) -> TokenStream { + if gen.sys { + "windows_sys".into() + } else { + "windows".into() + } +} + +pub fn gen_type_name(def: &TypeDef, gen: &Gen) -> TokenStream { + format_name(def, gen, gen_ident, false) +} + +fn format_name(def: &TypeDef, gen: &Gen, format_name: F, turbo: bool) -> TokenStream +where + F: FnOnce(&str) -> TokenStream, +{ + let type_name = def.type_name(); + + if type_name.namespace.is_empty() { + format_name(&scoped_name(def)) + } else { + let mut namespace = gen.namespace(type_name.namespace); + let name = format_name(type_name.name); + + if def.generics.is_empty() || gen.sys { + namespace.combine(&name); + namespace + } else { + let colon_separated = if turbo || !namespace.as_str().is_empty() { + quote! { :: } + } else { + quote! {} + }; + + let generics = def.generics.iter().map(|g| gen_element_name(g, gen)); + quote! { #namespace#name#colon_separated<#(#generics),*> } + } + } +} + +fn scoped_name(def: &TypeDef) -> String { + if let Some(enclosing_type) = def.enclosing_type() { + if let Some(nested_types) = enclosing_type.nested_types() { + for (index, (nested_type, _)) in nested_types.iter().enumerate() { + if *nested_type == def.name() { + return format!("{}_{}", &scoped_name(&enclosing_type), index); + } + } + } + } + + def.name().to_string() +} diff --git a/crates/deps/gen2/src/sig.rs b/crates/deps/gen2/src/sig.rs new file mode 100644 index 0000000000..46016a5272 --- /dev/null +++ b/crates/deps/gen2/src/sig.rs @@ -0,0 +1,62 @@ +use super::*; + +pub fn gen_sig(sig: &Signature, gen: &Gen) -> TokenStream { + gen_sig_with_const(sig, gen, sig.is_const) +} + +pub fn gen_param_sig(param: &MethodParam, gen: &Gen) -> TokenStream { + gen_sig_with_const(¶m.signature, gen, !param.param.flags().output()) +} + +pub fn gen_abi_param_sig(param: &MethodParam, gen: &Gen) -> TokenStream { + gen_abi_sig_with_const(¶m.signature, gen, !param.param.flags().output()) +} + +pub fn gen_return_sig(signature: &MethodSignature, gen: &Gen) -> TokenStream { + if let Some(return_sig) = &signature.return_sig { + let tokens = gen_sig(return_sig, gen); + quote! { -> #tokens } + } else { + quote! {} + } +} + +fn gen_abi_sig_with_const(sig: &Signature, gen: &Gen, is_const: bool) -> TokenStream { + let mut tokens = TokenStream::with_capacity(); + + for _ in 0..sig.pointers { + if is_const { + tokens.combine("e! { *const }); + } else { + tokens.combine("e! { *mut }); + } + } + + tokens.combine(&gen_element_name(&sig.kind, gen)); + tokens +} + +fn gen_sig_with_const(sig: &Signature, gen: &Gen, is_const: bool) -> TokenStream { + let mut tokens = TokenStream::with_capacity(); + + for _ in 0..sig.pointers { + if is_const { + tokens.combine("e! { *const }); + } else { + tokens.combine("e! { *mut }); + } + } + + let kind = gen_element_name(&sig.kind, gen); + + // TODO: harmonize these across sys/win + if sig.kind.is_nullable() && !gen.sys { + tokens.combine("e! { + ::core::option::Option<#kind> + }); + } else { + tokens.combine(&kind) + } + + tokens +} diff --git a/crates/deps/gen2/src/struct.rs b/crates/deps/gen2/src/struct.rs new file mode 100644 index 0000000000..e17c90de07 --- /dev/null +++ b/crates/deps/gen2/src/struct.rs @@ -0,0 +1,152 @@ +use super::*; + +pub fn gen_struct(def: &TypeDef, gen: &Gen) -> TokenStream { + if def.is_api_contract() { + return quote! {}; + } + + if gen.sys { + gen_sys_struct_with_name(def, def.name(), gen, "e! {}, "e! {}) + } else { + quote! {} + } +} + +fn gen_sys_struct_with_name(def: &TypeDef, struct_name: &str, gen: &Gen, arch_cfg: &TokenStream, feature_cfg: &TokenStream) -> TokenStream { + let name = gen_ident(struct_name); + + if def.is_handle() { + let signature = if def.type_name() == TypeName::HANDLE { + quote! { *mut ::core::ffi::c_void } + } else { + let signature = def.fields().next().map(|field| field.signature(Some(def))).unwrap(); + gen_sig(&signature, gen) + }; + + return quote! { + pub type #name = #signature; + }; + } + + let is_union = def.is_explicit(); + + let arch_cfg = if arch_cfg.is_empty() { gen.arch_cfg(def.attributes()) } else { arch_cfg.clone() }; + let feature_cfg = if feature_cfg.is_empty() { gen.type_cfg(def) } else { feature_cfg.clone() }; + + let fields: Vec<(Field, Signature, TokenStream)> = def + .fields() + .filter_map(move |f| { + if f.is_literal() { + None + } else { + let signature = f.signature(Some(def)); + let name = f.name(); + Some((f, signature, gen_ident(name))) + } + }) + .collect(); + + if fields.is_empty() { + if let Some(guid) = GUID::from_attributes(def.attributes()) { + let guid = gen_guid(&guid, gen); + + return quote! { + pub const #name: ::windows_sys::core::GUID = #guid; + }; + } else { + return quote! { + #[repr(C)] + pub struct #name(pub u8); + }; + } + } + + let repr = if let Some(layout) = def.class_layout() { + let packing = Literal::u32_unsuffixed(layout.packing_size()); + quote! { #[repr(C, packed(#packing))] } + } else { + quote! { #[repr(C)] } + }; + + let fields = fields.iter().map(|(_, signature, name)| { + let kind = gen_sig(signature, gen); + + quote! { + pub #name: #kind + } + }); + + let struct_or_union = if is_union { + quote! { union } + } else { + quote! { struct } + }; + + let nested_structs = gen_nested_sys_structs(struct_name, def, gen, &arch_cfg, &feature_cfg); + let constants = gen_struct_constants(def, &name, &arch_cfg, &feature_cfg); + + quote! { + #repr + #arch_cfg + #feature_cfg + pub #struct_or_union #name {#(#fields),*} + #constants + #arch_cfg + #feature_cfg + impl ::core::marker::Copy for #name {} + #arch_cfg + #feature_cfg + impl ::core::clone::Clone for #name { + fn clone(&self) -> Self { + *self + } + } + #nested_structs + } +} + +fn gen_struct_constants(def: &TypeDef, struct_name: &TokenStream, arch_cfg: &TokenStream, feature_cfg: &TokenStream) -> TokenStream { + let constants = def.fields().filter_map(|f| { + if f.is_literal() { + if let Some(constant) = f.constant() { + let name = gen_ident(f.name()); + let value = gen_constant_type_value(&constant.value()); + + return Some(quote! { + pub const #name: #value; + }); + } + } + + None + }); + + let mut tokens = quote! { #(#constants)* }; + + if !tokens.is_empty() { + tokens = quote! { + #arch_cfg + #feature_cfg + impl #struct_name { + #tokens + } + }; + } + + tokens +} + +fn gen_nested_sys_structs<'a>(enclosing_name: &'a str, enclosing_type: &'a TypeDef, gen: &Gen, arch_cfg: &TokenStream, feature_cfg: &TokenStream) -> TokenStream { + if let Some(nested_types) = enclosing_type.nested_types() { + nested_types + .iter() + .enumerate() + .map(|(index, (_, nested_type))| { + let nested_name = format!("{}_{}", enclosing_name, index); + gen_sys_struct_with_name(nested_type, &nested_name, gen, arch_cfg, feature_cfg) + }) + .collect() + } else { + TokenStream::new() + } +} diff --git a/crates/deps/gen2/src/winrt_interface.rs b/crates/deps/gen2/src/winrt_interface.rs new file mode 100644 index 0000000000..c59ae3e686 --- /dev/null +++ b/crates/deps/gen2/src/winrt_interface.rs @@ -0,0 +1,17 @@ +use super::*; + +pub fn gen_winrt_interface(def: &TypeDef, gen: &Gen) -> TokenStream { + if gen.sys { + if def.is_exclusive() { + quote! {} + } else { + let name = gen_generic_ident(def.name()); + + quote! { + pub type #name = *mut ::core::ffi::c_void; + } + } + } else { + quote! {} + } +} diff --git a/crates/deps/sys/Cargo.toml b/crates/deps/sys/Cargo.toml index 59fa7d3c4d..80a0cff337 100644 --- a/crates/deps/sys/Cargo.toml +++ b/crates/deps/sys/Cargo.toml @@ -7,9 +7,12 @@ edition = "2018" license = "MIT OR Apache-2.0" description = "Rust for Windows" repository = "https://github.com/microsoft/windows-rs" -documentation = "https://microsoft.github.io/windows-docs-rs/" readme = "../../../.github/readme.md" +[package.metadata.docs.rs] +default-target = "x86_64-pc-windows-msvc" +all-features = true + [target.i686-pc-windows-msvc.dependencies] windows_i686_msvc = { path = "../../targets/i686_msvc", version = "0.28.0" } diff --git a/crates/tools/sys/Cargo.toml b/crates/tools/sys/Cargo.toml index 5cc6de6902..9027719a46 100644 --- a/crates/tools/sys/Cargo.toml +++ b/crates/tools/sys/Cargo.toml @@ -6,5 +6,5 @@ publish = false [dependencies] reader = { package = "windows_reader", path = "../../deps/reader", version = "0.28.0" } -gen = { package = "windows_gen", path = "../../deps/gen", version = "0.28.0" } +gen2 = { package = "windows_gen2", path = "../../deps/gen2", version = "0.28.0" } rayon = "1.5.1" diff --git a/crates/tools/sys/src/main.rs b/crates/tools/sys/src/main.rs index 379f0f3474..e2215208c3 100644 --- a/crates/tools/sys/src/main.rs +++ b/crates/tools/sys/src/main.rs @@ -9,8 +9,6 @@ fn main() { output.pop(); let reader = reader::TypeReader::get_mut(); - include_all(&mut reader.types); - let root = reader.types.get_namespace("Windows").unwrap(); let mut trees = Vec::new(); @@ -37,9 +35,12 @@ edition = "2018" license = "MIT OR Apache-2.0" description = "Rust for Windows" repository = "https://github.com/microsoft/windows-rs" -documentation = "https://microsoft.github.io/windows-docs-rs/" readme = "../../../.github/readme.md" +[package.metadata.docs.rs] +default-target = "x86_64-pc-windows-msvc" +all-features = true + [target.i686-pc-windows-msvc.dependencies] windows_i686_msvc = { path = "../../targets/i686_msvc", version = "0.28.0" } @@ -67,16 +68,16 @@ default = [] fn write_features(file: &mut std::fs::File, root: &'static str, tree: &reader::TypeTree) { for tree in tree.namespaces.values() { + if tree.namespace == "Windows.Win32.Interop" { + continue; + } + write_feature(file, root, tree); write_features(file, root, tree); } } fn write_feature(file: &mut std::fs::File, root: &'static str, tree: &reader::TypeTree) { - if !tree.include { - return; - } - let feature = tree.namespace[root.len() + 1..].replace('.', "_"); if let Some(pos) = feature.rfind('_') { @@ -88,28 +89,16 @@ fn write_feature(file: &mut std::fs::File, root: &'static str, tree: &reader::Ty } } -fn include_all(tree: &mut reader::TypeTree) { - tree.include = true; - - tree.types.values_mut().for_each(|entry| entry.include = reader::TypeInclude::Full); - - tree.namespaces.values_mut().for_each(include_all); - - tree.exclude_namespace("Windows.Win32.Interop"); -} - fn collect_trees<'a>(output: &std::path::Path, root: &'static str, tree: &'a reader::TypeTree, trees: &mut Vec<&'a reader::TypeTree>) { trees.push(tree); - tree.namespaces.values().for_each(|tree| collect_trees(output, root, tree, trees)); - let mut path = std::path::PathBuf::from(output); path.push(tree.namespace.replace('.', "/")); std::fs::create_dir_all(&path).unwrap(); } -fn gen_tree(output: &std::path::Path, root: &'static str, tree: &reader::TypeTree) { - if !tree.include { +fn gen_tree(output: &std::path::Path, _root: &'static str, tree: &reader::TypeTree) { + if tree.namespace == "Windows.Win32.Interop" { return; } @@ -119,11 +108,13 @@ fn gen_tree(output: &std::path::Path, root: &'static str, tree: &reader::TypeTre path.push(tree.namespace.replace('.', "/")); path.push("mod.rs"); - let tokens = gen::gen_sys_file(root, tree, false); + let gen = gen2::Gen { namespace: tree.namespace, sys: true, cfg: true, ..Default::default() }; + + let tokens = gen2::gen_namespace(&gen); let mut child = std::process::Command::new("rustfmt").stdin(std::process::Stdio::piped()).stdout(std::process::Stdio::piped()).spawn().expect("Failed to spawn `rustfmt`"); let mut stdin = child.stdin.take().expect("Failed to open stdin"); - stdin.write_all(tokens.into_string().as_bytes()).unwrap(); + stdin.write_all(tokens.as_bytes()).unwrap(); drop(stdin); let output = child.wait_with_output().unwrap(); diff --git a/rustfmt.toml b/rustfmt.toml index 1714f1e802..0bc68903df 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,2 @@ newline_style = "Unix" max_width = 800 -ignore = [ - "src/Windows", - "crates/deps/sys/src/Windows", -]