Skip to content

Commit ede0c31

Browse files
Merge pull request #1153 from MarijnS95/bounds-impl
Use `impl Trait` instead of generic type names with trait bound
2 parents 545d89f + ee35a13 commit ede0c31

File tree

7 files changed

+138
-40
lines changed

7 files changed

+138
-40
lines changed

src/analysis/bounds.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,24 @@ pub enum BoundType {
2828

2929
impl BoundType {
3030
pub fn need_isa(&self) -> bool {
31-
matches!(*self, BoundType::IsA(_))
31+
matches!(*self, Self::IsA(_))
32+
}
33+
34+
// TODO: This is just a heuristic for now, based on what we do in codegen!
35+
// Theoretically the surrounding function should determine whether it needs to
36+
// reuse an alias (ie. to use in `call_func::<P, Q, R>`) or not.
37+
// In the latter case an `impl` is generated instead of a type name/alias.
38+
pub fn has_alias(&self) -> bool {
39+
matches!(*self, Self::NoWrapper)
3240
}
3341
}
3442

3543
#[derive(Clone, Eq, Debug, PartialEq)]
3644
pub struct Bound {
3745
pub bound_type: BoundType,
3846
pub parameter_name: String,
39-
pub alias: char,
47+
/// Bound does not have an alias when `param: impl type_str` is used
48+
pub alias: Option<char>,
4049
pub type_str: String,
4150
pub callback_modified: bool,
4251
}
@@ -217,7 +226,9 @@ impl Bounds {
217226
if self.used.iter().any(|n| n.parameter_name == name) {
218227
return;
219228
}
220-
let alias = self.unused.pop_front().expect("No free type aliases!");
229+
let alias = bound_type
230+
.has_alias()
231+
.then(|| self.unused.pop_front().expect("No free type aliases!"));
221232
self.used.push(Bound {
222233
bound_type,
223234
parameter_name: name.to_owned(),
@@ -375,15 +386,36 @@ mod tests {
375386
#[test]
376387
fn get_parameter_bound() {
377388
let mut bounds: Bounds = Default::default();
378-
let typ = BoundType::IsA(None);
389+
let typ = BoundType::NoWrapper;
379390
bounds.add_parameter("a", "", typ.clone(), false);
380391
bounds.add_parameter("b", "", typ.clone(), false);
381392
let bound = bounds.get_parameter_bound("a").unwrap();
382-
assert_eq!(bound.alias, 'P');
393+
// `NoWrapper `bounds are expected to have an alias:
394+
assert_eq!(bound.alias, Some('P'));
383395
assert_eq!(bound.bound_type, typ);
384396
let bound = bounds.get_parameter_bound("b").unwrap();
385-
assert_eq!(bound.alias, 'Q');
397+
assert_eq!(bound.alias, Some('Q'));
386398
assert_eq!(bound.bound_type, typ);
387399
assert_eq!(bounds.get_parameter_bound("c"), None);
388400
}
401+
402+
#[test]
403+
fn impl_bound() {
404+
let mut bounds: Bounds = Default::default();
405+
let typ = BoundType::IsA(None);
406+
bounds.add_parameter("a", "", typ.clone(), false);
407+
bounds.add_parameter("b", "", typ.clone(), false);
408+
let bound = bounds.get_parameter_bound("a").unwrap();
409+
// `IsA` is simplified to an inline `foo: impl IsA<Bar>` and
410+
// lacks an alias/type-parameter:
411+
assert_eq!(bound.alias, None);
412+
assert_eq!(bound.bound_type, typ);
413+
414+
let typ = BoundType::AsRef(None);
415+
bounds.add_parameter("c", "", typ.clone(), false);
416+
let bound = bounds.get_parameter_bound("c").unwrap();
417+
// Same `impl AsRef<Foo>` simplification as `IsA`:
418+
assert_eq!(bound.alias, None);
419+
assert_eq!(bound.bound_type, typ);
420+
}
389421
}

src/analysis/child_properties.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
use crate::{
2-
analysis::{bounds::Bounds, imports::Imports, ref_mode::RefMode, rust_type::RustType},
3-
codegen::function,
2+
analysis::{
3+
bounds::{Bound, Bounds},
4+
imports::Imports,
5+
ref_mode::RefMode,
6+
rust_type::RustType,
7+
},
48
config,
9+
consts::TYPE_PARAMETERS_START,
510
env::Env,
611
library::{self, ParameterDirection},
712
nameutil,
@@ -123,14 +128,25 @@ fn analyze_property(
123128
.ref_mode(RefMode::ByRefFake)
124129
.try_build()
125130
.into_string();
126-
let mut bounds = Bounds::default();
127-
bounds.add_parameter("P", &r_type, bound, false);
128-
let (s_bounds, _) = function::bounds(&bounds, &[], false, false);
129-
// Because the bounds won't necessarily be added into the final function, we
130-
// only keep the "inner" part to make the string computation easier. So
131-
// `<T: X>` becomes `T: X`.
132-
bounds_str.push_str(&s_bounds[1..s_bounds.len() - 1]);
133-
format!("{}: {}", prop_name, bounds.iter().last().unwrap().alias)
131+
132+
let _bound = Bound {
133+
bound_type: bound,
134+
parameter_name: TYPE_PARAMETERS_START.to_string(),
135+
alias: Some(TYPE_PARAMETERS_START.to_owned()),
136+
type_str: r_type,
137+
callback_modified: false,
138+
};
139+
// TODO: bounds_str push?!?!
140+
bounds_str.push_str("TODO");
141+
format!("{}: {}", prop_name, TYPE_PARAMETERS_START)
142+
// let mut bounds = Bounds::default();
143+
// bounds.add_parameter("P", &r_type, bound, false);
144+
// let (s_bounds, _) = function::bounds(&bounds, &[], false);
145+
// // Because the bounds won't necessarily be added into the final function, we
146+
// // only keep the "inner" part to make the string computation easier. So
147+
// // `<T: X>` becomes `T: X`.
148+
// bounds_str.push_str(&s_bounds[1..s_bounds.len() - 1]);
149+
// format!("{}: {}", prop_name, bounds.iter().last().unwrap().alias)
134150
} else {
135151
format!(
136152
"{}: {}",

src/codegen/bound.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ 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) -> char {
12+
pub(super) fn type_parameter_reference(&self) -> Option<char> {
1313
self.alias
1414
}
1515

@@ -19,22 +19,48 @@ impl Bound {
1919
&self,
2020
ref_mode: RefMode,
2121
nullable: Nullable,
22+
r#async: bool,
2223
) -> String {
23-
let t = self.type_parameter_reference();
2424
let ref_str = ref_mode.for_rust_type();
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);
32+
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();
38+
39+
if !ref_str.is_empty() && has_lifetime {
40+
format!("({})", trait_bound)
41+
} else {
42+
trait_bound
43+
}
44+
}
45+
_ => trait_bound,
46+
}
47+
}
48+
};
49+
2550
match self.bound_type {
2651
BoundType::IsA(_) if *nullable => {
27-
format!("Option<{}{}>", ref_str, t)
52+
format!("Option<{}{}>", ref_str, trait_bound)
2853
}
29-
BoundType::IsA(_) => format!("{}{}", ref_str, t),
30-
BoundType::NoWrapper | BoundType::AsRef(_) => t.to_string(),
54+
BoundType::IsA(_) => format!("{}{}", ref_str, trait_bound),
55+
BoundType::NoWrapper | BoundType::AsRef(_) => trait_bound,
3156
}
3257
}
3358

3459
/// Returns the type parameter definition for this bound, usually
3560
/// of the form `T: SomeTrait` or `T: IsA<Foo>`.
36-
pub(super) fn type_parameter_definition(&self, r#async: bool) -> String {
37-
format!("{}: {}", self.alias, self.trait_bound(r#async))
61+
pub(super) fn type_parameter_definition(&self, r#async: bool) -> Option<String> {
62+
self.alias
63+
.map(|alias| format!("{}: {}", alias, self.trait_bound(r#async)))
3864
}
3965

4066
/// Returns the trait bound, usually of the form `SomeTrait`
@@ -47,6 +73,7 @@ impl Bound {
4773
assert!(lifetime.is_none(), "Async overwrites lifetime");
4874
}
4975
let is_a = format!("IsA<{}>", self.type_str);
76+
5077
let lifetime = r#async
5178
.then(|| " + Clone + 'static".to_string())
5279
.or_else(|| lifetime.map(|l| format!(" + '{}", l)))

src/codegen/function.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub fn declaration(env: &Env, analysis: &analysis::functions::Info) -> String {
180180
param_str.push_str(", ")
181181
}
182182
let c_par = &analysis.parameters.c_parameters[par.ind_c];
183-
let s = c_par.to_parameter(env, &analysis.bounds);
183+
let s = c_par.to_parameter(env, &analysis.bounds, false);
184184
param_str.push_str(&s);
185185
}
186186

@@ -210,8 +210,12 @@ pub fn declaration_futures(env: &Env, analysis: &analysis::functions::Info) -> S
210210

211211
if c_par.name == "callback" || c_par.name == "cancellable" {
212212
skipped += 1;
213-
if let Some(bound) = analysis.bounds.get_parameter_bound(&c_par.name) {
214-
skipped_bounds.push(bound.type_parameter_reference());
213+
if let Some(alias) = analysis
214+
.bounds
215+
.get_parameter_bound(&c_par.name)
216+
.and_then(|bound| bound.type_parameter_reference())
217+
{
218+
skipped_bounds.push(alias);
215219
}
216220
continue;
217221
}
@@ -220,7 +224,7 @@ pub fn declaration_futures(env: &Env, analysis: &analysis::functions::Info) -> S
220224
param_str.push_str(", ")
221225
}
222226

223-
let s = c_par.to_parameter(env, &analysis.bounds);
227+
let s = c_par.to_parameter(env, &analysis.bounds, true);
224228
param_str.push_str(&s);
225229
}
226230

@@ -246,7 +250,8 @@ pub fn bounds(
246250

247251
let skip_lifetimes = bounds
248252
.iter()
249-
.filter(|bound| skip.contains(&bound.alias))
253+
// TODO: False or true?
254+
.filter(|bound| bound.alias.map_or(false, |alias| skip.contains(&alias)))
250255
.filter_map(|bound| match bound.bound_type {
251256
IsA(lifetime) | AsRef(lifetime) => lifetime,
252257
_ => None,
@@ -260,13 +265,18 @@ pub fn bounds(
260265
.collect::<Vec<_>>();
261266

262267
let bounds = bounds.iter().filter(|bound| {
263-
!skip.contains(&bound.alias) && (!filter_callback_modified || !bound.callback_modified)
268+
bound.alias.map_or(true, |alias| !skip.contains(&alias))
269+
&& (!filter_callback_modified || !bound.callback_modified)
264270
});
265271

266272
let type_names = lifetimes
267273
.iter()
268274
.cloned()
269-
.chain(bounds.clone().map(|b| b.type_parameter_definition(r#async)))
275+
.chain(
276+
bounds
277+
.clone()
278+
.filter_map(|b| b.type_parameter_definition(r#async)),
279+
)
270280
.collect::<Vec<_>>();
271281

272282
let type_names = if type_names.is_empty() {
@@ -277,7 +287,9 @@ pub fn bounds(
277287

278288
let bounds = lifetimes
279289
.into_iter()
280-
.chain(bounds.map(|b| b.alias.to_string()))
290+
// TODO: enforce that this is only used on NoWrapper!
291+
// TODO: Analyze if alias is used in function, otherwise set to None!
292+
.chain(bounds.filter_map(|b| b.alias).map(|a| a.to_string()))
281293
.collect::<Vec<_>>();
282294

283295
(type_names, bounds)

src/codegen/object.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ fn generate_builder(w: &mut dyn Write, env: &Env, analysis: &analysis::object::I
249249
bound.full_type_parameter_reference(
250250
RefMode::ByRef,
251251
Nullable(false),
252+
false,
252253
)
253254
});
254255
(alias, bounds, ".clone().upcast()")

src/codegen/parameter.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ use crate::{
88
};
99

1010
pub trait ToParameter {
11-
fn to_parameter(&self, env: &Env, bounds: &Bounds) -> String;
11+
fn to_parameter(&self, env: &Env, bounds: &Bounds, r#async: bool) -> String;
1212
}
1313

1414
impl ToParameter for CParameter {
15-
fn to_parameter(&self, env: &Env, bounds: &Bounds) -> String {
15+
fn to_parameter(&self, env: &Env, bounds: &Bounds, r#async: bool) -> String {
1616
if self.instance_parameter {
1717
format!("{}self", self.ref_mode.for_rust_type())
1818
} else {
1919
let type_str = match bounds.get_parameter_bound(&self.name) {
20-
Some(bound) => bound.full_type_parameter_reference(self.ref_mode, self.nullable),
20+
Some(bound) => {
21+
bound.full_type_parameter_reference(self.ref_mode, self.nullable, r#async)
22+
}
2123
None => {
2224
let type_name = RustType::builder(env, self.typ)
2325
.direction(self.direction)

src/codegen/trampoline.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@ pub fn generate(
2424
in_trait: bool,
2525
indent: usize,
2626
) -> Result<()> {
27-
let self_bound = in_trait
28-
.then(|| format!("{}: IsA<{}>, ", TYPE_PARAMETERS_START, analysis.type_name))
27+
let (self_bound, fn_self_bound) = in_trait
28+
.then(|| {
29+
(
30+
format!("{}: IsA<{}>, ", TYPE_PARAMETERS_START, analysis.type_name),
31+
Some(TYPE_PARAMETERS_START.to_string()),
32+
)
33+
})
2934
.unwrap_or_default();
3035

3136
let prepend = tabs(indent);
3237
let params_str = trampoline_parameters(env, analysis);
33-
let func_str = func_string(env, analysis, None, true);
38+
let func_str = func_string(env, analysis, fn_self_bound, true);
3439
let ret_str = trampoline_returns(env, analysis);
3540

3641
writeln!(
@@ -56,7 +61,7 @@ pub fn generate(
5661
pub fn func_string(
5762
env: &Env,
5863
analysis: &Trampoline,
59-
replace_self_bound: Option<&str>,
64+
replace_self_bound: Option<impl AsRef<str>>,
6065
closure: bool,
6166
) -> String {
6267
let param_str = func_parameters(env, analysis, replace_self_bound, closure);
@@ -89,7 +94,7 @@ pub fn func_string(
8994
fn func_parameters(
9095
env: &Env,
9196
analysis: &Trampoline,
92-
replace_self_bound: Option<&str>,
97+
replace_self_bound: Option<impl AsRef<str>>,
9398
closure: bool,
9499
) -> String {
95100
let mut param_str = String::with_capacity(100);
@@ -124,7 +129,10 @@ fn func_parameter(env: &Env, par: &RustParameter, bounds: &Bounds) -> String {
124129
};
125130

126131
match bounds.get_parameter_bound(&par.name) {
127-
Some(bound) => bound.full_type_parameter_reference(ref_mode, par.nullable),
132+
// TODO: ASYNC??
133+
Some(bound) => bound.full_type_parameter_reference(ref_mode, par.nullable, false),
134+
// TODO
135+
// Some((None, _)) => panic!("Trampoline expects type name"),
128136
None => RustType::builder(env, par.typ)
129137
.direction(par.direction)
130138
.nullable(par.nullable)

0 commit comments

Comments
 (0)