Skip to content

Commit 5ca7fa5

Browse files
committed
General pipeline updates
This were the result of me implementing the Python bindings using the new pipeline system. There were a handful of things I realized I needed/wanted there that didn't come up when I was implementing the JS bindings. Add `FfiTypeNode`. I didn't originally have this as part of the general pipeline, since it wasn't needed there. However, virtually all language pipelines are going to want to add it so we may as well do it in shared code. Determine enum discriminants for the bindings. This uses basically the same algorithm as from `ComponentInterface`. The one difference is that for enums without a specified `repr`, this calculates the smallest integer width needed to store the variants. I used a new pipeline technique for the enum discriminents: the old/optional `discr` field gets renamed to `meta_discr` so that we can create a new/non-optional `discr` field. Adding the `Node::has_descendant` method. I've noticed I'm implementing this code again and again, so it feels a good time to generalize. Several other smaller changes/fixes.
1 parent c7f6caa commit 5ca7fa5

File tree

18 files changed

+398
-78
lines changed

18 files changed

+398
-78
lines changed

docs/manual/src/internals/bindings_ir_pipeline.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ pub struct Node {
6565
}
6666
```
6767

68+
### Field Conversion order
69+
70+
Fields are converted in declaration order.
71+
If a node contains 2 fields that can come from a single source field, the first one declared will "win".
72+
73+
```rust
74+
#[derive(Node)]
75+
pub struct SourceNode {
76+
foo: Option<String>
77+
}
78+
79+
#[derive(Node)]
80+
pub struct DestNode {
81+
/// This field will be set to `SourceNode::foo`
82+
#[node(from(foo))]
83+
maybe_foo: Option<String>
84+
/// This field will be set to an empty string
85+
/// A pipeline pass can then populate this field using `maybe_foo`
86+
foo: String,
87+
}
88+
```
89+
6890
## Module structure
6991

7092
Each IRs will typically have a module dedicated to them with the following structure:

uniffi_bindgen/src/pipeline/general/callback_interfaces.rs

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,24 @@
55
//! FFI info for callback interfaces
66
77
use super::*;
8+
use heck::ToUpperCamelCase;
89
use std::collections::HashSet;
910

1011
pub fn pass(module: &mut Module) -> Result<()> {
1112
let crate_name = module.crate_name.clone();
13+
let module_name = module.name.clone();
1214
module.try_visit_mut(|cbi: &mut CallbackInterface| {
13-
cbi.vtable = vtable(&crate_name, &cbi.name, cbi.methods.clone())?;
15+
cbi.vtable = vtable(&module_name, &crate_name, &cbi.name, cbi.methods.clone())?;
1416
Ok(())
1517
})?;
1618
module.try_visit_mut(|int: &mut Interface| {
1719
if int.imp.has_callback_interface() {
18-
int.vtable = Some(vtable(&crate_name, &int.name, int.methods.clone())?);
20+
int.vtable = Some(vtable(
21+
&module_name,
22+
&crate_name,
23+
&int.name,
24+
int.methods.clone(),
25+
)?);
1926
}
2027
Ok(())
2128
})?;
@@ -47,17 +54,27 @@ pub fn pass(module: &mut Module) -> Result<()> {
4754
Ok(())
4855
}
4956

50-
fn vtable(crate_name: &str, interface_name: &str, methods: Vec<Method>) -> Result<VTable> {
57+
fn vtable(
58+
module_name: &str,
59+
crate_name: &str,
60+
interface_name: &str,
61+
methods: Vec<Method>,
62+
) -> Result<VTable> {
5163
Ok(VTable {
5264
struct_type: FfiType::Struct(FfiStructName(format!(
5365
"VTableCallbackInterface{}",
5466
interface_name
55-
))),
67+
)))
68+
.into(),
5669
interface_name: interface_name.to_string(),
5770
init_fn: RustFfiFunctionName(uniffi_meta::init_callback_vtable_fn_symbol_name(
5871
crate_name,
5972
interface_name,
6073
)),
74+
free_fn_type: FfiFunctionTypeName(format!(
75+
"CallbackInterfaceFree{}_{interface_name}",
76+
module_name.to_upper_camel_case(),
77+
)),
6178
methods: methods
6279
.into_iter()
6380
.enumerate()
@@ -67,7 +84,8 @@ fn vtable(crate_name: &str, interface_name: &str, methods: Vec<Method>) -> Resul
6784
ffi_type: FfiType::Function(FfiFunctionTypeName(format!(
6885
"CallbackInterface{}Method{i}",
6986
interface_name
70-
))),
87+
)))
88+
.into(),
7189
})
7290
})
7391
.collect::<Result<Vec<_>>>()?,
@@ -119,7 +137,7 @@ fn add_vtable_ffi_definitions(module: &mut Module) -> Result<()> {
119137
name: async_info.ffi_foreign_future_result.clone(),
120138
fields: match ffi_return_type {
121139
Some(return_ffi_type) => vec![
122-
FfiField::new("return_value", return_ffi_type),
140+
FfiField::new("return_value", return_ffi_type.ty),
123141
FfiField::new("call_status", FfiType::RustCallStatus),
124142
],
125143
None => vec![
@@ -154,7 +172,8 @@ fn add_vtable_ffi_definitions(module: &mut Module) -> Result<()> {
154172
ffi_definitions.extend([
155173
FfiFunctionType {
156174
name: FfiFunctionTypeName(format!(
157-
"CallbackInterfaceFree{module_name}_{interface_name}"
175+
"CallbackInterfaceFree{}_{interface_name}",
176+
module_name.to_upper_camel_case(),
158177
)),
159178
arguments: vec![FfiArgument::new(
160179
"handle",
@@ -172,13 +191,15 @@ fn add_vtable_ffi_definitions(module: &mut Module) -> Result<()> {
172191
fields: vtable
173192
.methods
174193
.iter()
175-
.map(|vtable_meth| {
176-
FfiField::new(&vtable_meth.callable.name, vtable_meth.ffi_type.clone())
194+
.map(|vtable_meth| FfiField {
195+
name: vtable_meth.callable.name.clone(),
196+
ty: vtable_meth.ffi_type.clone(),
177197
})
178198
.chain([FfiField::new(
179199
"uniffi_free",
180200
FfiType::Function(FfiFunctionTypeName(format!(
181-
"CallbackInterfaceFree{module_name}_{interface_name}"
201+
"CallbackInterfaceFree{}_{interface_name}",
202+
module_name.to_upper_camel_case(),
182203
))),
183204
)])
184205
.collect(),
@@ -191,7 +212,7 @@ fn add_vtable_ffi_definitions(module: &mut Module) -> Result<()> {
191212
name: vtable.init_fn.clone(),
192213
arguments: vec![FfiArgument {
193214
name: "vtable".into(),
194-
ty: FfiType::Reference(Box::new(vtable.struct_type.clone())),
215+
ty: FfiType::Reference(Box::new(vtable.struct_type.ty.clone())).into(),
195216
}],
196217
return_type: FfiReturnType { ty: None },
197218
async_data: None,
@@ -220,7 +241,8 @@ fn vtable_method(
220241
ty: FfiType::Handle(HandleKind::CallbackInterface {
221242
module_name: module_name.to_string(),
222243
interface_name: interface_name.to_string(),
223-
}),
244+
})
245+
.into(),
224246
})
225247
.chain(callable.arguments.iter().map(|arg| FfiArgument {
226248
name: arg.name.clone(),
@@ -229,11 +251,11 @@ fn vtable_method(
229251
.chain(std::iter::once(match &callable.return_type.ty {
230252
Some(ty) => FfiArgument {
231253
name: "uniffi_out_return".into(),
232-
ty: FfiType::MutReference(Box::new(ty.ffi_type.clone())),
254+
ty: FfiType::MutReference(Box::new(ty.ffi_type.ty.clone())).into(),
233255
},
234256
None => FfiArgument {
235257
name: "uniffi_out_return".into(),
236-
ty: FfiType::VoidPointer,
258+
ty: FfiType::VoidPointer.into(),
237259
},
238260
}))
239261
.collect(),
@@ -257,7 +279,8 @@ fn vtable_method_async(
257279
ty: FfiType::Handle(HandleKind::CallbackInterface {
258280
module_name: module_name.to_string(),
259281
interface_name: interface_name.to_string(),
260-
}),
282+
})
283+
.into(),
261284
})
262285
.chain(callable.arguments.iter().map(|arg| FfiArgument {
263286
name: arg.name.clone(),
@@ -266,17 +289,18 @@ fn vtable_method_async(
266289
.chain([
267290
FfiArgument {
268291
name: "uniffi_future_callback".into(),
269-
ty: FfiType::Function(async_data.ffi_foreign_future_complete.clone()),
292+
ty: FfiType::Function(async_data.ffi_foreign_future_complete.clone()).into(),
270293
},
271294
FfiArgument {
272295
name: "uniffi_callback_data".into(),
273-
ty: FfiType::Handle(HandleKind::ForeignFutureCallbackData),
296+
ty: FfiType::Handle(HandleKind::ForeignFutureCallbackData).into(),
274297
},
275298
FfiArgument {
276299
name: "uniffi_out_return".into(),
277300
ty: FfiType::MutReference(Box::new(FfiType::Struct(FfiStructName(
278301
"ForeignFuture".to_owned(),
279-
)))),
302+
))))
303+
.into(),
280304
},
281305
])
282306
.collect(),

uniffi_bindgen/src/pipeline/general/checksums.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub fn pass(module: &mut Module) -> Result<()> {
2121
async_data: None,
2222
arguments: vec![],
2323
return_type: FfiReturnType {
24-
ty: Some(FfiType::UInt32),
24+
ty: Some(FfiType::UInt32.into()),
2525
},
2626
has_rust_call_status_arg: false,
2727
kind: FfiFunctionKind::UniffiContractVersion,
@@ -33,13 +33,9 @@ pub fn pass(module: &mut Module) -> Result<()> {
3333
// Checksums, these are used to check that the bindings were built against the same
3434
// exported interface as the loaded library.
3535
let mut checksums = vec![];
36-
module.try_visit(|callable: &Callable| {
36+
// Closure to visit a callable
37+
let mut visit_callable = |callable: &Callable| {
3738
let Some(checksum) = callable.checksum else {
38-
if matches!(callable.kind, CallableKind::VTableMethod { .. }) {
39-
// UDL-based callbacks don't have checksum functions, ignore those rather than
40-
// returning an error.
41-
return Ok(());
42-
}
4339
bail!("Checksum not set for {:#?}", callable);
4440
};
4541
let fn_name = match &callable.kind {
@@ -66,7 +62,7 @@ pub fn pass(module: &mut Module) -> Result<()> {
6662
async_data: None,
6763
arguments: vec![],
6864
return_type: FfiReturnType {
69-
ty: Some(FfiType::UInt16),
65+
ty: Some(FfiType::UInt16.into()),
7066
},
7167
has_rust_call_status_arg: false,
7268
kind: FfiFunctionKind::Checksum,
@@ -75,7 +71,21 @@ pub fn pass(module: &mut Module) -> Result<()> {
7571
checksum,
7672
));
7773
Ok(())
74+
};
75+
// Call `visit_callable` for callables that we have checksums for
76+
// (functions/constructors/methods), but not ones where we don't (VTable methods and UniFFI
77+
// traits).
78+
module.try_visit(|function: &Function| function.try_visit(&mut visit_callable))?;
79+
module.try_visit(|int: &Interface| {
80+
for cons in int.constructors.iter() {
81+
visit_callable(&cons.callable)?;
82+
}
83+
for meth in int.methods.iter() {
84+
visit_callable(&meth.callable)?;
85+
}
86+
Ok(())
7887
})?;
88+
7989
for (ffi_func, checksum) in checksums {
8090
module.checksums.push(Checksum {
8191
fn_name: ffi_func.name.clone(),

uniffi_bindgen/src/pipeline/general/enums.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,128 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

55
use super::*;
6+
use std::cmp::{max, min};
67

78
pub fn pass(en: &mut Enum) -> Result<()> {
89
en.is_flat = match en.shape {
910
EnumShape::Error { flat } => flat,
1011
EnumShape::Enum => en.variants.iter().all(|v| v.fields.is_empty()),
1112
};
13+
for v in en.variants.iter_mut() {
14+
v.fields_kind = if v.fields.is_empty() {
15+
FieldsKind::Unit
16+
} else if v.fields.iter().any(|f| f.name.is_empty()) {
17+
FieldsKind::Unnamed
18+
} else {
19+
FieldsKind::Named
20+
};
21+
}
22+
determine_discriminants(en)?;
23+
Ok(())
24+
}
25+
26+
/// Set the `Enum::discr_type` and `Variant::discr` fields
27+
///
28+
/// If we get a value from the metadata, then those will be used. Otherwise, we will calculate the
29+
/// discriminants by following Rust's logic.
30+
pub fn determine_discriminants(en: &mut Enum) -> Result<()> {
31+
let signed = match &en.meta_discr_type {
32+
Some(type_node) => match &type_node.ty {
33+
Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => false,
34+
Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => true,
35+
ty => bail!("Invalid enum discriminant type: {ty:?}"),
36+
},
37+
// If not specified, then the discriminant type is signed. We'll calculate the width as we
38+
// go through the variant discriminants
39+
None => true,
40+
};
41+
42+
// Calculate all variant discriminants.
43+
// Use a placeholder value for the type, since we don't necessarily know it yet.
44+
let mut max_value: u64 = 0;
45+
let mut min_value: i64 = 0;
46+
let mut last_variant: Option<&Variant> = None;
47+
48+
for variant in en.variants.iter_mut() {
49+
let discr = match &variant.meta_discr {
50+
None => {
51+
let lit = match last_variant {
52+
None => {
53+
if signed {
54+
Literal::Int(0, Radix::Decimal, TypeNode::default())
55+
} else {
56+
Literal::UInt(0, Radix::Decimal, TypeNode::default())
57+
}
58+
}
59+
Some(variant) => match &variant.discr.lit {
60+
Literal::UInt(val, _, _) => {
61+
Literal::UInt(val + 1, Radix::Decimal, TypeNode::default())
62+
}
63+
Literal::Int(val, _, _) => {
64+
Literal::Int(val + 1, Radix::Decimal, TypeNode::default())
65+
}
66+
lit => bail!("Invalid enum discriminant literal: {lit:?}"),
67+
},
68+
};
69+
LiteralNode { lit }
70+
}
71+
Some(lit_node) => lit_node.clone(),
72+
};
73+
match &discr.lit {
74+
Literal::UInt(val, _, _) => {
75+
max_value = max(max_value, *val);
76+
}
77+
Literal::Int(val, _, _) => {
78+
if *val >= 0 {
79+
max_value = max(max_value, *val as u64);
80+
} else {
81+
min_value = min(min_value, *val);
82+
}
83+
}
84+
_ => unreachable!(),
85+
}
86+
variant.discr = discr;
87+
last_variant = Some(variant);
88+
}
89+
90+
// Finally, we can figure out the discriminant type
91+
en.discr_type = match &en.meta_discr_type {
92+
Some(type_node) => type_node.clone(),
93+
None => {
94+
if min_value >= i8::MIN as i64 && max_value <= i8::MAX as u64 {
95+
TypeNode {
96+
ty: Type::Int8,
97+
..TypeNode::default()
98+
}
99+
} else if min_value >= i16::MIN as i64 && max_value <= i16::MAX as u64 {
100+
TypeNode {
101+
ty: Type::Int16,
102+
..TypeNode::default()
103+
}
104+
} else if min_value >= i32::MIN as i64 && max_value <= i32::MAX as u64 {
105+
TypeNode {
106+
ty: Type::Int32,
107+
..TypeNode::default()
108+
}
109+
} else if max_value <= i64::MAX as u64 {
110+
// Note: no need to check `min_value` since that's always in the `i64` bounds.
111+
TypeNode {
112+
ty: Type::Int64,
113+
..TypeNode::default()
114+
}
115+
} else {
116+
bail!("Enum repr not set and magnitude exceeds i64::MAX");
117+
}
118+
}
119+
};
120+
for variant in en.variants.iter_mut() {
121+
match &mut variant.discr.lit {
122+
Literal::UInt(_, _, type_node) | Literal::Int(_, _, type_node) => {
123+
*type_node = en.discr_type.clone();
124+
}
125+
_ => unreachable!(),
126+
}
127+
}
128+
12129
Ok(())
13130
}

0 commit comments

Comments
 (0)