diff --git a/src/analysis/bounds.rs b/src/analysis/bounds.rs index ba03f2759..9cfb871d6 100644 --- a/src/analysis/bounds.rs +++ b/src/analysis/bounds.rs @@ -9,25 +9,56 @@ use crate::{ ref_mode::RefMode, rust_type::RustType, }, - config, + config::{self, parameter_matchable::ParameterMatchable}, consts::TYPE_PARAMETERS_START, env::Env, - library::{Basic, Class, Concurrency, Function, Nullable, ParameterDirection, Type, TypeId}, + library::{ + Basic, Class, Concurrency, Function, Nullable, ParameterDirection, ParameterScope, Type, + TypeId, + }, traits::*, }; #[derive(Clone, Eq, Debug, PartialEq)] pub enum BoundType { - NoWrapper, - // lifetime - IsA(Option), - // lifetime <- shouldn't be used but just in case... - AsRef(Option), + NoWrapper { + alias: Option, + }, + IsA { + alias: Option, + }, + AsRef, + IntoOption, + IntoOptionRef { + lt: Option, + }, + IntoOptionIsA { + lt: Option, + alias: Option, + }, } impl BoundType { - pub fn need_isa(&self) -> bool { - matches!(*self, Self::IsA(_)) + pub fn new_as_ref() -> Self { + Self::AsRef + } + pub fn new_into_option() -> Self { + Self::IntoOption + } + pub fn new_into_option_ref() -> Self { + Self::IntoOptionRef { lt: None } + } + pub fn new_into_option_is_a() -> Self { + Self::IntoOptionIsA { + lt: None, + alias: None, + } + } + pub fn new_is_a() -> Self { + Self::IsA { alias: None } + } + pub fn new_no_wrapper() -> Self { + Self::NoWrapper { alias: None } } pub fn get_to_glib_extra( @@ -38,14 +69,17 @@ impl BoundType { ) -> String { use BoundType::*; match *self { - AsRef(_) if move_ && nullable => ".map(|p| p.as_ref().clone().upcast())", - AsRef(_) if nullable => ".as_ref().map(|p| p.as_ref())", - AsRef(_) if move_ => ".upcast()", - AsRef(_) => ".as_ref()", - IsA(_) if move_ && nullable => ".map(|p| p.upcast())", - IsA(_) if nullable && !instance_parameter => ".map(|p| p.as_ref())", - IsA(_) if move_ => ".upcast()", - IsA(_) => ".as_ref()", + AsRef if move_ && nullable => ".map(|p| p.as_ref().clone().upcast())", + AsRef if nullable => ".as_ref().map(|p| p.as_ref())", + AsRef if move_ => ".upcast()", + AsRef => ".as_ref()", + IsA { .. } if move_ && nullable => ".map(|p| p.upcast())", + IsA { .. } if nullable && !instance_parameter => ".map(|p| p.as_ref())", + IsA { .. } if move_ => ".upcast()", + IsA { .. } => ".as_ref()", + IntoOption | IntoOptionRef { .. } => ".into()", + IntoOptionIsA { .. } if move_ => ".into()", + IntoOptionIsA { .. } => ".into().as_ref().map(|p| p.as_ref())", _ => "", } .to_string() @@ -56,8 +90,6 @@ impl BoundType { pub struct Bound { pub bound_type: BoundType, pub parameter_name: String, - /// Bound does not have an alias when `param: impl type_str` is used - pub alias: Option, pub type_str: String, pub callback_modified: bool, } @@ -66,7 +98,7 @@ impl Bound { pub fn alias(&self) -> Option { use BoundType::*; match self.bound_type { - NoWrapper | IsA(_) => self.alias, + NoWrapper { alias } | IsA { alias } | IntoOptionIsA { alias, .. } => alias, _ => None, } } @@ -74,7 +106,7 @@ impl Bound { pub fn lifetime(&self) -> Option { use BoundType::*; match self.bound_type { - IsA(lt) | AsRef(lt) => lt, + IntoOptionRef { lt } | IntoOptionIsA { lt, .. } => lt, _ => None, } } @@ -83,8 +115,8 @@ impl Bound { #[derive(Clone, Debug)] pub struct Bounds { unused: VecDeque, - used: Vec, - lifetimes: Vec, + pub used: Vec, + pub lifetimes: Vec, } impl Default for Bounds { @@ -146,27 +178,37 @@ impl Bounds { .iter() .find_map(|p| p.nullable) .unwrap_or(par.nullable); + let ref_mode = if par.move_ { + RefMode::None + } else { + par.ref_mode + }; let Ok(rust_type) = RustType::builder(env, par.typ) .direction(par.direction) .nullable(nullable) - .ref_mode(if par.move_ { - RefMode::None - } else { - par.ref_mode - }) + .ref_mode(ref_mode) .try_build() else { return (None, None); }; + let mut type_string = rust_type.into_string(); let mut callback_info = None; let mut ret = None; let mut need_is_into_check = false; if !par.instance_parameter && par.direction != ParameterDirection::Out { - if let Some(bound_type) = Bounds::type_for(env, par.typ) { + if let Some(bound_type) = Bounds::type_for( + env, + par.typ, + ref_mode, + nullable, + par.direction, + par.instance_parameter, + par.scope, + ) { ret = Some(bound_type.get_to_glib_extra( - *par.nullable, + *nullable, par.instance_parameter, par.move_, )); @@ -257,7 +299,15 @@ impl Bounds { } } } else if par.instance_parameter { - if let Some(bound_type) = Bounds::type_for(env, par.typ) { + if let Some(bound_type) = Bounds::type_for( + env, + par.typ, + ref_mode, + nullable, + par.direction, + par.instance_parameter, + par.scope, + ) { ret = Some(bound_type.get_to_glib_extra(*par.nullable, true, par.move_)); } } @@ -280,21 +330,39 @@ impl Bounds { .try_build() .into_string(); - let Some(bound_type) = Bounds::type_for(env, type_id) else { + let Some(mut bound_type) = Bounds::type_for( + env, + type_id, + ref_mode, + nullable, + ParameterDirection::In, + false, + ParameterScope::None, + ) else { return false; }; - let alias = match bound_type { - BoundType::NoWrapper | BoundType::IsA(_) => { - Some(self.unused.pop_front().expect("No free type aliases!")) + match &mut bound_type { + BoundType::NoWrapper { alias } => { + // TODO: This is just a heuristic for now, based on what we do in codegen! + // Theoretically the surrounding function should determine whether it needs to + // reuse an alias (ie. to use in `call_func::`) or not. + // In the latter case an `impl` is generated instead of a type name/alias. + *alias = Some(self.unused.pop_front().expect("No free type aliases!")); } - _ => None, - }; + BoundType::IntoOptionRef { lt } => { + *lt = Some(self.get_lifetime()); + } + BoundType::IntoOptionIsA { lt, alias, .. } => { + *lt = Some(self.get_lifetime()); + *alias = Some(self.unused.pop_front().expect("No free type aliases!")); + } + _ => (), + } self.push(Bound { bound_type, parameter_name: name.to_string(), - alias, type_str, callback_modified: false, }); @@ -302,24 +370,61 @@ impl Bounds { true } - pub fn type_for(env: &Env, type_id: TypeId) -> Option { - use self::BoundType::*; + pub fn type_for( + env: &Env, + type_id: TypeId, + ref_mode: RefMode, + nullable: Nullable, + direction: ParameterDirection, + instance_parameter: bool, + scope: ParameterScope, + ) -> Option { match env.library.type_(type_id) { - Type::Basic(Basic::Filename | Basic::OsString) => Some(AsRef(None)), + Type::Basic(Basic::Filename | Basic::OsString) => Some(BoundType::new_as_ref()), Type::Class(Class { is_fundamental: true, .. - }) => Some(AsRef(None)), + }) => Some(BoundType::new_as_ref()), Type::Class(Class { final_type: true, .. - }) => None, + }) => { + if *nullable + && direction == ParameterDirection::In + && !ref_mode.is_none() + && !instance_parameter + { + Some(BoundType::new_into_option_ref()) + } else { + None + } + } Type::Class(Class { final_type: false, .. - }) => Some(IsA(None)), - Type::Interface(..) => Some(IsA(None)), + }) + | Type::Interface(..) => { + if *nullable + && direction == ParameterDirection::In + && !ref_mode.is_none() + && !instance_parameter + { + Some(BoundType::new_into_option_is_a()) + } else { + Some(BoundType::new_is_a()) + } + } Type::List(_) | Type::SList(_) | Type::CArray(_) => None, - Type::Function(_) => Some(NoWrapper), - _ => None, + Type::Function(_) => Some(BoundType::new_no_wrapper()), + _ => { + if *nullable && direction == ParameterDirection::In && scope.is_none() { + if !ref_mode.is_none() { + Some(BoundType::new_into_option_ref()) + } else { + Some(BoundType::new_into_option()) + } + } else { + None + } + } } } @@ -328,13 +433,21 @@ impl Bounds { type_id: TypeId, ref_mode: RefMode, nullable: Nullable, + direction: ParameterDirection, instance_parameter: bool, + scope: ParameterScope, ) -> Option { - let res = Self::type_for(env, type_id)?.get_to_glib_extra( - *nullable, + let bound_type = Self::type_for( + env, + type_id, + ref_mode, + nullable, + direction, instance_parameter, - ref_mode.is_none(), - ); + scope, + )?; + + let res = bound_type.get_to_glib_extra(*nullable, instance_parameter, ref_mode.is_none()); if res.is_empty() { return None; @@ -343,6 +456,16 @@ impl Bounds { Some(res) } + fn get_lifetime(&mut self) -> char { + // In practice, we always need at most one lifetime so far + let lt = 'a'; + if self.lifetimes.is_empty() { + self.lifetimes.push(lt); + } + + lt + } + pub fn add_parameter( &mut self, name: &str, @@ -351,25 +474,31 @@ impl Bounds { r#async: bool, ) { if r#async && name == "callback" { - bound_type = BoundType::NoWrapper; + bound_type = BoundType::new_no_wrapper(); } if self.used.iter().any(|n| n.parameter_name == name) { return; } - let alias = match bound_type { - BoundType::NoWrapper => { + match &mut bound_type { + BoundType::NoWrapper { alias } => { // TODO: This is just a heuristic for now, based on what we do in codegen! // Theoretically the surrounding function should determine whether it needs to // reuse an alias (ie. to use in `call_func::`) or not. // In the latter case an `impl` is generated instead of a type name/alias. - Some(self.unused.pop_front().expect("No free type aliases!")) + *alias = Some(self.unused.pop_front().expect("No free type aliases!")); } - _ => None, - }; + BoundType::IntoOptionRef { lt } => { + *lt = Some(self.get_lifetime()); + } + BoundType::IntoOptionIsA { lt, alias, .. } => { + *lt = Some(self.get_lifetime()); + *alias = Some(self.unused.pop_front().expect("No free type aliases!")); + } + _ => (), + } self.used.push(Bound { bound_type, parameter_name: name.to_owned(), - alias, type_str: type_str.to_owned(), callback_modified: false, }); @@ -384,9 +513,9 @@ impl Bounds { use self::BoundType::*; for used in &self.used { match used.bound_type { - NoWrapper => (), - IsA(_) => imports.add("glib::prelude::*"), - AsRef(_) => imports.add_used_type(&used.type_str), + NoWrapper { .. } | IntoOption | IntoOptionRef { .. } => (), + IsA { .. } | IntoOptionIsA { .. } => imports.add("glib::prelude::*"), + AsRef { .. } => imports.add_used_type(&used.type_str), } } } @@ -399,8 +528,8 @@ impl Bounds { self.used.iter() } - pub fn iter_lifetimes(&self) -> Iter<'_, char> { - self.lifetimes.iter() + pub fn iter_aliases(&self) -> impl Iterator + '_ { + self.used.iter().flat_map(|u| u.alias()) } } @@ -466,11 +595,12 @@ fn find_error_type(env: &Env, function: &Function) -> Option { #[cfg(test)] mod tests { use super::*; + use std::mem::discriminant; #[test] fn get_new_all() { let mut bounds: Bounds = Default::default(); - let typ = BoundType::IsA(None); + let typ = BoundType::new_is_a(); bounds.add_parameter("a", "", typ.clone(), false); assert_eq!(bounds.iter().len(), 1); // Don't add second time @@ -495,7 +625,7 @@ mod tests { #[should_panic(expected = "No free type aliases!")] fn exhaust_type_parameters() { let mut bounds: Bounds = Default::default(); - let typ = BoundType::NoWrapper; + let typ = BoundType::new_no_wrapper(); for c in 'a'..='l' { // Should panic on `l` because all type parameters are exhausted bounds.add_parameter(c.to_string().as_str(), "", typ.clone(), false); @@ -505,36 +635,36 @@ mod tests { #[test] fn get_parameter_bound() { let mut bounds: Bounds = Default::default(); - let typ = BoundType::NoWrapper; + let typ = BoundType::new_no_wrapper(); bounds.add_parameter("a", "", typ.clone(), false); bounds.add_parameter("b", "", typ.clone(), false); let bound = bounds.get_parameter_bound("a").unwrap(); // `NoWrapper `bounds are expected to have an alias: - assert_eq!(bound.alias, Some('P')); - assert_eq!(bound.bound_type, typ); + assert_eq!(bound.alias(), Some('P')); + assert_eq!(discriminant(&bound.bound_type), discriminant(&typ)); let bound = bounds.get_parameter_bound("b").unwrap(); - assert_eq!(bound.alias, Some('Q')); - assert_eq!(bound.bound_type, typ); + assert_eq!(bound.alias(), Some('Q')); + assert_eq!(discriminant(&bound.bound_type), discriminant(&typ)); assert_eq!(bounds.get_parameter_bound("c"), None); } #[test] fn impl_bound() { let mut bounds: Bounds = Default::default(); - let typ = BoundType::IsA(None); + let typ = BoundType::new_is_a(); bounds.add_parameter("a", "", typ.clone(), false); bounds.add_parameter("b", "", typ.clone(), false); let bound = bounds.get_parameter_bound("a").unwrap(); // `IsA` is simplified to an inline `foo: impl IsA` and // lacks an alias/type-parameter: - assert_eq!(bound.alias, None); + assert_eq!(bound.alias(), None); assert_eq!(bound.bound_type, typ); - let typ = BoundType::AsRef(None); + let typ = BoundType::new_as_ref(); bounds.add_parameter("c", "", typ.clone(), false); let bound = bounds.get_parameter_bound("c").unwrap(); // Same `impl AsRef` simplification as `IsA`: - assert_eq!(bound.alias, None); + assert_eq!(bound.alias(), None); assert_eq!(bound.bound_type, typ); } } diff --git a/src/analysis/child_properties.rs b/src/analysis/child_properties.rs index e19eb80da..839906332 100644 --- a/src/analysis/child_properties.rs +++ b/src/analysis/child_properties.rs @@ -115,7 +115,17 @@ fn analyze_property( let mut bounds_str = String::new(); let dir = ParameterDirection::In; - let set_params = if Bounds::type_for(env, typ).is_some() { + let set_params = if Bounds::type_for( + env, + typ, + set_in_ref_mode, + nullable, + dir, + false, + Default::default(), + ) + .is_some() + { // TODO: bounds_str push?!?! bounds_str.push_str("TODO"); format!("{prop_name}: {TYPE_PARAMETERS_START}") diff --git a/src/analysis/class_builder.rs b/src/analysis/class_builder.rs index a22601648..a06652171 100644 --- a/src/analysis/class_builder.rs +++ b/src/analysis/class_builder.rs @@ -9,7 +9,7 @@ use crate::{ }, config::{self, GObject}, env::Env, - library, + library::{self, Nullable}, traits::*, }; @@ -124,9 +124,10 @@ fn analyze_property( let (get_out_ref_mode, set_in_ref_mode, _) = get_property_ref_modes(env, prop); let mut bounds = Bounds::default(); - if let Some(bound) = Bounds::type_for(env, prop.typ) { + let set_has_bound = + bounds.add_for_property_setter(env, prop.typ, &prop.name, set_in_ref_mode, Nullable(false)); + if set_has_bound { imports.add("glib::prelude::*"); - bounds.add_parameter(&prop.name, &rust_type_res.into_string(), bound, false); } Some(Property { diff --git a/src/analysis/function_parameters.rs b/src/analysis/function_parameters.rs index 95a97b80f..407b23eae 100644 --- a/src/analysis/function_parameters.rs +++ b/src/analysis/function_parameters.rs @@ -289,7 +289,15 @@ pub fn analyze( } if let Some(array_par) = array_par { let mut array_name = nameutil::mangle_keywords(&array_par.name); - if let Some(bound_type) = Bounds::type_for(env, array_par.typ) { + if let Some(bound_type) = Bounds::type_for( + env, + array_par.typ, + if move_ { RefMode::None } else { RefMode::ByRef }, + Nullable(false), + array_par.direction, + array_par.instance_parameter, + array_par.scope, + ) { array_name = (array_name.into_owned() + &bound_type.get_to_glib_extra( *array_par.nullable, diff --git a/src/analysis/functions.rs b/src/analysis/functions.rs index ce7414b80..83a595e4b 100644 --- a/src/analysis/functions.rs +++ b/src/analysis/functions.rs @@ -390,7 +390,9 @@ fn analyze_callbacks( par.ref_mode }, par.nullable, + par.direction, par.instance_parameter, + par.scope, ), None, ) diff --git a/src/analysis/ref_mode.rs b/src/analysis/ref_mode.rs index 73a319f6e..4eb3e5c06 100644 --- a/src/analysis/ref_mode.rs +++ b/src/analysis/ref_mode.rs @@ -128,6 +128,29 @@ impl RefMode { pub fn is_none(self) -> bool { matches!(self, Self::None) } + + pub fn to_string_with_maybe_lt(self, lt: Option) -> String { + match self { + RefMode::None | RefMode::ByRefFake => { + assert!(lt.is_none(), "incompatible ref mode {self:?} with lifetime"); + String::new() + } + RefMode::ByRef | RefMode::ByRefImmut | RefMode::ByRefConst => { + if let Some(lt) = lt { + format!("&'{lt}") + } else { + "&".to_string() + } + } + RefMode::ByRefMut => { + if let Some(lt) = lt { + format!("&'{lt} mut ") + } else { + "&mut ".to_string() + } + } + } + } } impl fmt::Display for RefMode { diff --git a/src/analysis/trampolines.rs b/src/analysis/trampolines.rs index 0074a9d20..84c712b0f 100644 --- a/src/analysis/trampolines.rs +++ b/src/analysis/trampolines.rs @@ -87,14 +87,14 @@ pub fn analyze( bounds.add_parameter( "this", &type_name.into_string(), - BoundType::AsRef(None), + BoundType::new_as_ref(), false, ); } else { bounds.add_parameter( "this", &type_name.into_string(), - BoundType::IsA(None), + BoundType::new_is_a(), false, ); } @@ -132,14 +132,14 @@ pub fn analyze( bounds.add_parameter( "this", &type_name.into_string(), - BoundType::AsRef(None), + BoundType::new_as_ref(), false, ); } else { bounds.add_parameter( "this", &type_name.into_string(), - BoundType::IsA(None), + BoundType::new_is_a(), false, ); } diff --git a/src/chunk/chunk.rs b/src/chunk/chunk.rs index 4919360df..969f35908 100644 --- a/src/chunk/chunk.rs +++ b/src/chunk/chunk.rs @@ -67,7 +67,7 @@ pub enum Chunk { Name(String), ExternCFunc { name: String, - bounds: String, + generic_params: String, parameters: Vec, body: Box, return_value: Option, diff --git a/src/codegen/bound.rs b/src/codegen/bound.rs index 7e28ffe3a..e4b21a05a 100644 --- a/src/codegen/bound.rs +++ b/src/codegen/bound.rs @@ -9,83 +9,91 @@ use crate::{ impl Bound { /// Returns the type parameter reference. /// Currently always returns the alias. - pub(super) fn type_parameter_reference(&self) -> Option { - self.alias + /// + /// This doesn't include the lifetime, which could be shared by other type + /// parameters. Use [Bounds::to_generic_params_str](crate::Bounds::to_generic_params_str) + /// to get the full generic parameter list, including lifetimes. + pub fn type_parameter_definition(&self, r#async: bool) -> Option { + use BoundType::*; + match self.bound_type { + NoWrapper { alias: Some(alias) } => Some(format!("{alias}: {}", self.type_str)), + IsA { alias: Some(alias) } => Some(format!( + "{alias}: IsA<{}>{}", + self.type_str, + if r#async { " + Clone + 'static" } else { "" }, + )), + IntoOptionIsA { alias, .. } => { + let alias = alias.expect("should be defined in this context"); + Some(format!( + "{alias}: IsA<{}>{}", + self.type_str, + if r#async { " + Clone + 'static" } else { "" }, + )) + } + _ => None, + } } /// Returns the type parameter reference, with [`BoundType::IsA`] wrapped /// in `ref_mode` and `nullable` as appropriate. - pub(super) fn full_type_parameter_reference( + pub fn full_type_parameter_reference( &self, ref_mode: RefMode, nullable: Nullable, r#async: bool, ) -> String { - let ref_str = ref_mode.to_string(); - - // Generate `impl Trait` if this bound does not have an alias - let trait_bound = match self.type_parameter_reference() { - Some(t) => t.to_string(), - None => { - let trait_bound = self.trait_bound(r#async); - let trait_bound = format!("impl {trait_bound}"); + use BoundType::*; + match self.bound_type { + NoWrapper { alias } => alias.unwrap().to_string(), + IsA { alias: None } => { + let suffix = r#async + .then(|| " + Clone + 'static".to_string()) + .unwrap_or_default(); - // Combining a ref mode and lifetime requires parentheses for disambiguation - match self.bound_type { - BoundType::IsA(lifetime) => { - // TODO: This is fragile - let has_lifetime = r#async || lifetime.is_some(); + let mut trait_bound = format!("impl IsA<{}>{suffix}", self.type_str); - if !ref_str.is_empty() && has_lifetime { - format!("({trait_bound})") - } else { - trait_bound - } - } - _ => trait_bound, + let ref_str = ref_mode.to_string(); + if !ref_str.is_empty() && r#async { + trait_bound = format!("({trait_bound})"); } - } - }; - match self.bound_type { - BoundType::IsA(_) if *nullable => { - format!("Option<{ref_str}{trait_bound}>") + if *nullable { + format!("Option<{ref_str}{trait_bound}>") + } else { + format!("{ref_str}{trait_bound}") + } } - BoundType::IsA(_) => format!("{ref_str}{trait_bound}"), - BoundType::AsRef(_) if *nullable => { - format!("Option<{trait_bound}>") + IsA { alias: Some(alias) } => { + let ref_str = ref_mode.to_string(); + if *nullable { + format!("Option<{ref_str} {alias}>") + } else { + format!("{ref_str} {alias}") + } } - BoundType::NoWrapper | BoundType::AsRef(_) => trait_bound, - } - } - - /// Returns the type parameter definition for this bound, usually - /// of the form `T: SomeTrait` or `T: IsA`. - pub(super) fn type_parameter_definition(&self, r#async: bool) -> Option { - self.alias - .map(|alias| format!("{}: {}", alias, self.trait_bound(r#async))) - } - - /// Returns the trait bound, usually of the form `SomeTrait` - /// or `IsA`. - pub(super) fn trait_bound(&self, r#async: bool) -> String { - match self.bound_type { - BoundType::NoWrapper => self.type_str.clone(), - BoundType::IsA(lifetime) => { - if r#async { - assert!(lifetime.is_none(), "Async overwrites lifetime"); + AsRef => { + if *nullable { + format!("Option>", self.type_str) + } else { + format!("impl AsRef<{}>", self.type_str) } - let is_a = format!("IsA<{}>", self.type_str); + } + IntoOption => { + format!("impl Into>", self.type_str) + } + IntoOptionRef { lt } => { + assert!(lt.is_some(), "must be defined in this context"); + let ref_str = ref_mode.to_string_with_maybe_lt(lt); - let lifetime = r#async - .then(|| " + Clone + 'static".to_string()) - .or_else(|| lifetime.map(|l| format!(" + '{l}"))) - .unwrap_or_default(); + format!("impl Into>", self.type_str) + } + IntoOptionIsA { lt, alias } => { + assert!(lt.is_some(), "must be defined in this context"); + let ref_str = ref_mode.to_string_with_maybe_lt(lt); + let alias = alias.expect("must be defined in this context"); - format!("{is_a}{lifetime}") + format!("impl Into>") } - BoundType::AsRef(Some(_ /* lifetime */)) => panic!("AsRef cannot have a lifetime"), - BoundType::AsRef(None) => format!("AsRef<{}>", self.type_str), } } } diff --git a/src/codegen/bounds.rs b/src/codegen/bounds.rs new file mode 100644 index 000000000..797704a84 --- /dev/null +++ b/src/codegen/bounds.rs @@ -0,0 +1,46 @@ +use crate::analysis::bounds::Bounds; + +impl Bounds { + pub fn to_generic_params_str(&self) -> String { + self.to_generic_params_str_(false) + } + + pub fn to_generic_params_str_async(&self) -> String { + self.to_generic_params_str_(true) + } + + fn to_generic_params_str_(&self, r#async: bool) -> String { + let mut res = String::new(); + + if self.lifetimes.is_empty() && self.used.iter().find_map(|b| b.alias()).is_none() { + return res; + } + + res.push('<'); + let mut is_first = true; + + for lt in self.lifetimes.iter() { + if is_first { + is_first = false; + } else { + res.push_str(", "); + } + res.push('\''); + res.push(*lt); + } + + for bound in self.used.iter() { + if let Some(type_param_def) = bound.type_parameter_definition(r#async) { + if is_first { + is_first = false; + } else { + res.push_str(", "); + } + res.push_str(&type_param_def); + } + } + res.push('>'); + + res + } +} diff --git a/src/codegen/function.rs b/src/codegen/function.rs index c22a6cd7b..f1a05f87b 100644 --- a/src/codegen/function.rs +++ b/src/codegen/function.rs @@ -215,8 +215,6 @@ pub fn declaration(env: &Env, analysis: &analysis::functions::Info) -> String { }; let mut param_str = String::with_capacity(100); - let (bounds, _) = bounds(&analysis.bounds, &[], false, false); - for par in &analysis.parameters.rust_parameters { if !param_str.is_empty() { param_str.push_str(", "); @@ -229,7 +227,7 @@ pub fn declaration(env: &Env, analysis: &analysis::functions::Info) -> String { format!( "fn {}{}({}){}", analysis.codegen_name(), - bounds, + analysis.bounds.to_generic_params_str(), param_str, return_str, ) @@ -252,98 +250,34 @@ pub fn declaration_futures(env: &Env, analysis: &analysis::functions::Info) -> S let mut param_str = String::with_capacity(100); - let mut skipped = 0; - let mut skipped_bounds = vec![]; - for (pos, par) in analysis.parameters.rust_parameters.iter().enumerate() { + let mut bounds = Bounds::default(); + for par in analysis.parameters.rust_parameters.iter() { let c_par = &analysis.parameters.c_parameters[par.ind_c]; if c_par.name == "callback" || c_par.name == "cancellable" { - skipped += 1; - if let Some(alias) = analysis - .bounds - .get_parameter_bound(&c_par.name) - .and_then(|bound| bound.type_parameter_reference()) - { - skipped_bounds.push(alias); - } continue; } - if pos - skipped > 0 { - param_str.push_str(", "); + if let Some(bound) = analysis.bounds.get_parameter_bound(&c_par.name).cloned() { + bounds.push(bound); } let s = c_par.to_parameter(env, &analysis.bounds, true); + if !param_str.is_empty() { + param_str.push_str(", "); + } param_str.push_str(&s); } - let (bounds, _) = bounds(&analysis.bounds, skipped_bounds.as_ref(), true, false); - format!( "fn {}{}({}){}", - async_future.name, bounds, param_str, return_str, + async_future.name, + bounds.to_generic_params_str_async(), + param_str, + return_str, ) } -pub fn bounds( - bounds: &Bounds, - skip: &[char], - r#async: bool, - filter_callback_modified: bool, -) -> (String, Vec) { - use crate::analysis::bounds::BoundType::*; - - if bounds.is_empty() { - return (String::new(), Vec::new()); - } - - let skip_lifetimes = bounds - .iter() - // TODO: False or true? - .filter(|bound| bound.alias.is_some_and(|alias| skip.contains(&alias))) - .filter_map(|bound| match bound.bound_type { - IsA(lifetime) | AsRef(lifetime) => lifetime, - _ => None, - }) - .collect::>(); - - let lifetimes = bounds - .iter_lifetimes() - .filter(|s| !skip_lifetimes.contains(s)) - .map(|s| format!("'{s}")) - .collect::>(); - - let bounds = bounds.iter().filter(|bound| { - bound.alias.map_or(true, |alias| !skip.contains(&alias)) - && (!filter_callback_modified || !bound.callback_modified) - }); - - let type_names = lifetimes - .iter() - .cloned() - .chain( - bounds - .clone() - .filter_map(|b| b.type_parameter_definition(r#async)), - ) - .collect::>(); - - let type_names = if type_names.is_empty() { - String::new() - } else { - format!("<{}>", type_names.join(", ")) - }; - - let bounds = lifetimes - .into_iter() - // TODO: enforce that this is only used on NoWrapper! - // TODO: Analyze if alias is used in function, otherwise set to None! - .chain(bounds.filter_map(|b| b.alias).map(|a| a.to_string())) - .collect::>(); - - (type_names, bounds) -} - pub fn body_chunk( env: &Env, analysis: &analysis::functions::Info, @@ -396,9 +330,7 @@ pub fn body_chunk( } } - let (bounds, bounds_names) = bounds(&analysis.bounds, &[], false, true); - - builder.generate(env, &bounds, &bounds_names.join(", ")) + builder.generate(env, &analysis.bounds) } pub fn body_chunk_futures( diff --git a/src/codegen/function_body_chunk.rs b/src/codegen/function_body_chunk.rs index 6bee059c9..37d376005 100644 --- a/src/codegen/function_body_chunk.rs +++ b/src/codegen/function_body_chunk.rs @@ -3,6 +3,7 @@ use std::collections::{hash_map::Entry, BTreeMap, HashMap}; use crate::{ analysis::{ self, + bounds::Bounds, conversion_type::ConversionType, function_parameters::{ CParameter as AnalysisCParameter, Transformation, TransformationType, @@ -70,12 +71,19 @@ pub struct Builder { // Key: user data index // Value: (global position used as id, type, callbacks) -type FuncParameters<'a> = BTreeMap>; +type UserDataById<'a> = BTreeMap>; -struct FuncParameter<'a> { +struct UserData<'a> { pos: usize, - full_type: Option<(String, String)>, + typ_: Option, callbacks: Vec<&'a Trampoline>, + generic_params: String, + qualified_aliases: String, +} + +struct UserDataType { + immutable: String, + mutable: String, } impl Builder { @@ -136,7 +144,7 @@ impl Builder { self.in_unsafe = in_unsafe; self } - pub fn generate(&self, env: &Env, bounds: &str, bounds_names: &str) -> Chunk { + pub fn generate(&self, env: &Env, outer_bounds: &Bounds) -> Chunk { let mut body = Vec::new(); let mut uninitialized_vars = if self.outs_as_return { @@ -145,28 +153,50 @@ impl Builder { Vec::new() }; - let mut group_by_user_data = FuncParameters::new(); + let mut user_data_by_id = UserDataById::new(); // We group arguments by callbacks. if !self.callbacks.is_empty() || !self.destroys.is_empty() { for (pos, callback) in self.callbacks.iter().enumerate() { let user_data_index = callback.user_data_index; - if group_by_user_data.contains_key(&user_data_index) { + if user_data_by_id.contains_key(&user_data_index) { continue; } + + let mut bounds = Bounds::default(); let calls = self .callbacks .iter() .filter(|c| c.user_data_index == user_data_index) + .inspect(|c| { + if let Some(bound) = outer_bounds.get_parameter_bound(&c.name).cloned() { + bounds.push(bound) + } + }) .collect::>(); - group_by_user_data.insert( + + let (generic_params, qualified_aliases) = if bounds.is_empty() { + (String::new(), String::new()) + } else { + let qualified_aliases = format!( + "::<{}>", + bounds + .iter_aliases() + .map(|a| a.to_string()) + .collect::>() + .join(", ") + ); + (bounds.to_generic_params_str(), qualified_aliases) + }; + + user_data_by_id.insert( user_data_index, - FuncParameter { + UserData { pos, - full_type: if calls.len() > 1 { + typ_: if calls.len() > 1 { if calls.iter().all(|c| c.scope.is_call()) { - Some(( - format!( + Some(UserDataType { + immutable: format!( "&({})", calls .iter() @@ -174,7 +204,7 @@ impl Builder { .collect::>() .join(", ") ), - format!( + mutable: format!( "&mut ({})", calls .iter() @@ -182,7 +212,7 @@ impl Builder { .collect::>() .join(", ") ), - )) + }) } else { let s = format!( "Box_<({})>", @@ -192,18 +222,23 @@ impl Builder { .collect::>() .join(", ") ); - Some((s.clone(), s)) + Some(UserDataType { + immutable: s.clone(), + mutable: s, + }) } } else { None }, callbacks: calls, + generic_params, + qualified_aliases, }, ); } } - let call = self.generate_call(&group_by_user_data); + let call = self.generate_call(&user_data_by_id); let call = self.generate_call_conversion(call, &mut uninitialized_vars); let ret = self.generate_out_return(&mut uninitialized_vars); let (call, ret) = self.apply_outs_mode(call, ret, &mut uninitialized_vars); @@ -221,7 +256,7 @@ impl Builder { if !self.callbacks.is_empty() || !self.destroys.is_empty() { // Key: user data index // Value: the current pos in the tuple for the given argument. - let mut poses = HashMap::with_capacity(group_by_user_data.len()); + let mut poses = HashMap::with_capacity(user_data_by_id.len()); for trampoline in &self.callbacks { *poses .entry(&trampoline.user_data_index) @@ -239,13 +274,11 @@ impl Builder { env, &mut chunks, trampoline, - &group_by_user_data[&user_data_index].full_type, + &user_data_by_id[&user_data_index], match pos { Entry::Occupied(ref x) => Some(*x.get()), _ => None, }, - bounds, - bounds_names, false, ); pos.and_modify(|x| { @@ -257,18 +290,17 @@ impl Builder { env, &mut chunks, destroy, - &group_by_user_data[&destroy.user_data_index].full_type, + &user_data_by_id[&destroy.user_data_index], None, // doesn't matter for destroy - bounds, - bounds_names, true, ); } - for FuncParameter { + for UserData { pos, - full_type, + typ_: user_data_type, callbacks: calls, - } in group_by_user_data.values() + .. + } in user_data_by_id.values() { if calls.len() > 1 { chunks.push(Chunk::Let { @@ -303,7 +335,10 @@ impl Builder { ) })), type_: Some(Box::new(Chunk::Custom( - full_type.clone().map(|x| x.0).unwrap(), + user_data_type + .as_ref() + .map(|x| x.immutable.clone()) + .unwrap(), ))), }); } else if !calls.is_empty() { @@ -384,14 +419,12 @@ impl Builder { env: &Env, chunks: &mut Vec, trampoline: &Trampoline, - full_type: &Option<(String, String)>, + user_data: &UserData, pos: Option, - bounds: &str, - bounds_names: &str, is_destroy: bool, ) { if !is_destroy { - if full_type.is_none() { + if user_data.typ_.is_none() { if trampoline.scope.is_call() { chunks.push(Chunk::Custom(format!( "let mut {0}_data: {1} = {0};", @@ -459,13 +492,13 @@ impl Builder { .last() .map_or_else(|| "Unknown".to_owned(), |p| p.name.clone()); - if let Some(full_type) = full_type { + if let Some(ref user_data_typ_) = user_data.typ_ { if is_destroy || trampoline.scope.is_async() { body.push(Chunk::Let { name: format!("{}callback", if is_destroy { "_" } else { "" }), is_mut: false, value: Box::new(Chunk::Custom(format!("Box_::from_raw({func} as *mut _)"))), - type_: Some(Box::new(Chunk::Custom(full_type.1.clone()))), + type_: Some(Box::new(Chunk::Custom(user_data_typ_.mutable.clone()))), }); } else { body.push(Chunk::Let { @@ -486,15 +519,15 @@ impl Builder { if !trampoline.scope.is_async() && !trampoline.scope.is_call() { format!( "&{}", - full_type - .1 + user_data_typ_ + .mutable .strip_prefix("Box_<") .unwrap() .strip_suffix(">") .unwrap() ) } else { - full_type.1.clone() + user_data_typ_.mutable.clone() }, ))), }); @@ -618,6 +651,7 @@ impl Builder { let extern_func = Chunk::ExternCFunc { name: format!("{}_func", trampoline.name), + generic_params: user_data.generic_params.clone(), parameters: trampoline .parameters .c_parameters @@ -650,31 +684,25 @@ impl Builder { } else { None }, - bounds: bounds.to_owned(), }; chunks.push(extern_func); - let bounds_str = if bounds_names.is_empty() { - String::new() - } else { - format!("::<{bounds_names}>") - }; if !is_destroy { if *trampoline.nullable { chunks.push(Chunk::Custom(format!( "let {0} = if {0}_data.is_some() {{ Some({0}_func{1} as _) }} else {{ None }};", - trampoline.name, bounds_str + trampoline.name, user_data.qualified_aliases, ))); } else { chunks.push(Chunk::Custom(format!( "let {0} = Some({0}_func{1} as _);", - trampoline.name, bounds_str + trampoline.name, user_data.qualified_aliases, ))); } } else { chunks.push(Chunk::Custom(format!( "let destroy_call{} = Some({}_func{} as _);", - trampoline.destroy_index, trampoline.name, bounds_str + trampoline.destroy_index, trampoline.name, user_data.qualified_aliases, ))); } } @@ -918,10 +946,10 @@ impl Builder { "{}<{}: {}>", trampoline.name, trampoline.bound_name, trampoline.callback_type ), + generic_params: String::new(), parameters, body: Box::new(Chunk::Chunks(body)), return_value: None, - bounds: String::new(), }); let chunk = Chunk::Let { name: "callback".to_string(), @@ -972,7 +1000,7 @@ impl Builder { } } - fn generate_call(&self, calls: &FuncParameters<'_>) -> Chunk { + fn generate_call(&self, calls: &UserDataById<'_>) -> Chunk { let params = self.generate_func_parameters(calls); Chunk::FfiCall { name: self.glib_name.clone(), @@ -992,7 +1020,7 @@ impl Builder { call: Box::new(call), } } - fn generate_func_parameters(&self, calls: &FuncParameters<'_>) -> Vec { + fn generate_func_parameters(&self, calls: &UserDataById<'_>) -> Vec { let mut params = Vec::new(); for trans in &self.transformations { if !trans.transformation_type.is_to_glib() { @@ -1010,7 +1038,7 @@ impl Builder { params.push(chunk); } let mut to_insert = Vec::new(); - for (user_data_index, FuncParameter { pos, callbacks, .. }) in calls.iter() { + for (user_data_index, UserData { pos, callbacks, .. }) in calls.iter() { let all_call = callbacks.iter().all(|c| c.scope.is_call()); to_insert.push(( *user_data_index, diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index a3752cf41..986755b47 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -16,6 +16,7 @@ use crate::{ mod alias; mod bound; +mod bounds; mod child_properties; mod constants; mod doc; diff --git a/src/codegen/object.rs b/src/codegen/object.rs index 3f7bf3b0d..7a2d201c3 100644 --- a/src/codegen/object.rs +++ b/src/codegen/object.rs @@ -13,8 +13,8 @@ use super::{ }; use crate::{ analysis::{ - self, bounds::BoundType, object::has_builder_properties, record_type::RecordType, - ref_mode::RefMode, rust_type::RustType, safety_assertion_mode::SafetyAssertionMode, + self, object::has_builder_properties, record_type::RecordType, rust_type::RustType, + safety_assertion_mode::SafetyAssertionMode, }, env::Env, library::{self, Nullable}, @@ -378,24 +378,32 @@ fn generate_builder(w: &mut dyn Write, env: &Env, analysis: &analysis::object::I "&str" => ( Some(format!("impl Into<{glib_crate_name}::GString>")), String::new(), - ".into()", + ".into()".to_string(), ), "&[&str]" => ( Some(format!("impl Into<{glib_crate_name}::StrV>")), String::new(), - ".into()", + ".into()".to_string(), ), - _ if !property.bounds.is_empty() => { - let (bounds, _) = function::bounds(&property.bounds, &[], false, false); - let param_bound = property.bounds.get_parameter_bound(&property.name); - let alias = param_bound.map(|bound| { - bound.full_type_parameter_reference(RefMode::ByRef, Nullable(false), false) - }); - let conversion = param_bound.and_then(|bound| match bound.bound_type { - BoundType::AsRef(_) => Some(".as_ref().clone()"), - _ => None, - }); - (alias, bounds, conversion.unwrap_or(".clone().upcast()")) + _ if property.set_bound().is_some() => { + let set_bound = property.set_bound().unwrap(); + + let param_type_override = set_bound.full_type_parameter_reference( + property.set_in_ref_mode, + Nullable(false), + false, + ); + let mut conversion = + set_bound + .bound_type + .get_to_glib_extra(*property.nullable, false, false); + conversion.push_str(".clone()"); + + ( + Some(param_type_override), + property.bounds.to_generic_params_str(), + conversion, + ) } typ if typ.starts_with('&') => { let should_clone = @@ -415,9 +423,9 @@ fn generate_builder(w: &mut dyn Write, env: &Env, analysis: &analysis::object::I ".clone()" }; - (None, String::new(), should_clone) + (None, String::new(), should_clone.to_string()) } - _ => (None, String::new(), ""), + _ => (None, String::new(), "".to_string()), }; if let Some(param_type_override) = param_type_override { param_type_str = param_type_override.to_string(); diff --git a/src/codegen/properties.rs b/src/codegen/properties.rs index 93b44a0ab..e790449c3 100644 --- a/src/codegen/properties.rs +++ b/src/codegen/properties.rs @@ -80,18 +80,19 @@ fn generate_prop_func( } fn declaration(env: &Env, prop: &Property) -> String { - let bound: String; + let generic_param: String; let set_param = if prop.is_get { - bound = String::new(); + generic_param = String::new(); String::new() } else if let Some(set_bound) = prop.set_bound() { - let alias = set_bound - .alias() - .expect("Property only supports IsA bound type with an alias"); - bound = format!("<{}: IsA<{}>>", alias, set_bound.type_str); - format!(", {}: Option<&{}>", prop.var_name, alias) + generic_param = prop.bounds.to_generic_params_str(); + format!( + ", {}: {}", + prop.var_name, + set_bound.full_type_parameter_reference(prop.set_in_ref_mode, prop.nullable, false), + ) } else { - bound = String::new(); + generic_param = String::new(); let dir = library::ParameterDirection::In; let param_type = RustType::builder(env, prop.typ) .direction(dir) @@ -99,7 +100,7 @@ fn declaration(env: &Env, prop: &Property) -> String { .ref_mode(prop.set_in_ref_mode) .try_build_param() .into_string(); - format!(", {}: {}", prop.var_name, param_type) + format!(", {}: {param_type}", prop.var_name) }; let return_str = if prop.is_get { let dir = library::ParameterDirection::Return; @@ -114,8 +115,8 @@ fn declaration(env: &Env, prop: &Property) -> String { String::new() }; format!( - "fn {}{}(&self{}){}", - prop.func_name, bound, set_param, return_str + "fn {}{generic_param}(&self{set_param}){return_str}", + prop.func_name, ) } diff --git a/src/writer/to_code.rs b/src/writer/to_code.rs index 4b8608215..fd66e1e16 100644 --- a/src/writer/to_code.rs +++ b/src/writer/to_code.rs @@ -170,12 +170,12 @@ impl ToCode for Chunk { Name(ref name) => vec![name.clone()], ExternCFunc { ref name, - ref bounds, + ref generic_params, ref parameters, ref body, ref return_value, } => { - let prefix = format!(r#"unsafe extern "C" fn {name}{bounds}("#); + let prefix = format!(r#"unsafe extern "C" fn {name}{generic_params}("#); let suffix = ")".to_string(); let params: Vec<_> = parameters .iter()