Skip to content

Commit d65f584

Browse files
committed
Generate Into<Option<_>> in argument position where applicable
This commit adds support for generating `Into<Option<_>>`, `Into<Option<&_>>` and `Into<Option<&IsA<_>>` in argument position. The existing `analysis::Bound` type was updated to support new bounds for these variants: 1. Owned value This is straightforward, just need a `to_glib_extra` `.into()`: ```rust impl AudioDecoder { fn finish_frame(&self, buf: impl Into<Option<gst::Buffer>>) -> Result<...> { [...] ffi::gst_audio_decoder_finish_frame( [...] buf.into().into_glib_ptr(), ) [...] } } ``` 2. Concrete types by reference Same, but needs a lifetime: ```rust impl TextOverlay { fn set_text<'a>(&self, text: impl Into<Option<&'a str>>) { [...] ffi::ges_text_overlay_set_text() [...] text.into().to_glib_none().0, )) [...] } } ``` 3. Trait bound by reference Needs a lifetime and a generic parameter and a longer `to_glib_extra`: ```rust impl Pipeline { fn use_clock<'a, P: IsA<Clock>>(&self, clock: impl Into<Option<&'a P>>) { [...] ffi::gst_pipeline_use_clock( [...] clock.into().as_ref().map(|p| p.as_ref()).to_glib_none().0, )) [...] } } ``` Other Changes: These changes revealed a bug in trampoline `user_data` generic parameters handling: these parameters can be grouped, in which case the grouped callbacks are passed as a tuple in `user_data`. The actual callback types are then required to recover the callbacks from the tuple. The way it was implemented, all the callback generic parameters (bounds) from the outter function were considered as taking part in the `user_data`, regardless of the actual grouping. From the code bases on which I tested this, this had no consequences since callbacks for a particular function were all grouped anyway. However, with the new bounds implemented by this commit, functions with callbacks can now use a lifetime, which may not be part of the callback signatures, in which case it should not be included as part of a callback group. This is now fixed. I took the liberty to add details and remane a couple of identifiers to ease my understanding of what this code was achieving. This commit also moved the `Bound::alias` field to the elligible `BoundType` variants and `BoundType` variants contain named fields instead of a tuple of value: * Only certain `BoundType`s actually used an alias. * This make it clear whether the `char` refers to a lifetime or an alias.
1 parent 7b437e8 commit d65f584

File tree

16 files changed

+499
-301
lines changed

16 files changed

+499
-301
lines changed

src/analysis/bounds.rs

Lines changed: 204 additions & 74 deletions
Large diffs are not rendered by default.

src/analysis/child_properties.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,17 @@ fn analyze_property(
115115

116116
let mut bounds_str = String::new();
117117
let dir = ParameterDirection::In;
118-
let set_params = if Bounds::type_for(env, typ).is_some() {
118+
let set_params = if Bounds::type_for(
119+
env,
120+
typ,
121+
set_in_ref_mode,
122+
nullable,
123+
dir,
124+
false,
125+
Default::default(),
126+
)
127+
.is_some()
128+
{
119129
// TODO: bounds_str push?!?!
120130
bounds_str.push_str("TODO");
121131
format!("{prop_name}: {TYPE_PARAMETERS_START}")

src/analysis/class_builder.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
},
1010
config::{self, GObject},
1111
env::Env,
12-
library,
12+
library::{self, Nullable},
1313
traits::*,
1414
};
1515

@@ -124,9 +124,10 @@ fn analyze_property(
124124
let (get_out_ref_mode, set_in_ref_mode, _) = get_property_ref_modes(env, prop);
125125

126126
let mut bounds = Bounds::default();
127-
if let Some(bound) = Bounds::type_for(env, prop.typ) {
127+
let set_has_bound =
128+
bounds.add_for_property_setter(env, prop.typ, &prop.name, set_in_ref_mode, Nullable(false));
129+
if set_has_bound {
128130
imports.add("glib::prelude::*");
129-
bounds.add_parameter(&prop.name, &rust_type_res.into_string(), bound, false);
130131
}
131132

132133
Some(Property {

src/analysis/function_parameters.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,15 @@ pub fn analyze(
289289
}
290290
if let Some(array_par) = array_par {
291291
let mut array_name = nameutil::mangle_keywords(&array_par.name);
292-
if let Some(bound_type) = Bounds::type_for(env, array_par.typ) {
292+
if let Some(bound_type) = Bounds::type_for(
293+
env,
294+
array_par.typ,
295+
if move_ { RefMode::None } else { RefMode::ByRef },
296+
Nullable(false),
297+
array_par.direction,
298+
array_par.instance_parameter,
299+
array_par.scope,
300+
) {
293301
array_name = (array_name.into_owned()
294302
+ &bound_type.get_to_glib_extra(
295303
*array_par.nullable,

src/analysis/functions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,9 @@ fn analyze_callbacks(
390390
par.ref_mode
391391
},
392392
par.nullable,
393+
par.direction,
393394
par.instance_parameter,
395+
par.scope,
394396
),
395397
None,
396398
)

src/analysis/ref_mode.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,29 @@ impl RefMode {
128128
pub fn is_none(self) -> bool {
129129
matches!(self, Self::None)
130130
}
131+
132+
pub fn to_string_with_maybe_lt(self, lt: Option<char>) -> String {
133+
match self {
134+
RefMode::None | RefMode::ByRefFake => {
135+
assert!(lt.is_none(), "incompatible ref mode {self:?} with lifetime");
136+
String::new()
137+
}
138+
RefMode::ByRef | RefMode::ByRefImmut | RefMode::ByRefConst => {
139+
if let Some(lt) = lt {
140+
format!("&'{lt}")
141+
} else {
142+
"&".to_string()
143+
}
144+
}
145+
RefMode::ByRefMut => {
146+
if let Some(lt) = lt {
147+
format!("&'{lt} mut ")
148+
} else {
149+
"&mut ".to_string()
150+
}
151+
}
152+
}
153+
}
131154
}
132155

133156
impl fmt::Display for RefMode {

src/analysis/trampolines.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@ pub fn analyze(
8787
bounds.add_parameter(
8888
"this",
8989
&type_name.into_string(),
90-
BoundType::AsRef(None),
90+
BoundType::new_as_ref(),
9191
false,
9292
);
9393
} else {
9494
bounds.add_parameter(
9595
"this",
9696
&type_name.into_string(),
97-
BoundType::IsA(None),
97+
BoundType::new_is_a(),
9898
false,
9999
);
100100
}
@@ -132,14 +132,14 @@ pub fn analyze(
132132
bounds.add_parameter(
133133
"this",
134134
&type_name.into_string(),
135-
BoundType::AsRef(None),
135+
BoundType::new_as_ref(),
136136
false,
137137
);
138138
} else {
139139
bounds.add_parameter(
140140
"this",
141141
&type_name.into_string(),
142-
BoundType::IsA(None),
142+
BoundType::new_is_a(),
143143
false,
144144
);
145145
}

src/chunk/chunk.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub enum Chunk {
6767
Name(String),
6868
ExternCFunc {
6969
name: String,
70-
bounds: String,
70+
generic_params: String,
7171
parameters: Vec<Param>,
7272
body: Box<Chunk>,
7373
return_value: Option<String>,

src/codegen/bound.rs

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,83 +9,91 @@ use crate::{
99
impl Bound {
1010
/// Returns the type parameter reference.
1111
/// Currently always returns the alias.
12-
pub(super) fn type_parameter_reference(&self) -> Option<char> {
13-
self.alias
12+
///
13+
/// This doesn't include the lifetime, which could be shared by other type
14+
/// parameters. Use [Bounds::to_generic_params_str](crate::Bounds::to_generic_params_str)
15+
/// to get the full generic parameter list, including lifetimes.
16+
pub fn type_parameter_definition(&self, r#async: bool) -> Option<String> {
17+
use BoundType::*;
18+
match self.bound_type {
19+
NoWrapper { alias: Some(alias) } => Some(format!("{alias}: {}", self.type_str)),
20+
IsA { alias: Some(alias) } => Some(format!(
21+
"{alias}: IsA<{}>{}",
22+
self.type_str,
23+
if r#async { " + Clone + 'static" } else { "" },
24+
)),
25+
IntoOptionIsA { alias, .. } => {
26+
let alias = alias.expect("should be defined in this context");
27+
Some(format!(
28+
"{alias}: IsA<{}>{}",
29+
self.type_str,
30+
if r#async { " + Clone + 'static" } else { "" },
31+
))
32+
}
33+
_ => None,
34+
}
1435
}
1536

1637
/// Returns the type parameter reference, with [`BoundType::IsA`] wrapped
1738
/// in `ref_mode` and `nullable` as appropriate.
18-
pub(super) fn full_type_parameter_reference(
39+
pub fn full_type_parameter_reference(
1940
&self,
2041
ref_mode: RefMode,
2142
nullable: Nullable,
2243
r#async: bool,
2344
) -> String {
24-
let ref_str = ref_mode.to_string();
25-
26-
// Generate `impl Trait` if this bound does not have an alias
27-
let trait_bound = match self.type_parameter_reference() {
28-
Some(t) => t.to_string(),
29-
None => {
30-
let trait_bound = self.trait_bound(r#async);
31-
let trait_bound = format!("impl {trait_bound}");
45+
use BoundType::*;
46+
match self.bound_type {
47+
NoWrapper { alias } => alias.unwrap().to_string(),
48+
IsA { alias: None } => {
49+
let suffix = r#async
50+
.then(|| " + Clone + 'static".to_string())
51+
.unwrap_or_default();
3252

33-
// Combining a ref mode and lifetime requires parentheses for disambiguation
34-
match self.bound_type {
35-
BoundType::IsA(lifetime) => {
36-
// TODO: This is fragile
37-
let has_lifetime = r#async || lifetime.is_some();
53+
let mut trait_bound = format!("impl IsA<{}>{suffix}", self.type_str);
3854

39-
if !ref_str.is_empty() && has_lifetime {
40-
format!("({trait_bound})")
41-
} else {
42-
trait_bound
43-
}
44-
}
45-
_ => trait_bound,
55+
let ref_str = ref_mode.to_string();
56+
if !ref_str.is_empty() && r#async {
57+
trait_bound = format!("({trait_bound})");
4658
}
47-
}
48-
};
4959

50-
match self.bound_type {
51-
BoundType::IsA(_) if *nullable => {
52-
format!("Option<{ref_str}{trait_bound}>")
60+
if *nullable {
61+
format!("Option<{ref_str}{trait_bound}>")
62+
} else {
63+
format!("{ref_str}{trait_bound}")
64+
}
5365
}
54-
BoundType::IsA(_) => format!("{ref_str}{trait_bound}"),
55-
BoundType::AsRef(_) if *nullable => {
56-
format!("Option<{trait_bound}>")
66+
IsA { alias: Some(alias) } => {
67+
let ref_str = ref_mode.to_string();
68+
if *nullable {
69+
format!("Option<{ref_str} {alias}>")
70+
} else {
71+
format!("{ref_str} {alias}")
72+
}
5773
}
58-
BoundType::NoWrapper | BoundType::AsRef(_) => trait_bound,
59-
}
60-
}
61-
62-
/// Returns the type parameter definition for this bound, usually
63-
/// of the form `T: SomeTrait` or `T: IsA<Foo>`.
64-
pub(super) fn type_parameter_definition(&self, r#async: bool) -> Option<String> {
65-
self.alias
66-
.map(|alias| format!("{}: {}", alias, self.trait_bound(r#async)))
67-
}
68-
69-
/// Returns the trait bound, usually of the form `SomeTrait`
70-
/// or `IsA<Foo>`.
71-
pub(super) fn trait_bound(&self, r#async: bool) -> String {
72-
match self.bound_type {
73-
BoundType::NoWrapper => self.type_str.clone(),
74-
BoundType::IsA(lifetime) => {
75-
if r#async {
76-
assert!(lifetime.is_none(), "Async overwrites lifetime");
74+
AsRef => {
75+
if *nullable {
76+
format!("Option<impl AsRef<{}>>", self.type_str)
77+
} else {
78+
format!("impl AsRef<{}>", self.type_str)
7779
}
78-
let is_a = format!("IsA<{}>", self.type_str);
80+
}
81+
IntoOption => {
82+
format!("impl Into<Option<{}>>", self.type_str)
83+
}
84+
IntoOptionRef { lt } => {
85+
assert!(lt.is_some(), "must be defined in this context");
86+
let ref_str = ref_mode.to_string_with_maybe_lt(lt);
7987

80-
let lifetime = r#async
81-
.then(|| " + Clone + 'static".to_string())
82-
.or_else(|| lifetime.map(|l| format!(" + '{l}")))
83-
.unwrap_or_default();
88+
format!("impl Into<Option<{ref_str} {}>>", self.type_str)
89+
}
90+
IntoOptionIsA { lt, alias } => {
91+
assert!(lt.is_some(), "must be defined in this context");
92+
let ref_str = ref_mode.to_string_with_maybe_lt(lt);
93+
let alias = alias.expect("must be defined in this context");
8494

85-
format!("{is_a}{lifetime}")
95+
format!("impl Into<Option<{ref_str} {alias}>>")
8696
}
87-
BoundType::AsRef(Some(_ /* lifetime */)) => panic!("AsRef cannot have a lifetime"),
88-
BoundType::AsRef(None) => format!("AsRef<{}>", self.type_str),
8997
}
9098
}
9199
}

src/codegen/bounds.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use crate::analysis::bounds::Bounds;
2+
3+
impl Bounds {
4+
pub fn to_generic_params_str(&self) -> String {
5+
self.to_generic_params_str_(false)
6+
}
7+
8+
pub fn to_generic_params_str_async(&self) -> String {
9+
self.to_generic_params_str_(true)
10+
}
11+
12+
fn to_generic_params_str_(&self, r#async: bool) -> String {
13+
let mut res = String::new();
14+
15+
if self.lifetimes.is_empty() && self.used.iter().find_map(|b| b.alias()).is_none() {
16+
return res;
17+
}
18+
19+
res.push('<');
20+
let mut is_first = true;
21+
22+
for lt in self.lifetimes.iter() {
23+
if is_first {
24+
is_first = false;
25+
} else {
26+
res.push_str(", ");
27+
}
28+
res.push('\'');
29+
res.push(*lt);
30+
}
31+
32+
for bound in self.used.iter() {
33+
if let Some(type_param_def) = bound.type_parameter_definition(r#async) {
34+
if is_first {
35+
is_first = false;
36+
} else {
37+
res.push_str(", ");
38+
}
39+
res.push_str(&type_param_def);
40+
}
41+
}
42+
res.push('>');
43+
44+
res
45+
}
46+
}

0 commit comments

Comments
 (0)