Skip to content

Commit

Permalink
Merge pull request #1945 from fzyzcjy/feat/12020
Browse files Browse the repository at this point in the history
Support `Result` type when Rust calls Dart back
  • Loading branch information
fzyzcjy authored May 20, 2024
2 parents 2b87aac + f5fdcac commit 4b055ae
Show file tree
Hide file tree
Showing 51 changed files with 4,431 additions and 2,661 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
clippy::double_parens,
clippy::let_and_return,
clippy::too_many_arguments,
clippy::match_single_binding
clippy::match_single_binding,
clippy::let_unit_value
)]

// Section: imports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl<'a> ApiDartGeneratorInfoTrait for DartFnApiDartGenerator<'a> {
fn dart_api_type(&self) -> String {
format!(
"FutureOr<{}> Function({})",
ApiDartGenerator::new(self.ir.output.clone(), self.context).dart_api_type(),
ApiDartGenerator::new(self.ir.output.normal.clone(), self.context).dart_api_type(),
(self.ir.inputs.iter())
.map(|x| ApiDartGenerator::new(x.clone(), self.context).dart_api_type())
.join(", "),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ impl<'a> CodecSseTyTrait for DelegateCodecSseTy<'a> {
IrTypeDelegate::String => "utf8.encoder.convert(self)".to_owned(),
IrTypeDelegate::Char => "self".to_owned(),
IrTypeDelegate::PrimitiveEnum(_) => "self.index".to_owned(),
IrTypeDelegate::Backtrace | IrTypeDelegate::AnyhowException => {
IrTypeDelegate::Backtrace => {
return Some(format!("{};", lang.throw_unreachable("")));
}
IrTypeDelegate::AnyhowException => "self.message".to_owned(),
IrTypeDelegate::Map(_) => {
"self.entries.map((e) => (e.key, e.value)).toList()".to_owned()
}
Expand Down Expand Up @@ -132,9 +133,10 @@ impl<'a> CodecSseTyTrait for DelegateCodecSseTy<'a> {
IrTypeDelegate::PrimitiveEnum(inner) => {
rust_decode_primitive_enum(inner, self.context.ir_pack, "inner")
}
IrTypeDelegate::Backtrace | IrTypeDelegate::AnyhowException => {
IrTypeDelegate::Backtrace => {
return Some(format!("{};", lang.throw_unreachable("")));
}
IrTypeDelegate::AnyhowException => r#"anyhow::anyhow!("{}", inner)"#.to_owned(),
IrTypeDelegate::Map(_) => "inner.into_iter().collect()".to_owned(),
IrTypeDelegate::Set(_) => "inner.into_iter().collect()".to_owned(),
IrTypeDelegate::Time(ir) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::codegen::generator::api_dart::spec_generator::base::ApiDartGenerator;
use crate::codegen::generator::wire::dart::spec_generator::base::*;
use crate::codegen::generator::wire::dart::spec_generator::misc::ty::WireDartGeneratorMiscTrait;
use crate::codegen::generator::wire::dart::spec_generator::output_code::WireDartOutputCode;
use crate::codegen::generator::wire::rust::spec_generator::misc::ty::dart_fn::DartFnOutputAction;
use crate::codegen::ir::ty::IrTypeTrait;
use crate::library::codegen::generator::api_dart::spec_generator::info::ApiDartGeneratorInfoTrait;
use itertools::Itertools;
Expand All @@ -25,26 +26,54 @@ impl<'a> WireDartGeneratorMiscTrait for DartFnWireDartGenerator<'a> {
let dart_api_type =
ApiDartGenerator::new(self.ir.clone(), self.context.as_api_dart_context())
.dart_api_type();
let return_type_safe_ident = self.ir.output.safe_ident();

let output_normal_dart_api_type = ApiDartGenerator::new(
self.ir.output.normal.clone(),
self.context.as_api_dart_context(),
)
.dart_api_type();
let output_error_dart_api_type = ApiDartGenerator::new(
self.ir.output.error.clone(),
self.context.as_api_dart_context(),
)
.dart_api_type();
let output_normal_safe_ident = self.ir.output.normal.safe_ident();
let output_error_safe_ident = self.ir.output.error.safe_ident();

let action_normal = DartFnOutputAction::Success as i32;
let action_error = DartFnOutputAction::Error as i32;

let api_impl_body = format!(
"
r#"
Future<void> Function(int, {repeated_dynamics})
encode_{ir_safe_ident}({dart_api_type} raw) {{
return (callId, {raw_parameter_names}) async {{
{decode_block}
final rawOutput = await raw({parameter_names});
Box<{output_normal_dart_api_type}>? rawOutput;
Box<{output_error_dart_api_type}>? rawError;
try {{
rawOutput = Box(await raw({parameter_names}));
}} catch (e, s) {{
rawError = Box(AnyhowException("$e\n\n$s"));
}}
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_{return_type_safe_ident}(rawOutput, serializer);
assert((rawOutput != null) ^ (rawError != null));
if (rawOutput != null) {{
serializer.buffer.putUint8({action_normal});
sse_encode_{output_normal_safe_ident}(rawOutput.value, serializer);
}} else {{
serializer.buffer.putUint8({action_error});
sse_encode_{output_error_safe_ident}(rawError!.value, serializer);
}}
final output = serializer.intoRaw();
generalizedFrbRustBinding.dartFnDeliverOutput(
callId: callId, ptr: output.ptr, rustVecLen: output.rustVecLen, dataLen: output.dataLen);
}};
}}
",
"#,
);
Some(Acc::new_common(WireDartOutputCode {
api_impl_class_body: api_impl_body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ clippy::unused_unit,
clippy::double_parens,
clippy::let_and_return,
clippy::too_many_arguments,
clippy::match_single_binding
clippy::match_single_binding,
clippy::let_unit_value
)]"#;

fn generate_imports(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,39 @@ impl<'a> WireRustGeneratorMiscTrait for DartFnWireRustGenerator<'a> {
.map(|i| format!("arg{i}.into_into_dart().into_dart(),"))
.join("");

let return_type = self.ir.output.rust_api_type();
let return_type_outer = self.ir.output.rust_api_type();
let output_normal_type = self.ir.output.normal.rust_api_type();
let output_error_type = self.ir.output.error.rust_api_type();

let action_normal = DartFnOutputAction::Success as i32;
let action_error = DartFnOutputAction::Error as i32;

let maybe_unwrap_ans = if self.ir.output.api_fallible {
""
} else {
r#"let ans = ans.expect("Dart throws exception but Rust side assume it is not failable");"#
};

Acc::new_common(
format!(
"fn decode_{safe_ident}(
dart_opaque: flutter_rust_bridge::DartOpaque,
) -> impl Fn({parameter_types}) -> flutter_rust_bridge::DartFnFuture<{return_type}> {{
) -> impl Fn({parameter_types}) -> flutter_rust_bridge::DartFnFuture<{return_type_outer}> {{
use flutter_rust_bridge::IntoDart;
async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, {parameter_names_and_types}) -> {return_type} {{
async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, {parameter_names_and_types}) -> {return_type_outer} {{
let args = vec![{into_dart_expressions}];
let message = {HANDLER_NAME}.dart_fn_invoke(dart_opaque, args).await;
<{return_type}>::sse_decode_single(message)
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let action = deserializer.cursor.read_u8().unwrap();
let ans = match action {{
{action_normal} => std::result::Result::Ok(<{output_normal_type}>::sse_decode(&mut deserializer)),
{action_error} => std::result::Result::Err(<{output_error_type}>::sse_decode(&mut deserializer)),
_ => unreachable!(),
}};
deserializer.end();
{maybe_unwrap_ans}ans
}}
move |{parameter_names_and_types}| {{
Expand All @@ -57,3 +77,9 @@ impl<'a> WireRustGeneratorMiscTrait for DartFnWireRustGenerator<'a> {
Some(self.ir.get_delegate().rust_api_type())
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DartFnOutputAction {
Success = 0,
Error = 1,
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::codegen::generator::wire::rust::spec_generator::base::*;

mod boxed;
mod dart_fn;
pub(crate) mod dart_fn;
mod dart_opaque;
mod delegate;
mod dynamic;
Expand Down
36 changes: 35 additions & 1 deletion frb_codegen/src/library/codegen/ir/ty/dart_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ use itertools::Itertools;
crate::ir! {
pub struct IrTypeDartFn {
pub inputs: Vec<IrType>,
pub output: Box<IrType>,
pub output: Box<IrDartFnOutput>,
}

pub(crate) struct IrDartFnOutput {
pub(crate) normal: IrType,
pub(crate) error: IrType,
/// Whether the error is provided to users, or error yields panic
pub(crate) api_fallible: bool,
}
}

Expand Down Expand Up @@ -46,3 +53,30 @@ impl IrTypeDartFn {
IrType::DartOpaque(IrTypeDartOpaque)
}
}

impl IrDartFnOutput {
pub(crate) fn visit_types<F: FnMut(&IrType) -> bool>(
&self,
f: &mut F,
ir_context: &impl IrContext,
) {
self.normal.visit_types(f, ir_context);
self.error.visit_types(f, ir_context);
}

pub(crate) fn safe_ident(&self) -> String {
format!("{}_{}", self.normal.safe_ident(), self.error.safe_ident())
}

pub(crate) fn rust_api_type(&self) -> String {
if self.api_fallible {
format!(
"std::result::Result<{}, {}>",
self.normal.rust_api_type(),
self.error.rust_api_type()
)
} else {
self.normal.rust_api_type()
}
}
}
61 changes: 4 additions & 57 deletions frb_codegen/src/library/codegen/parser/function_parser/output.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use crate::codegen::ir::ty::delegate::IrTypeDelegate;
use crate::codegen::ir::ty::primitive::IrTypePrimitive;
use crate::codegen::ir::ty::IrType;
use crate::codegen::ir::ty::IrType::{EnumRef, StructRef};
use crate::codegen::parser::function_parser::{FunctionParser, FunctionPartialInfo};
use crate::codegen::parser::type_parser::unencodable::splay_segments;
use crate::codegen::parser::type_parser::result::parse_type_maybe_result;
use crate::codegen::parser::type_parser::TypeParserParsingContext;
use syn::*;

Expand All @@ -28,66 +26,15 @@ impl<'a, 'b> FunctionParser<'a, 'b> {
context: &TypeParserParsingContext,
) -> anyhow::Result<FunctionPartialInfo> {
let ir = self.type_parser.parse_type(ty, context)?;

if let IrType::RustAutoOpaque(inner) = ir {
match splay_segments(&inner.raw.segments).last() {
Some(("Result", args)) => {
return parse_fn_output_type_result(
&(args.iter())
.map(|arg| self.type_parser.parse_type(arg, context))
.collect::<anyhow::Result<Vec<_>>>()?,
);
}
_ => {}
}
}

let info = parse_type_maybe_result(&ir, self.type_parser, context)?;
Ok(FunctionPartialInfo {
ok_output: Some(self.type_parser.parse_type(ty, context)?),
ok_output: Some(info.ok_output),
error_output: info.error_output,
..Default::default()
})
}
}

fn parse_fn_output_type_result(args: &[IrType]) -> anyhow::Result<FunctionPartialInfo> {
let ok_output = args.first().unwrap();

let is_anyhow = args.len() == 1
|| args.iter().any(|x| {
if let IrType::RustAutoOpaque(inner) = x {
return inner.raw.string == "anyhow :: Error";
}
false
});

let error_output = if is_anyhow {
Some(IrType::Delegate(IrTypeDelegate::AnyhowException))
} else {
args.last().cloned()
};

let error_output = error_output.map(set_is_exception_flag);

Ok(FunctionPartialInfo {
ok_output: Some(ok_output.clone()),
error_output,
..Default::default()
})
}

fn set_is_exception_flag(mut ty: IrType) -> IrType {
match &mut ty {
StructRef(ref mut inner) => {
inner.is_exception = true;
}
EnumRef(ref mut inner) => {
inner.is_exception = true;
}
_ => {}
}
ty
}

// Convert primitive Unit type -> None
fn remove_primitive_unit(info: FunctionPartialInfo) -> FunctionPartialInfo {
let ok_output = if info.ok_output == Some(IrType::Primitive(IrTypePrimitive::Unit)) {
Expand Down
21 changes: 17 additions & 4 deletions frb_codegen/src/library/codegen/parser/type_parser/dart_fn.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::codegen::ir::ty::dart_fn::IrDartFnOutput;
use crate::codegen::ir::ty::dart_fn::IrTypeDartFn;
use crate::codegen::ir::ty::delegate::IrTypeDelegate;
use crate::codegen::ir::ty::IrType;
use crate::codegen::parser::type_parser::result::{parse_type_maybe_result, ResultTypeInfo};
use crate::codegen::parser::type_parser::TypeParserWithContext;
use crate::if_then_some;
use anyhow::{bail, Context};
Expand Down Expand Up @@ -38,9 +41,16 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {
.map(|x| self.parse_type(x))
.collect::<anyhow::Result<Vec<_>>>()?;

let output = Box::new(self.parse_dart_fn_output(&arguments.output)?);
let output = self.parse_dart_fn_output(&arguments.output)?;

return Ok(IrType::DartFn(IrTypeDartFn { inputs, output }));
return Ok(IrType::DartFn(IrTypeDartFn {
inputs,
output: Box::new(IrDartFnOutput {
normal: output.ok_output,
error: output.error_output.clone().unwrap_or(FALLBACK_ERROR_TYPE),
api_fallible: output.error_output.is_some(),
}),
}));

// This will stop the whole generator and tell the users, so we do not care about testing it
// frb-coverage:ignore-start
Expand All @@ -52,7 +62,7 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {

// the function signature is not covered while the whole body is covered - looks like a bug in coverage tool
// frb-coverage:ignore-start
fn parse_dart_fn_output(&mut self, return_type: &ReturnType) -> anyhow::Result<IrType> {
fn parse_dart_fn_output(&mut self, return_type: &ReturnType) -> anyhow::Result<ResultTypeInfo> {
// frb-coverage:ignore-end
if let ReturnType::Type(_, ret_ty) = return_type {
if let Type::Path(TypePath { ref path, .. }) = **ret_ty {
Expand All @@ -67,7 +77,8 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {
.find(|arg| matches!(arg, GenericArgument::Type(_)))
.unwrap()
{
return self.parse_type(inner_ty);
let ir = self.parse_type(inner_ty)?;
return parse_type_maybe_result(&ir, self.inner, self.context);

// This will stop the whole generator and tell the users, so we do not care about testing it
// frb-coverage:ignore-start
Expand All @@ -82,6 +93,8 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {
}
}

const FALLBACK_ERROR_TYPE: IrType = IrType::Delegate(IrTypeDelegate::AnyhowException);

// // Use this unit "test" to see how a type will be parsed into a tree
// #[cfg(test)]
// mod tests {
Expand Down
1 change: 1 addition & 0 deletions frb_codegen/src/library/codegen/parser/type_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub(crate) mod optional;
pub(crate) mod path;
pub(crate) mod path_data;
pub(crate) mod primitive;
pub(crate) mod result;
pub(crate) mod rust_auto_opaque;
mod rust_opaque;
pub(crate) mod structure;
Expand Down
Loading

0 comments on commit 4b055ae

Please sign in to comment.