flutter_rust_bridge_codegen 2.0.0-dev.0

High-level memory-safe binding generator for Flutter/Dart <-> Rust
Documentation
pub(crate) mod argument;
pub(crate) mod output;
mod transformer;

use crate::codegen::ir::field::IrField;
use crate::codegen::ir::func::{
    IrFunc, IrFuncMode, IrFuncOwnerInfo, IrFuncOwnerInfoMethod, IrFuncOwnerInfoMethodMode,
};
use crate::codegen::ir::namespace::{Namespace, NamespacedName};
use crate::codegen::ir::ty::primitive::IrTypePrimitive;
use crate::codegen::ir::ty::IrType;
use crate::codegen::parser::attribute_parser::FrbAttributes;
use crate::codegen::parser::function_extractor::GeneralizedItemFn;
use crate::codegen::parser::type_parser::misc::parse_comments;
use crate::codegen::parser::type_parser::{TypeParser, TypeParserParsingContext};
use crate::library::codegen::ir::ty::IrTypeTrait;
use anyhow::{bail, Context};
use itertools::concat;
use log::debug;
use std::fmt::Debug;
use std::path::Path;
use syn::*;
use IrType::Primitive;

const STREAM_SINK_IDENT: &str = "StreamSink";

pub(crate) struct FunctionParser<'a, 'b> {
    type_parser: &'a mut TypeParser<'b>,
}

impl<'a, 'b> FunctionParser<'a, 'b> {
    pub(crate) fn new(type_parser: &'a mut TypeParser<'b>) -> Self {
        Self { type_parser }
    }

    pub(crate) fn parse_function(
        &mut self,
        func: &GeneralizedItemFn,
        file_path: &Path,
        rust_crate_dir: &Path,
    ) -> anyhow::Result<Option<IrFunc>> {
        self.parse_function_inner(func, file_path, rust_crate_dir)
            .with_context(|| format!("function={:?}", func.sig().ident))
    }

    fn parse_function_inner(
        &mut self,
        func: &GeneralizedItemFn,
        file_path: &Path,
        rust_crate_dir: &Path,
    ) -> anyhow::Result<Option<IrFunc>> {
        debug!("parse_function function name: {:?}", func.sig().ident);

        let sig = func.sig();
        let namespace = Namespace::new_from_rust_crate_path(file_path, rust_crate_dir)?;
        let src_lineno = func.span().start().line;
        let context = TypeParserParsingContext {
            initiated_namespace: namespace.clone(),
        };

        let owner = if let Some(owner) = self.parse_owner(func, &context)? {
            owner
        } else {
            return Ok(None);
        };

        let func_name = parse_name(sig, &owner);
        let attributes = FrbAttributes::parse(func.attrs())?;

        let mut info = FunctionPartialInfo::default();
        for (i, sig_input) in sig.inputs.iter().enumerate() {
            info = info.merge(self.parse_fn_arg(i, sig_input, &owner, &context)?)?;
        }
        info = info.merge(self.parse_fn_output(sig, &context)?)?;
        info = self.transform_fn_info(info, &context);

        let codec_mode_pack = attributes.codec_mode_pack();
        let mode = compute_func_mode(&attributes, &info);

        Ok(Some(IrFunc {
            name: NamespacedName::new(namespace, func_name),
            inputs: info.inputs,
            output: info.ok_output.unwrap_or(Primitive(IrTypePrimitive::Unit)),
            error_output: info.error_output,
            owner,
            mode,
            rust_async: sig.asyncness.is_some(),
            comments: parse_comments(func.attrs()),
            codec_mode_pack,
            src_lineno,
        }))
    }

    fn parse_owner(
        &mut self,
        item_fn: &GeneralizedItemFn,
        context: &TypeParserParsingContext,
    ) -> anyhow::Result<Option<IrFuncOwnerInfo>> {
        Ok(Some(match item_fn {
            GeneralizedItemFn::Function { .. } => IrFuncOwnerInfo::Function,
            GeneralizedItemFn::Method {
                item_impl,
                impl_item_fn,
            } => {
                let mode = if matches!(impl_item_fn.sig.inputs.first(), Some(FnArg::Receiver(..))) {
                    IrFuncOwnerInfoMethodMode::Instance
                } else {
                    IrFuncOwnerInfoMethodMode::Static
                };

                let enum_or_struct_name =
                    if let Some(x) = self.parse_enum_or_struct_name(item_impl, context)? {
                        x
                    } else {
                        return Ok(None);
                    };

                IrFuncOwnerInfo::Method(IrFuncOwnerInfoMethod {
                    enum_or_struct_name,
                    actual_method_name: impl_item_fn.sig.ident.to_string(),
                    mode,
                })
            }
        }))
    }

    fn parse_enum_or_struct_name(
        &mut self,
        item_impl: &ItemImpl,
        context: &TypeParserParsingContext,
    ) -> anyhow::Result<Option<NamespacedName>> {
        let self_ty_path = if let Type::Path(self_ty_path) = item_impl.self_ty.as_ref() {
            self_ty_path
        } else {
            return Ok(None);
        };

        let enum_or_struct_name = (self_ty_path.path.segments.first().unwrap().ident).to_string();
        let syn_ty: Type = parse_str(&enum_or_struct_name)?;
        let ty = self.type_parser.parse_type(&syn_ty, context)?;

        let namespace: Option<Namespace> = ty.self_namespace();
        Ok(namespace.map(|namespace| NamespacedName::new(namespace, enum_or_struct_name)))
    }
}

fn compute_func_mode(attributes: &FrbAttributes, info: &FunctionPartialInfo) -> IrFuncMode {
    info.mode.unwrap_or(if attributes.sync() {
        IrFuncMode::Sync
    } else {
        IrFuncMode::Normal
    })
}

fn parse_name(sig: &Signature, owner: &IrFuncOwnerInfo) -> String {
    match owner {
        IrFuncOwnerInfo::Function => sig.ident.to_string(),
        IrFuncOwnerInfo::Method(method) => {
            format!(
                "{}_{}",
                method.enum_or_struct_name.name, method.actual_method_name
            )
        }
    }
}

#[derive(Debug, Default)]
struct FunctionPartialInfo {
    inputs: Vec<IrField>,
    ok_output: Option<IrType>,
    error_output: Option<IrType>,
    mode: Option<IrFuncMode>,
}

impl FunctionPartialInfo {
    fn merge(self, other: Self) -> anyhow::Result<Self> {
        Ok(Self {
            inputs: concat([self.inputs, other.inputs]),
            ok_output: merge_option(self.ok_output, other.ok_output).context("ok_output type")?,
            error_output: merge_option(self.error_output, other.error_output)
                .context("error_output type")?,
            mode: merge_option(self.mode, other.mode).context("mode")?,
        })
    }
}

fn merge_option<T: Debug>(a: Option<T>, b: Option<T>) -> anyhow::Result<Option<T>> {
    if a.is_some() && b.is_some() {
        bail!("Function has conflicting arguments and/or outputs: {a:?} and {b:?}");
    }
    Ok(a.or(b))
}