mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-11 07:11:01 +03:00
fix: compile error when using enum in flutter
This commit is contained in:
267
libs/flutter_rust_bridge_codegen/src/commands.rs
Normal file
267
libs/flutter_rust_bridge_codegen/src/commands.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::process::Output;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use log::{debug, info, warn};
|
||||
|
||||
#[must_use]
|
||||
fn call_shell(cmd: &str) -> Output {
|
||||
#[cfg(windows)]
|
||||
return execute_command("powershell", &["-noprofile", "-c", cmd], None);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
execute_command("sh", &["-c", cmd], None)
|
||||
}
|
||||
|
||||
pub fn ensure_tools_available() -> Result {
|
||||
let output = call_shell("dart pub global list");
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
if !output.contains("ffigen") {
|
||||
return Err(Error::MissingExe(String::from("ffigen")));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bindgen_rust_to_dart(
|
||||
rust_crate_dir: &str,
|
||||
c_output_path: &str,
|
||||
dart_output_path: &str,
|
||||
dart_class_name: &str,
|
||||
c_struct_names: Vec<String>,
|
||||
llvm_install_path: &[String],
|
||||
llvm_compiler_opts: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
cbindgen(rust_crate_dir, c_output_path, c_struct_names)?;
|
||||
ffigen(
|
||||
c_output_path,
|
||||
dart_output_path,
|
||||
dart_class_name,
|
||||
llvm_install_path,
|
||||
llvm_compiler_opts,
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use = "Error path must be handled."]
|
||||
fn execute_command(bin: &str, args: &[&str], current_dir: Option<&str>) -> Output {
|
||||
let mut cmd = Command::new(bin);
|
||||
cmd.args(args);
|
||||
|
||||
if let Some(current_dir) = current_dir {
|
||||
cmd.current_dir(current_dir);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"execute command: bin={} args={:?} current_dir={:?} cmd={:?}",
|
||||
bin, args, current_dir, cmd
|
||||
);
|
||||
|
||||
let result = cmd
|
||||
.output()
|
||||
.unwrap_or_else(|err| panic!("\"{}\" \"{}\" failed: {}", bin, args.join(" "), err));
|
||||
|
||||
let stdout = String::from_utf8_lossy(&result.stdout);
|
||||
if result.status.success() {
|
||||
debug!(
|
||||
"command={:?} stdout={} stderr={}",
|
||||
cmd,
|
||||
stdout,
|
||||
String::from_utf8_lossy(&result.stderr)
|
||||
);
|
||||
if stdout.contains("fatal error") {
|
||||
warn!("See keywords such as `error` in command output. Maybe there is a problem? command={:?} output={:?}", cmd, result);
|
||||
} else if args.contains(&"ffigen") && stdout.contains("[SEVERE]") {
|
||||
// HACK: If ffigen can't find a header file it will generate broken
|
||||
// bindings but still exit successfully. We can detect these broken
|
||||
// bindings by looking for a "[SEVERE]" log message.
|
||||
//
|
||||
// It may emit SEVERE log messages for non-fatal errors though, so
|
||||
// we don't want to error out completely.
|
||||
|
||||
warn!(
|
||||
"The `ffigen` command emitted a SEVERE error. Maybe there is a problem? command={:?} output=\n{}",
|
||||
cmd, String::from_utf8_lossy(&result.stdout)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"command={:?} stdout={} stderr={}",
|
||||
cmd,
|
||||
stdout,
|
||||
String::from_utf8_lossy(&result.stderr)
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn cbindgen(
|
||||
rust_crate_dir: &str,
|
||||
c_output_path: &str,
|
||||
c_struct_names: Vec<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
debug!(
|
||||
"execute cbindgen rust_crate_dir={} c_output_path={}",
|
||||
rust_crate_dir, c_output_path
|
||||
);
|
||||
|
||||
let config = cbindgen::Config {
|
||||
language: cbindgen::Language::C,
|
||||
sys_includes: vec![
|
||||
"stdbool.h".to_string(),
|
||||
"stdint.h".to_string(),
|
||||
"stdlib.h".to_string(),
|
||||
],
|
||||
no_includes: true,
|
||||
export: cbindgen::ExportConfig {
|
||||
include: c_struct_names
|
||||
.iter()
|
||||
.map(|name| format!("\"{}\"", name))
|
||||
.collect::<Vec<_>>(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
debug!("cbindgen config: {:?}", config);
|
||||
|
||||
let canonical = Path::new(rust_crate_dir)
|
||||
.canonicalize()
|
||||
.expect("Could not canonicalize rust crate dir");
|
||||
let mut path = canonical.to_str().unwrap();
|
||||
|
||||
// on windows get rid of the UNC path
|
||||
if path.starts_with(r"\\?\") {
|
||||
path = &path[r"\\?\".len()..];
|
||||
}
|
||||
|
||||
if cbindgen::generate_with_config(path, config)?.write_to_file(c_output_path) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::str("cbindgen failed writing file").into())
|
||||
}
|
||||
}
|
||||
|
||||
fn ffigen(
|
||||
c_path: &str,
|
||||
dart_path: &str,
|
||||
dart_class_name: &str,
|
||||
llvm_path: &[String],
|
||||
llvm_compiler_opts: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
debug!(
|
||||
"execute ffigen c_path={} dart_path={} llvm_path={:?}",
|
||||
c_path, dart_path, llvm_path
|
||||
);
|
||||
let mut config = format!(
|
||||
"
|
||||
output: '{}'
|
||||
name: '{}'
|
||||
description: 'generated by flutter_rust_bridge'
|
||||
headers:
|
||||
entry-points:
|
||||
- '{}'
|
||||
include-directives:
|
||||
- '{}'
|
||||
comments: false
|
||||
preamble: |
|
||||
// ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names
|
||||
",
|
||||
dart_path, dart_class_name, c_path, c_path,
|
||||
);
|
||||
if !llvm_path.is_empty() {
|
||||
write!(
|
||||
&mut config,
|
||||
"
|
||||
llvm-path:\n"
|
||||
)?;
|
||||
for path in llvm_path {
|
||||
writeln!(&mut config, " - '{}'", path)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !llvm_compiler_opts.is_empty() {
|
||||
config = format!(
|
||||
"{}
|
||||
compiler-opts:
|
||||
- '{}'",
|
||||
config, llvm_compiler_opts
|
||||
);
|
||||
}
|
||||
|
||||
debug!("ffigen config: {}", config);
|
||||
|
||||
let mut config_file = tempfile::NamedTempFile::new()?;
|
||||
std::io::Write::write_all(&mut config_file, config.as_bytes())?;
|
||||
debug!("ffigen config_file: {:?}", config_file);
|
||||
|
||||
// NOTE please install ffigen globally first: `dart pub global activate ffigen`
|
||||
let res = call_shell(&format!(
|
||||
"dart pub global run ffigen --config \"{}\"",
|
||||
config_file.path().to_string_lossy()
|
||||
));
|
||||
if !res.status.success() {
|
||||
let err = String::from_utf8_lossy(&res.stderr);
|
||||
let out = String::from_utf8_lossy(&res.stdout);
|
||||
let pat = "Couldn't find dynamic library in default locations.";
|
||||
if err.contains(pat) || out.contains(pat) {
|
||||
return Err(Error::FfigenLlvm.into());
|
||||
}
|
||||
return Err(
|
||||
Error::string(format!("ffigen failed:\nstderr: {}\nstdout: {}", err, out)).into(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn format_rust(path: &str) -> Result {
|
||||
debug!("execute format_rust path={}", path);
|
||||
let res = execute_command("rustfmt", &[path], None);
|
||||
if !res.status.success() {
|
||||
return Err(Error::Rustfmt(
|
||||
String::from_utf8_lossy(&res.stderr).to_string(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn format_dart(path: &str, line_length: i32) -> Result {
|
||||
debug!(
|
||||
"execute format_dart path={} line_length={}",
|
||||
path, line_length
|
||||
);
|
||||
let res = call_shell(&format!(
|
||||
"dart format {} --line-length {}",
|
||||
path, line_length
|
||||
));
|
||||
if !res.status.success() {
|
||||
return Err(Error::Dartfmt(
|
||||
String::from_utf8_lossy(&res.stderr).to_string(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build_runner(dart_root: &str) -> Result {
|
||||
info!("Running build_runner at {}", dart_root);
|
||||
let out = if cfg!(windows) {
|
||||
call_shell(&format!(
|
||||
"cd \"{}\"; flutter pub run build_runner build --delete-conflicting-outputs",
|
||||
dart_root
|
||||
))
|
||||
} else {
|
||||
call_shell(&format!(
|
||||
"cd \"{}\" && flutter pub run build_runner build --delete-conflicting-outputs",
|
||||
dart_root
|
||||
))
|
||||
};
|
||||
if !out.status.success() {
|
||||
return Err(Error::StringError(format!(
|
||||
"Failed to run build_runner for {}: {}",
|
||||
dart_root,
|
||||
String::from_utf8_lossy(&out.stdout)
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
292
libs/flutter_rust_bridge_codegen/src/config.rs
Normal file
292
libs/flutter_rust_bridge_codegen/src/config.rs
Normal file
@@ -0,0 +1,292 @@
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use convert_case::{Case, Casing};
|
||||
use serde::Deserialize;
|
||||
use structopt::clap::AppSettings;
|
||||
use structopt::StructOpt;
|
||||
use toml::Value;
|
||||
|
||||
#[derive(StructOpt, Debug, PartialEq, Deserialize, Default)]
|
||||
#[structopt(setting(AppSettings::DeriveDisplayOrder))]
|
||||
pub struct RawOpts {
|
||||
/// Path of input Rust code
|
||||
#[structopt(short, long)]
|
||||
pub rust_input: String,
|
||||
/// Path of output generated Dart code
|
||||
#[structopt(short, long)]
|
||||
pub dart_output: String,
|
||||
/// If provided, generated Dart declaration code to this separate file
|
||||
#[structopt(long)]
|
||||
pub dart_decl_output: Option<String>,
|
||||
|
||||
/// Path of output generated C header
|
||||
#[structopt(short, long)]
|
||||
pub c_output: Option<Vec<String>>,
|
||||
/// Crate directory for your Rust project
|
||||
#[structopt(long)]
|
||||
pub rust_crate_dir: Option<String>,
|
||||
/// Path of output generated Rust code
|
||||
#[structopt(long)]
|
||||
pub rust_output: Option<String>,
|
||||
/// Generated class name
|
||||
#[structopt(long)]
|
||||
pub class_name: Option<String>,
|
||||
/// Line length for dart formatting
|
||||
#[structopt(long)]
|
||||
pub dart_format_line_length: Option<i32>,
|
||||
/// Skip automatically adding `mod bridge_generated;` to `lib.rs`
|
||||
#[structopt(long)]
|
||||
pub skip_add_mod_to_lib: bool,
|
||||
/// Path to the installed LLVM
|
||||
#[structopt(long)]
|
||||
pub llvm_path: Option<Vec<String>>,
|
||||
/// LLVM compiler opts
|
||||
#[structopt(long)]
|
||||
pub llvm_compiler_opts: Option<String>,
|
||||
/// Path to root of Dart project, otherwise inferred from --dart-output
|
||||
#[structopt(long)]
|
||||
pub dart_root: Option<String>,
|
||||
/// Skip running build_runner even when codegen-capable code is detected
|
||||
#[structopt(long)]
|
||||
pub no_build_runner: bool,
|
||||
/// Show debug messages.
|
||||
#[structopt(short, long)]
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Opts {
|
||||
pub rust_input_path: String,
|
||||
pub dart_output_path: String,
|
||||
pub dart_decl_output_path: Option<String>,
|
||||
pub c_output_path: Vec<String>,
|
||||
pub rust_crate_dir: String,
|
||||
pub rust_output_path: String,
|
||||
pub class_name: String,
|
||||
pub dart_format_line_length: i32,
|
||||
pub skip_add_mod_to_lib: bool,
|
||||
pub llvm_path: Vec<String>,
|
||||
pub llvm_compiler_opts: String,
|
||||
pub manifest_path: String,
|
||||
pub dart_root: Option<String>,
|
||||
pub build_runner: bool,
|
||||
}
|
||||
|
||||
pub fn parse(raw: RawOpts) -> Opts {
|
||||
let rust_input_path = canon_path(&raw.rust_input);
|
||||
|
||||
let rust_crate_dir = canon_path(&raw.rust_crate_dir.unwrap_or_else(|| {
|
||||
fallback_rust_crate_dir(&rust_input_path)
|
||||
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("rust_crate_dir")))
|
||||
}));
|
||||
let manifest_path = {
|
||||
let mut path = std::path::PathBuf::from_str(&rust_crate_dir).unwrap();
|
||||
path.push("Cargo.toml");
|
||||
path_to_string(path).unwrap()
|
||||
};
|
||||
let rust_output_path = canon_path(&raw.rust_output.unwrap_or_else(|| {
|
||||
fallback_rust_output_path(&rust_input_path)
|
||||
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("rust_output")))
|
||||
}));
|
||||
let class_name = raw.class_name.unwrap_or_else(|| {
|
||||
fallback_class_name(&*rust_crate_dir)
|
||||
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("class_name")))
|
||||
});
|
||||
let c_output_path = raw
|
||||
.c_output
|
||||
.map(|outputs| {
|
||||
outputs
|
||||
.iter()
|
||||
.map(|output| canon_path(output))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
vec![fallback_c_output_path()
|
||||
.unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("c_output")))]
|
||||
});
|
||||
|
||||
let dart_root = {
|
||||
let dart_output = &raw.dart_output;
|
||||
raw.dart_root
|
||||
.as_deref()
|
||||
.map(canon_path)
|
||||
.or_else(|| fallback_dart_root(dart_output).ok())
|
||||
};
|
||||
|
||||
Opts {
|
||||
rust_input_path,
|
||||
dart_output_path: canon_path(&raw.dart_output),
|
||||
dart_decl_output_path: raw
|
||||
.dart_decl_output
|
||||
.as_ref()
|
||||
.map(|s| canon_path(s.as_str())),
|
||||
c_output_path,
|
||||
rust_crate_dir,
|
||||
rust_output_path,
|
||||
class_name,
|
||||
dart_format_line_length: raw.dart_format_line_length.unwrap_or(80),
|
||||
skip_add_mod_to_lib: raw.skip_add_mod_to_lib,
|
||||
llvm_path: raw.llvm_path.unwrap_or_else(|| {
|
||||
vec![
|
||||
"/opt/homebrew/opt/llvm".to_owned(), // Homebrew root
|
||||
"/usr/local/opt/llvm".to_owned(), // Homebrew x86-64 root
|
||||
// Possible Linux LLVM roots
|
||||
"/usr/lib/llvm-9".to_owned(),
|
||||
"/usr/lib/llvm-10".to_owned(),
|
||||
"/usr/lib/llvm-11".to_owned(),
|
||||
"/usr/lib/llvm-12".to_owned(),
|
||||
"/usr/lib/llvm-13".to_owned(),
|
||||
"/usr/lib/llvm-14".to_owned(),
|
||||
"/usr/lib/".to_owned(),
|
||||
"/usr/lib64/".to_owned(),
|
||||
"C:/Program Files/llvm".to_owned(), // Default on Windows
|
||||
"C:/Program Files/LLVM".to_owned(),
|
||||
"C:/msys64/mingw64".to_owned(), // https://packages.msys2.org/package/mingw-w64-x86_64-clang
|
||||
]
|
||||
}),
|
||||
llvm_compiler_opts: raw.llvm_compiler_opts.unwrap_or_else(|| "".to_string()),
|
||||
manifest_path,
|
||||
dart_root,
|
||||
build_runner: !raw.no_build_runner,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_fail_to_guess_error(name: &str) -> String {
|
||||
format!(
|
||||
"fail to guess {}, please specify it manually in command line arguments",
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
fn fallback_rust_crate_dir(rust_input_path: &str) -> Result<String> {
|
||||
let mut dir_curr = Path::new(rust_input_path)
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!(""))?;
|
||||
|
||||
loop {
|
||||
let path_cargo_toml = dir_curr.join("Cargo.toml");
|
||||
|
||||
if path_cargo_toml.exists() {
|
||||
return Ok(dir_curr
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!(""))?
|
||||
.to_string());
|
||||
}
|
||||
|
||||
if let Some(next_parent) = dir_curr.parent() {
|
||||
dir_curr = next_parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(anyhow!(
|
||||
"look at parent directories but none contains Cargo.toml"
|
||||
))
|
||||
}
|
||||
|
||||
fn fallback_c_output_path() -> Result<String> {
|
||||
let named_temp_file = Box::leak(Box::new(tempfile::Builder::new().suffix(".h").tempfile()?));
|
||||
Ok(named_temp_file
|
||||
.path()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!(""))?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn fallback_rust_output_path(rust_input_path: &str) -> Result<String> {
|
||||
Ok(Path::new(rust_input_path)
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!(""))?
|
||||
.join("bridge_generated.rs")
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!(""))?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn fallback_dart_root(dart_output_path: &str) -> Result<String> {
|
||||
let mut res = canon_pathbuf(dart_output_path);
|
||||
while res.pop() {
|
||||
if res.join("pubspec.yaml").is_file() {
|
||||
return res
|
||||
.to_str()
|
||||
.map(ToString::to_string)
|
||||
.ok_or_else(|| anyhow!("Non-utf8 path"));
|
||||
}
|
||||
}
|
||||
Err(anyhow!(
|
||||
"Root of Dart library could not be inferred from Dart output"
|
||||
))
|
||||
}
|
||||
|
||||
fn fallback_class_name(rust_crate_dir: &str) -> Result<String> {
|
||||
let cargo_toml_path = Path::new(rust_crate_dir).join("Cargo.toml");
|
||||
let cargo_toml_content = fs::read_to_string(cargo_toml_path)?;
|
||||
|
||||
let cargo_toml_value = cargo_toml_content.parse::<Value>()?;
|
||||
let package_name = cargo_toml_value
|
||||
.get("package")
|
||||
.ok_or_else(|| anyhow!("no `package` in Cargo.toml"))?
|
||||
.get("name")
|
||||
.ok_or_else(|| anyhow!("no `name` in Cargo.toml"))?
|
||||
.as_str()
|
||||
.ok_or_else(|| anyhow!(""))?;
|
||||
|
||||
Ok(package_name.to_case(Case::Pascal))
|
||||
}
|
||||
|
||||
fn canon_path(sub_path: &str) -> String {
|
||||
let path = canon_pathbuf(sub_path);
|
||||
path_to_string(path).unwrap_or_else(|_| panic!("fail to parse path: {}", sub_path))
|
||||
}
|
||||
|
||||
fn canon_pathbuf(sub_path: &str) -> PathBuf {
|
||||
let mut path =
|
||||
env::current_dir().unwrap_or_else(|_| panic!("fail to parse path: {}", sub_path));
|
||||
path.push(sub_path);
|
||||
path
|
||||
}
|
||||
|
||||
fn path_to_string(path: PathBuf) -> Result<String, OsString> {
|
||||
path.into_os_string().into_string()
|
||||
}
|
||||
|
||||
impl Opts {
|
||||
pub fn dart_api_class_name(&self) -> String {
|
||||
self.class_name.clone()
|
||||
}
|
||||
|
||||
pub fn dart_api_impl_class_name(&self) -> String {
|
||||
format!("{}Impl", self.class_name)
|
||||
}
|
||||
|
||||
pub fn dart_wire_class_name(&self) -> String {
|
||||
format!("{}Wire", self.class_name)
|
||||
}
|
||||
|
||||
/// Returns None if the path terminates in "..", or not utf8.
|
||||
pub fn dart_output_path_name(&self) -> Option<&str> {
|
||||
let name = Path::new(&self.dart_output_path);
|
||||
let root = name.file_name()?.to_str()?;
|
||||
if let Some((name, _)) = root.rsplit_once('.') {
|
||||
Some(name)
|
||||
} else {
|
||||
Some(root)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dart_output_freezed_path(&self) -> Option<String> {
|
||||
Some(
|
||||
Path::new(&self.dart_output_path)
|
||||
.with_extension("freezed.dart")
|
||||
.to_str()?
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
}
|
||||
32
libs/flutter_rust_bridge_codegen/src/error.rs
Normal file
32
libs/flutter_rust_bridge_codegen/src/error.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result = std::result::Result<(), Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("rustfmt failed: {0}")]
|
||||
Rustfmt(String),
|
||||
#[error("dart fmt failed: {0}")]
|
||||
Dartfmt(String),
|
||||
#[error(
|
||||
"ffigen could not find LLVM.
|
||||
Please supply --llvm-path to flutter_rust_bridge_codegen, e.g.:
|
||||
|
||||
flutter_rust_bridge_codegen .. --llvm-path <path_to_llvm>"
|
||||
)]
|
||||
FfigenLlvm,
|
||||
#[error("{0} is not a command, or not executable.")]
|
||||
MissingExe(String),
|
||||
#[error("{0}")]
|
||||
StringError(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn str(msg: &str) -> Self {
|
||||
Self::StringError(msg.to_owned())
|
||||
}
|
||||
|
||||
pub fn string(msg: String) -> Self {
|
||||
Self::StringError(msg)
|
||||
}
|
||||
}
|
||||
14
libs/flutter_rust_bridge_codegen/src/generator/c/mod.rs
Normal file
14
libs/flutter_rust_bridge_codegen/src/generator/c/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
pub fn generate_dummy(func_names: &[String]) -> String {
|
||||
format!(
|
||||
r#"static int64_t dummy_method_to_enforce_bundling(void) {{
|
||||
int64_t dummy_var = 0;
|
||||
{}
|
||||
return dummy_var;
|
||||
}}"#,
|
||||
func_names
|
||||
.iter()
|
||||
.map(|func_name| { format!(" dummy_var ^= ((int64_t) (void*) {});", func_name) })
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
393
libs/flutter_rust_bridge_codegen/src/generator/dart/mod.rs
Normal file
393
libs/flutter_rust_bridge_codegen/src/generator/dart/mod.rs
Normal file
@@ -0,0 +1,393 @@
|
||||
mod ty;
|
||||
mod ty_boxed;
|
||||
mod ty_delegate;
|
||||
mod ty_enum;
|
||||
mod ty_general_list;
|
||||
mod ty_optional;
|
||||
mod ty_primitive;
|
||||
mod ty_primitive_list;
|
||||
mod ty_struct;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub use ty::*;
|
||||
pub use ty_boxed::*;
|
||||
pub use ty_delegate::*;
|
||||
pub use ty_enum::*;
|
||||
pub use ty_general_list::*;
|
||||
pub use ty_optional::*;
|
||||
pub use ty_primitive::*;
|
||||
pub use ty_primitive_list::*;
|
||||
pub use ty_struct::*;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use log::debug;
|
||||
|
||||
use crate::ir::IrType::*;
|
||||
use crate::ir::*;
|
||||
use crate::others::*;
|
||||
|
||||
pub struct Output {
|
||||
pub file_prelude: DartBasicCode,
|
||||
pub decl_code: DartBasicCode,
|
||||
pub impl_code: DartBasicCode,
|
||||
}
|
||||
|
||||
pub fn generate(
|
||||
ir_file: &IrFile,
|
||||
dart_api_class_name: &str,
|
||||
dart_api_impl_class_name: &str,
|
||||
dart_wire_class_name: &str,
|
||||
dart_output_file_root: &str,
|
||||
) -> (Output, bool) {
|
||||
let distinct_types = ir_file.distinct_types(true, true);
|
||||
let distinct_input_types = ir_file.distinct_types(true, false);
|
||||
let distinct_output_types = ir_file.distinct_types(false, true);
|
||||
debug!("distinct_input_types={:?}", distinct_input_types);
|
||||
debug!("distinct_output_types={:?}", distinct_output_types);
|
||||
|
||||
let dart_func_signatures_and_implementations = ir_file
|
||||
.funcs
|
||||
.iter()
|
||||
.map(generate_api_func)
|
||||
.collect::<Vec<_>>();
|
||||
let dart_structs = distinct_types
|
||||
.iter()
|
||||
.map(|ty| TypeDartGenerator::new(ty.clone(), ir_file).structs())
|
||||
.collect::<Vec<_>>();
|
||||
let dart_api2wire_funcs = distinct_input_types
|
||||
.iter()
|
||||
.map(|ty| generate_api2wire_func(ty, ir_file))
|
||||
.collect::<Vec<_>>();
|
||||
let dart_api_fill_to_wire_funcs = distinct_input_types
|
||||
.iter()
|
||||
.map(|ty| generate_api_fill_to_wire_func(ty, ir_file))
|
||||
.collect::<Vec<_>>();
|
||||
let dart_wire2api_funcs = distinct_output_types
|
||||
.iter()
|
||||
.map(|ty| generate_wire2api_func(ty, ir_file))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let needs_freezed = distinct_types.iter().any(|ty| match ty {
|
||||
EnumRef(e) if e.is_struct => true,
|
||||
StructRef(s) if s.freezed => true,
|
||||
_ => false,
|
||||
});
|
||||
let freezed_header = if needs_freezed {
|
||||
DartBasicCode {
|
||||
import: "import 'package:freezed_annotation/freezed_annotation.dart';".to_string(),
|
||||
part: format!("part '{}.freezed.dart';", dart_output_file_root),
|
||||
body: "".to_string(),
|
||||
}
|
||||
} else {
|
||||
DartBasicCode::default()
|
||||
};
|
||||
|
||||
let imports = ir_file
|
||||
.struct_pool
|
||||
.values()
|
||||
.flat_map(|s| s.dart_metadata.iter().flat_map(|it| &it.library))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let import_header = if !imports.is_empty() {
|
||||
DartBasicCode {
|
||||
import: imports
|
||||
.iter()
|
||||
.map(|it| match &it.alias {
|
||||
Some(alias) => format!("import '{}' as {};", it.uri, alias),
|
||||
_ => format!("import '{}';", it.uri),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
part: "".to_string(),
|
||||
body: "".to_string(),
|
||||
}
|
||||
} else {
|
||||
DartBasicCode::default()
|
||||
};
|
||||
|
||||
let common_header = DartBasicCode {
|
||||
import: "import 'dart:convert';
|
||||
import 'dart:typed_data';"
|
||||
.to_string(),
|
||||
part: "".to_string(),
|
||||
body: "".to_string(),
|
||||
};
|
||||
|
||||
let decl_body = format!(
|
||||
"abstract class {} {{
|
||||
{}
|
||||
}}
|
||||
|
||||
{}
|
||||
",
|
||||
dart_api_class_name,
|
||||
dart_func_signatures_and_implementations
|
||||
.iter()
|
||||
.map(|(sig, _, comm)| format!("{}{}", comm, sig))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n"),
|
||||
dart_structs.join("\n\n"),
|
||||
);
|
||||
|
||||
let impl_body = format!(
|
||||
"class {dart_api_impl_class_name} extends FlutterRustBridgeBase<{dart_wire_class_name}> implements {dart_api_class_name} {{
|
||||
factory {dart_api_impl_class_name}(ffi.DynamicLibrary dylib) => {dart_api_impl_class_name}.raw({dart_wire_class_name}(dylib));
|
||||
|
||||
{dart_api_impl_class_name}.raw({dart_wire_class_name} inner) : super(inner);
|
||||
|
||||
{}
|
||||
|
||||
// Section: api2wire
|
||||
{}
|
||||
|
||||
// Section: api_fill_to_wire
|
||||
{}
|
||||
}}
|
||||
|
||||
// Section: wire2api
|
||||
{}
|
||||
",
|
||||
dart_func_signatures_and_implementations
|
||||
.iter()
|
||||
.map(|(_, imp, _)| imp.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n"),
|
||||
dart_api2wire_funcs.join("\n\n"),
|
||||
dart_api_fill_to_wire_funcs.join("\n\n"),
|
||||
dart_wire2api_funcs.join("\n\n"),
|
||||
dart_api_impl_class_name = dart_api_impl_class_name,
|
||||
dart_wire_class_name = dart_wire_class_name,
|
||||
dart_api_class_name = dart_api_class_name,
|
||||
);
|
||||
|
||||
let decl_code = &common_header
|
||||
+ &freezed_header
|
||||
+ &import_header
|
||||
+ &DartBasicCode {
|
||||
import: "".to_string(),
|
||||
part: "".to_string(),
|
||||
body: decl_body,
|
||||
};
|
||||
|
||||
let impl_code = &common_header
|
||||
+ &DartBasicCode {
|
||||
import: "import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';".to_string(),
|
||||
part: "".to_string(),
|
||||
body: impl_body,
|
||||
};
|
||||
|
||||
let file_prelude = DartBasicCode {
|
||||
import: format!("{}
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, prefer_single_quotes, prefer_const_constructors
|
||||
",
|
||||
CODE_HEADER
|
||||
),
|
||||
part: "".to_string(),
|
||||
body: "".to_string(),
|
||||
};
|
||||
|
||||
(
|
||||
Output {
|
||||
file_prelude,
|
||||
decl_code,
|
||||
impl_code,
|
||||
},
|
||||
needs_freezed,
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_api_func(func: &IrFunc) -> (String, String, String) {
|
||||
let raw_func_param_list = func
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|input| {
|
||||
format!(
|
||||
"{}{} {}",
|
||||
input.ty.dart_required_modifier(),
|
||||
input.ty.dart_api_type(),
|
||||
input.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let full_func_param_list = [raw_func_param_list, vec!["dynamic hint".to_string()]].concat();
|
||||
|
||||
let wire_param_list = [
|
||||
if func.mode.has_port_argument() {
|
||||
vec!["port_".to_string()]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
func.inputs
|
||||
.iter()
|
||||
.map(|input| {
|
||||
// edge case: ffigen performs its own bool-to-int conversions
|
||||
if let IrType::Primitive(IrTypePrimitive::Bool) = input.ty {
|
||||
input.name.dart_style()
|
||||
} else {
|
||||
format!(
|
||||
"_api2wire_{}({})",
|
||||
&input.ty.safe_ident(),
|
||||
&input.name.dart_style()
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let partial = format!(
|
||||
"{} {}({{ {} }})",
|
||||
func.mode.dart_return_type(&func.output.dart_api_type()),
|
||||
func.name.to_case(Case::Camel),
|
||||
full_func_param_list.join(","),
|
||||
);
|
||||
|
||||
let execute_func_name = match func.mode {
|
||||
IrFuncMode::Normal => "executeNormal",
|
||||
IrFuncMode::Sync => "executeSync",
|
||||
IrFuncMode::Stream => "executeStream",
|
||||
};
|
||||
|
||||
let signature = format!("{};", partial);
|
||||
|
||||
let comments = dart_comments(&func.comments);
|
||||
|
||||
let task_common_args = format!(
|
||||
"
|
||||
constMeta: const FlutterRustBridgeTaskConstMeta(
|
||||
debugName: \"{}\",
|
||||
argNames: [{}],
|
||||
),
|
||||
argValues: [{}],
|
||||
hint: hint,
|
||||
",
|
||||
func.name,
|
||||
func.inputs
|
||||
.iter()
|
||||
.map(|input| format!("\"{}\"", input.name.dart_style()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
func.inputs
|
||||
.iter()
|
||||
.map(|input| input.name.dart_style())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
);
|
||||
|
||||
let implementation = match func.mode {
|
||||
IrFuncMode::Sync => format!(
|
||||
"{} => {}(FlutterRustBridgeSyncTask(
|
||||
callFfi: () => inner.{}({}),
|
||||
{}
|
||||
));",
|
||||
partial,
|
||||
execute_func_name,
|
||||
func.wire_func_name(),
|
||||
wire_param_list.join(", "),
|
||||
task_common_args,
|
||||
),
|
||||
_ => format!(
|
||||
"{} => {}(FlutterRustBridgeTask(
|
||||
callFfi: (port_) => inner.{}({}),
|
||||
parseSuccessData: _wire2api_{},
|
||||
{}
|
||||
));",
|
||||
partial,
|
||||
execute_func_name,
|
||||
func.wire_func_name(),
|
||||
wire_param_list.join(", "),
|
||||
func.output.safe_ident(),
|
||||
task_common_args,
|
||||
),
|
||||
};
|
||||
|
||||
(signature, implementation, comments)
|
||||
}
|
||||
|
||||
fn generate_api2wire_func(ty: &IrType, ir_file: &IrFile) -> String {
|
||||
if let Some(body) = TypeDartGenerator::new(ty.clone(), ir_file).api2wire_body() {
|
||||
format!(
|
||||
"{} _api2wire_{}({} raw) {{
|
||||
{}
|
||||
}}
|
||||
",
|
||||
ty.dart_wire_type(),
|
||||
ty.safe_ident(),
|
||||
ty.dart_api_type(),
|
||||
body,
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_api_fill_to_wire_func(ty: &IrType, ir_file: &IrFile) -> String {
|
||||
if let Some(body) = TypeDartGenerator::new(ty.clone(), ir_file).api_fill_to_wire_body() {
|
||||
let target_wire_type = match ty {
|
||||
Optional(inner) => &inner.inner,
|
||||
it => it,
|
||||
};
|
||||
|
||||
format!(
|
||||
"void _api_fill_to_wire_{}({} apiObj, {} wireObj) {{
|
||||
{}
|
||||
}}",
|
||||
ty.safe_ident(),
|
||||
ty.dart_api_type(),
|
||||
target_wire_type.dart_wire_type(),
|
||||
body,
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_wire2api_func(ty: &IrType, ir_file: &IrFile) -> String {
|
||||
let body = TypeDartGenerator::new(ty.clone(), ir_file).wire2api_body();
|
||||
|
||||
format!(
|
||||
"{} _wire2api_{}(dynamic raw) {{
|
||||
{}
|
||||
}}
|
||||
",
|
||||
ty.dart_api_type(),
|
||||
ty.safe_ident(),
|
||||
body,
|
||||
)
|
||||
}
|
||||
|
||||
fn gen_wire2api_simple_type_cast(s: &str) -> String {
|
||||
format!("return raw as {};", s)
|
||||
}
|
||||
|
||||
/// A trailing newline is included if comments is not empty.
|
||||
fn dart_comments(comments: &[IrComment]) -> String {
|
||||
let mut comments = comments
|
||||
.iter()
|
||||
.map(IrComment::comment)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
if !comments.is_empty() {
|
||||
comments.push('\n');
|
||||
}
|
||||
comments
|
||||
}
|
||||
fn dart_metadata(metadata: &[IrDartAnnotation]) -> String {
|
||||
let mut metadata = metadata
|
||||
.iter()
|
||||
.map(|it| match &it.library {
|
||||
Some(IrDartImport {
|
||||
alias: Some(alias), ..
|
||||
}) => format!("@{}.{}", alias, it.content),
|
||||
_ => format!("@{}", it.content),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
if !metadata.is_empty() {
|
||||
metadata.push('\n');
|
||||
}
|
||||
metadata
|
||||
}
|
||||
64
libs/flutter_rust_bridge_codegen/src/generator/dart/ty.rs
Normal file
64
libs/flutter_rust_bridge_codegen/src/generator/dart/ty.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use crate::generator::dart::*;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait TypeDartGeneratorTrait {
|
||||
fn api2wire_body(&self) -> Option<String>;
|
||||
|
||||
fn api_fill_to_wire_body(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn structs(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeGeneratorContext<'a> {
|
||||
pub ir_file: &'a IrFile,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! type_dart_generator_struct {
|
||||
($cls:ident, $ir_cls:ty) => {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct $cls<'a> {
|
||||
pub ir: $ir_cls,
|
||||
pub context: TypeGeneratorContext<'a>,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[enum_dispatch(TypeDartGeneratorTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeDartGenerator<'a> {
|
||||
Primitive(TypePrimitiveGenerator<'a>),
|
||||
Delegate(TypeDelegateGenerator<'a>),
|
||||
PrimitiveList(TypePrimitiveListGenerator<'a>),
|
||||
Optional(TypeOptionalGenerator<'a>),
|
||||
GeneralList(TypeGeneralListGenerator<'a>),
|
||||
StructRef(TypeStructRefGenerator<'a>),
|
||||
Boxed(TypeBoxedGenerator<'a>),
|
||||
EnumRef(TypeEnumRefGenerator<'a>),
|
||||
}
|
||||
|
||||
impl<'a> TypeDartGenerator<'a> {
|
||||
pub fn new(ty: IrType, ir_file: &'a IrFile) -> Self {
|
||||
let context = TypeGeneratorContext { ir_file };
|
||||
match ty {
|
||||
Primitive(ir) => TypePrimitiveGenerator { ir, context }.into(),
|
||||
Delegate(ir) => TypeDelegateGenerator { ir, context }.into(),
|
||||
PrimitiveList(ir) => TypePrimitiveListGenerator { ir, context }.into(),
|
||||
Optional(ir) => TypeOptionalGenerator { ir, context }.into(),
|
||||
GeneralList(ir) => TypeGeneralListGenerator { ir, context }.into(),
|
||||
StructRef(ir) => TypeStructRefGenerator { ir, context }.into(),
|
||||
Boxed(ir) => TypeBoxedGenerator { ir, context }.into(),
|
||||
EnumRef(ir) => TypeEnumRefGenerator { ir, context }.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use crate::generator::dart::gen_wire2api_simple_type_cast;
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::ir::IrType::{EnumRef, Primitive, StructRef};
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypeBoxedGenerator, IrTypeBoxed);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypeBoxedGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
Some(match &*self.ir.inner {
|
||||
Primitive(_) => {
|
||||
format!("return inner.new_{}(raw);", self.ir.safe_ident())
|
||||
}
|
||||
inner => {
|
||||
format!(
|
||||
"final ptr = inner.new_{}();
|
||||
_api_fill_to_wire_{}(raw, ptr.ref);
|
||||
return ptr;",
|
||||
self.ir.safe_ident(),
|
||||
inner.safe_ident(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn api_fill_to_wire_body(&self) -> Option<String> {
|
||||
if !matches!(*self.ir.inner, Primitive(_)) {
|
||||
Some(format!(
|
||||
" _api_fill_to_wire_{}(apiObj, wireObj.ref);",
|
||||
self.ir.inner.safe_ident()
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
match &*self.ir.inner {
|
||||
StructRef(inner) => format!("return _wire2api_{}(raw);", inner.safe_ident()),
|
||||
EnumRef(inner) => format!("return _wire2api_{}(raw);", inner.safe_ident()),
|
||||
_ => gen_wire2api_simple_type_cast(&self.ir.dart_api_type()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use crate::generator::dart::gen_wire2api_simple_type_cast;
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypeDelegateGenerator, IrTypeDelegate);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypeDelegateGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
Some(match self.ir {
|
||||
IrTypeDelegate::String => {
|
||||
"return _api2wire_uint_8_list(utf8.encoder.convert(raw));".to_string()
|
||||
}
|
||||
IrTypeDelegate::SyncReturnVecU8 => "/*unsupported*/".to_string(),
|
||||
IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
|
||||
format!(
|
||||
"return _api2wire_{}(raw);",
|
||||
self.ir.get_delegate().safe_ident()
|
||||
)
|
||||
}
|
||||
IrTypeDelegate::StringList => "final ans = inner.new_StringList(raw.length);
|
||||
for (var i = 0; i < raw.length; i++) {
|
||||
ans.ref.ptr[i] = _api2wire_String(raw[i]);
|
||||
}
|
||||
return ans;"
|
||||
.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
match &self.ir {
|
||||
IrTypeDelegate::String
|
||||
| IrTypeDelegate::SyncReturnVecU8
|
||||
| IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
|
||||
gen_wire2api_simple_type_cast(&self.ir.dart_api_type())
|
||||
}
|
||||
IrTypeDelegate::StringList => {
|
||||
"return (raw as List<dynamic>).cast<String>();".to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
libs/flutter_rust_bridge_codegen/src/generator/dart/ty_enum.rs
Normal file
207
libs/flutter_rust_bridge_codegen/src/generator/dart/ty_enum.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use crate::generator::dart::dart_comments;
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypeEnumRefGenerator, IrTypeEnumRef);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypeEnumRefGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
if !self.ir.is_struct {
|
||||
Some("return raw.index;".to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn api_fill_to_wire_body(&self) -> Option<String> {
|
||||
if self.ir.is_struct {
|
||||
Some(
|
||||
self.ir
|
||||
.get(self.context.ir_file)
|
||||
.variants()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, variant)| {
|
||||
if let IrVariantKind::Value = &variant.kind {
|
||||
format!(
|
||||
"if (apiObj is {}) {{ wireObj.tag = {}; return; }}",
|
||||
variant.name, idx
|
||||
)
|
||||
} else {
|
||||
let r = format!("wireObj.kind.ref.{}.ref", variant.name);
|
||||
let body: Vec<_> = match &variant.kind {
|
||||
IrVariantKind::Struct(st) => st
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{}.{} = _api2wire_{}(apiObj.{});",
|
||||
r,
|
||||
field.name.rust_style(),
|
||||
field.ty.safe_ident(),
|
||||
field.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
format!(
|
||||
"if (apiObj is {0}) {{
|
||||
wireObj.tag = {1};
|
||||
wireObj.kind = inner.inflate_{2}_{0}();
|
||||
{3}
|
||||
}}",
|
||||
variant.name,
|
||||
idx,
|
||||
self.ir.name,
|
||||
body.join("\n")
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
if self.ir.is_struct {
|
||||
let enu = self.ir.get(self.context.ir_file);
|
||||
let variants = enu
|
||||
.variants()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, variant)| {
|
||||
let args = match &variant.kind {
|
||||
IrVariantKind::Value => "".to_owned(),
|
||||
IrVariantKind::Struct(st) => st
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, field)| {
|
||||
let val = format!(
|
||||
"_wire2api_{}(raw[{}]),",
|
||||
field.ty.safe_ident(),
|
||||
idx + 1
|
||||
);
|
||||
if st.is_fields_named {
|
||||
format!("{}: {}", field.name.dart_style(), val)
|
||||
} else {
|
||||
val
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
};
|
||||
format!("case {}: return {}({});", idx, variant.name, args)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!(
|
||||
"switch (raw[0]) {{
|
||||
{}
|
||||
default: throw Exception(\"unreachable\");
|
||||
}}",
|
||||
variants.join("\n"),
|
||||
)
|
||||
} else {
|
||||
format!("return {}.values[raw];", self.ir.name)
|
||||
}
|
||||
}
|
||||
|
||||
fn structs(&self) -> String {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
|
||||
let comments = dart_comments(&src.comments);
|
||||
if src.is_struct() {
|
||||
let variants = src
|
||||
.variants()
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
let args = match &variant.kind {
|
||||
IrVariantKind::Value => "".to_owned(),
|
||||
IrVariantKind::Struct(IrStruct {
|
||||
is_fields_named: false,
|
||||
fields,
|
||||
..
|
||||
}) => {
|
||||
let types = fields.iter().map(|field| &field.ty).collect::<Vec<_>>();
|
||||
let split = optional_boundary_index(&types);
|
||||
let types = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{}{} {},",
|
||||
dart_comments(&field.comments),
|
||||
field.ty.dart_api_type(),
|
||||
field.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(idx) = split {
|
||||
let before = &types[..idx];
|
||||
let after = &types[idx..];
|
||||
format!("{}[{}]", before.join(""), after.join(""))
|
||||
} else {
|
||||
types.join("")
|
||||
}
|
||||
}
|
||||
IrVariantKind::Struct(st) => {
|
||||
let fields = st
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{}{}{} {},",
|
||||
dart_comments(&field.comments),
|
||||
field.ty.dart_required_modifier(),
|
||||
field.ty.dart_api_type(),
|
||||
field.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!("{{ {} }}", fields.join(""))
|
||||
}
|
||||
};
|
||||
format!(
|
||||
"{}const factory {}.{}({}) = {};",
|
||||
dart_comments(&variant.comments),
|
||||
self.ir.name,
|
||||
variant.name.dart_style(),
|
||||
args,
|
||||
variant.name.rust_style(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!(
|
||||
"@freezed
|
||||
class {0} with _${0} {{
|
||||
{1}
|
||||
}}",
|
||||
self.ir.name,
|
||||
variants.join("\n")
|
||||
)
|
||||
} else {
|
||||
let variants = src
|
||||
.variants()
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
format!(
|
||||
"{}{},",
|
||||
dart_comments(&variant.comments),
|
||||
variant.name.rust_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
format!(
|
||||
"{}enum {} {{
|
||||
{}
|
||||
}}",
|
||||
comments, self.ir.name, variants
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypeGeneralListGenerator, IrTypeGeneralList);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypeGeneralListGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
// NOTE the memory strategy is same as PrimitiveList, see comments there.
|
||||
Some(format!(
|
||||
"final ans = inner.new_{}(raw.length);
|
||||
for (var i = 0; i < raw.length; ++i) {{
|
||||
_api_fill_to_wire_{}(raw[i], ans.ref.ptr[i]);
|
||||
}}
|
||||
return ans;",
|
||||
self.ir.safe_ident(),
|
||||
self.ir.inner.safe_ident()
|
||||
))
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
format!(
|
||||
"return (raw as List<dynamic>).map(_wire2api_{}).toList();",
|
||||
self.ir.inner.safe_ident()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypeOptionalGenerator, IrTypeOptional);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypeOptionalGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
Some(format!(
|
||||
"return raw == null ? ffi.nullptr : _api2wire_{}(raw);",
|
||||
self.ir.inner.safe_ident()
|
||||
))
|
||||
}
|
||||
|
||||
fn api_fill_to_wire_body(&self) -> Option<String> {
|
||||
if !self.ir.needs_initialization() || self.ir.is_list() {
|
||||
return None;
|
||||
}
|
||||
Some(format!(
|
||||
"if (apiObj != null) _api_fill_to_wire_{}(apiObj, wireObj);",
|
||||
self.ir.inner.safe_ident()
|
||||
))
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
format!(
|
||||
"return raw == null ? null : _wire2api_{}(raw);",
|
||||
self.ir.inner.safe_ident()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use crate::generator::dart::gen_wire2api_simple_type_cast;
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypePrimitiveGenerator, IrTypePrimitive);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypePrimitiveGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
Some(match self.ir {
|
||||
IrTypePrimitive::Bool => "return raw ? 1 : 0;".to_owned(),
|
||||
_ => "return raw;".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
match self.ir {
|
||||
IrTypePrimitive::Unit => "return;".to_owned(),
|
||||
_ => gen_wire2api_simple_type_cast(&self.ir.dart_api_type()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::generator::dart::gen_wire2api_simple_type_cast;
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypePrimitiveListGenerator, IrTypePrimitiveList);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypePrimitiveListGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
// NOTE Dart code *only* allocates memory. It never *release* memory by itself.
|
||||
// Instead, Rust receives that pointer and now it is in control of Rust.
|
||||
// Therefore, *never* continue to use this pointer after you have passed the pointer
|
||||
// to Rust.
|
||||
// NOTE WARN: Never use the [calloc] provided by Dart FFI to allocate any memory.
|
||||
// Instead, ask Rust to allocate some memory and return raw pointers. Otherwise,
|
||||
// memory will be allocated in one dylib (e.g. libflutter.so), and then be released
|
||||
// by another dylib (e.g. my_rust_code.so), especially in Android platform. It can be
|
||||
// undefined behavior.
|
||||
Some(format!(
|
||||
"final ans = inner.new_{}(raw.length);
|
||||
ans.ref.ptr.asTypedList(raw.length).setAll(0, raw);
|
||||
return ans;",
|
||||
self.ir.safe_ident(),
|
||||
))
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
gen_wire2api_simple_type_cast(&self.ir.dart_api_type())
|
||||
}
|
||||
}
|
||||
135
libs/flutter_rust_bridge_codegen/src/generator/dart/ty_struct.rs
Normal file
135
libs/flutter_rust_bridge_codegen/src/generator/dart/ty_struct.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use crate::generator::dart::ty::*;
|
||||
use crate::generator::dart::{dart_comments, dart_metadata};
|
||||
use crate::ir::*;
|
||||
use crate::type_dart_generator_struct;
|
||||
|
||||
type_dart_generator_struct!(TypeStructRefGenerator, IrTypeStructRef);
|
||||
|
||||
impl TypeDartGeneratorTrait for TypeStructRefGenerator<'_> {
|
||||
fn api2wire_body(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn api_fill_to_wire_body(&self) -> Option<String> {
|
||||
let s = self.ir.get(self.context.ir_file);
|
||||
Some(
|
||||
s.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"wireObj.{} = _api2wire_{}(apiObj.{});",
|
||||
field.name.rust_style(),
|
||||
field.ty.safe_ident(),
|
||||
field.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
fn wire2api_body(&self) -> String {
|
||||
let s = self.ir.get(self.context.ir_file);
|
||||
let inner = s
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, field)| {
|
||||
format!(
|
||||
"{}: _wire2api_{}(arr[{}]),",
|
||||
field.name.dart_style(),
|
||||
field.ty.safe_ident(),
|
||||
idx
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
format!(
|
||||
"final arr = raw as List<dynamic>;
|
||||
if (arr.length != {}) throw Exception('unexpected arr length: expect {} but see ${{arr.length}}');
|
||||
return {}({});",
|
||||
s.fields.len(),
|
||||
s.fields.len(),
|
||||
s.name, inner,
|
||||
)
|
||||
}
|
||||
|
||||
fn structs(&self) -> String {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
let comments = dart_comments(&src.comments);
|
||||
let metadata = dart_metadata(&src.dart_metadata);
|
||||
|
||||
if src.using_freezed() {
|
||||
let constructor_params = src
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| {
|
||||
format!(
|
||||
"{} {} {},",
|
||||
f.ty.dart_required_modifier(),
|
||||
f.ty.dart_api_type(),
|
||||
f.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
format!(
|
||||
"{}{}class {} with _${} {{
|
||||
const factory {}({{{}}}) = _{};
|
||||
}}",
|
||||
comments,
|
||||
metadata,
|
||||
self.ir.name,
|
||||
self.ir.name,
|
||||
self.ir.name,
|
||||
constructor_params,
|
||||
self.ir.name
|
||||
)
|
||||
} else {
|
||||
let field_declarations = src
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let comments = dart_comments(&f.comments);
|
||||
format!(
|
||||
"{}{} {} {};",
|
||||
comments,
|
||||
if f.is_final { "final" } else { "" },
|
||||
f.ty.dart_api_type(),
|
||||
f.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
let constructor_params = src
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| {
|
||||
format!(
|
||||
"{}this.{},",
|
||||
f.ty.dart_required_modifier(),
|
||||
f.name.dart_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
format!(
|
||||
"{}{}class {} {{
|
||||
{}
|
||||
|
||||
{}({{{}}});
|
||||
}}",
|
||||
comments,
|
||||
metadata,
|
||||
self.ir.name,
|
||||
field_declarations,
|
||||
self.ir.name,
|
||||
constructor_params
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
3
libs/flutter_rust_bridge_codegen/src/generator/mod.rs
Normal file
3
libs/flutter_rust_bridge_codegen/src/generator/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod c;
|
||||
pub mod dart;
|
||||
pub mod rust;
|
||||
481
libs/flutter_rust_bridge_codegen/src/generator/rust/mod.rs
Normal file
481
libs/flutter_rust_bridge_codegen/src/generator/rust/mod.rs
Normal file
@@ -0,0 +1,481 @@
|
||||
mod ty;
|
||||
mod ty_boxed;
|
||||
mod ty_delegate;
|
||||
mod ty_enum;
|
||||
mod ty_general_list;
|
||||
mod ty_optional;
|
||||
mod ty_primitive;
|
||||
mod ty_primitive_list;
|
||||
mod ty_struct;
|
||||
|
||||
pub use ty::*;
|
||||
pub use ty_boxed::*;
|
||||
pub use ty_delegate::*;
|
||||
pub use ty_enum::*;
|
||||
pub use ty_general_list::*;
|
||||
pub use ty_optional::*;
|
||||
pub use ty_primitive::*;
|
||||
pub use ty_primitive_list::*;
|
||||
pub use ty_struct::*;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::ir::IrType::*;
|
||||
use crate::ir::*;
|
||||
use crate::others::*;
|
||||
|
||||
pub const HANDLER_NAME: &str = "FLUTTER_RUST_BRIDGE_HANDLER";
|
||||
|
||||
pub struct Output {
|
||||
pub code: String,
|
||||
pub extern_func_names: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn generate(ir_file: &IrFile, rust_wire_mod: &str) -> Output {
|
||||
let mut generator = Generator::new();
|
||||
let code = generator.generate(ir_file, rust_wire_mod);
|
||||
|
||||
Output {
|
||||
code,
|
||||
extern_func_names: generator.extern_func_collector.names,
|
||||
}
|
||||
}
|
||||
|
||||
struct Generator {
|
||||
extern_func_collector: ExternFuncCollector,
|
||||
}
|
||||
|
||||
impl Generator {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
extern_func_collector: ExternFuncCollector::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate(&mut self, ir_file: &IrFile, rust_wire_mod: &str) -> String {
|
||||
let mut lines: Vec<String> = vec![];
|
||||
|
||||
let distinct_input_types = ir_file.distinct_types(true, false);
|
||||
let distinct_output_types = ir_file.distinct_types(false, true);
|
||||
|
||||
lines.push(r#"#![allow(non_camel_case_types, unused, clippy::redundant_closure, clippy::useless_conversion, clippy::unit_arg, clippy::double_parens, non_snake_case)]"#.to_string());
|
||||
lines.push(CODE_HEADER.to_string());
|
||||
|
||||
lines.push(String::new());
|
||||
lines.push(format!("use crate::{}::*;", rust_wire_mod));
|
||||
lines.push("use flutter_rust_bridge::*;".to_string());
|
||||
lines.push(String::new());
|
||||
|
||||
lines.push(self.section_header_comment("imports"));
|
||||
lines.extend(self.generate_imports(
|
||||
ir_file,
|
||||
rust_wire_mod,
|
||||
&distinct_input_types,
|
||||
&distinct_output_types,
|
||||
));
|
||||
lines.push(String::new());
|
||||
|
||||
lines.push(self.section_header_comment("wire functions"));
|
||||
lines.extend(
|
||||
ir_file
|
||||
.funcs
|
||||
.iter()
|
||||
.map(|f| self.generate_wire_func(f, ir_file)),
|
||||
);
|
||||
|
||||
lines.push(self.section_header_comment("wire structs"));
|
||||
lines.extend(
|
||||
distinct_input_types
|
||||
.iter()
|
||||
.map(|ty| self.generate_wire_struct(ty, ir_file)),
|
||||
);
|
||||
lines.extend(
|
||||
distinct_input_types
|
||||
.iter()
|
||||
.map(|ty| TypeRustGenerator::new(ty.clone(), ir_file).structs()),
|
||||
);
|
||||
|
||||
lines.push(self.section_header_comment("wrapper structs"));
|
||||
lines.extend(
|
||||
distinct_output_types
|
||||
.iter()
|
||||
.filter_map(|ty| self.generate_wrapper_struct(ty, ir_file)),
|
||||
);
|
||||
lines.push(self.section_header_comment("static checks"));
|
||||
let static_checks: Vec<_> = distinct_output_types
|
||||
.iter()
|
||||
.filter_map(|ty| self.generate_static_checks(ty, ir_file))
|
||||
.collect();
|
||||
if !static_checks.is_empty() {
|
||||
lines.push("const _: fn() = || {".to_owned());
|
||||
lines.extend(static_checks);
|
||||
lines.push("};".to_owned());
|
||||
}
|
||||
|
||||
lines.push(self.section_header_comment("allocate functions"));
|
||||
lines.extend(
|
||||
distinct_input_types
|
||||
.iter()
|
||||
.map(|f| self.generate_allocate_funcs(f, ir_file)),
|
||||
);
|
||||
|
||||
lines.push(self.section_header_comment("impl Wire2Api"));
|
||||
lines.push(self.generate_wire2api_misc().to_string());
|
||||
lines.extend(
|
||||
distinct_input_types
|
||||
.iter()
|
||||
.map(|ty| self.generate_wire2api_func(ty, ir_file)),
|
||||
);
|
||||
|
||||
lines.push(self.section_header_comment("impl NewWithNullPtr"));
|
||||
lines.push(self.generate_new_with_nullptr_misc().to_string());
|
||||
lines.extend(
|
||||
distinct_input_types
|
||||
.iter()
|
||||
.map(|ty| self.generate_new_with_nullptr_func(ty, ir_file)),
|
||||
);
|
||||
|
||||
lines.push(self.section_header_comment("impl IntoDart"));
|
||||
lines.extend(
|
||||
distinct_output_types
|
||||
.iter()
|
||||
.map(|ty| self.generate_impl_intodart(ty, ir_file)),
|
||||
);
|
||||
|
||||
lines.push(self.section_header_comment("executor"));
|
||||
lines.push(self.generate_executor(ir_file));
|
||||
|
||||
lines.push(self.section_header_comment("sync execution mode utility"));
|
||||
lines.push(self.generate_sync_execution_mode_utility());
|
||||
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn section_header_comment(&self, section_name: &str) -> String {
|
||||
format!("// Section: {}\n", section_name)
|
||||
}
|
||||
|
||||
fn generate_imports(
|
||||
&self,
|
||||
ir_file: &IrFile,
|
||||
rust_wire_mod: &str,
|
||||
distinct_input_types: &[IrType],
|
||||
distinct_output_types: &[IrType],
|
||||
) -> impl Iterator<Item = String> {
|
||||
let input_type_imports = distinct_input_types
|
||||
.iter()
|
||||
.map(|api_type| generate_import(api_type, ir_file));
|
||||
let output_type_imports = distinct_output_types
|
||||
.iter()
|
||||
.map(|api_type| generate_import(api_type, ir_file));
|
||||
|
||||
input_type_imports
|
||||
.chain(output_type_imports)
|
||||
// Filter out `None` and unwrap
|
||||
.flatten()
|
||||
// Don't include imports from the API file
|
||||
.filter(|import| !import.starts_with(&format!("use crate::{}::", rust_wire_mod)))
|
||||
// de-duplicate
|
||||
.collect::<HashSet<String>>()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
fn generate_executor(&mut self, ir_file: &IrFile) -> String {
|
||||
if ir_file.has_executor {
|
||||
"/* nothing since executor detected */".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"support::lazy_static! {{
|
||||
pub static ref {}: support::DefaultHandler = Default::default();
|
||||
}}
|
||||
",
|
||||
HANDLER_NAME
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_sync_execution_mode_utility(&mut self) -> String {
|
||||
self.extern_func_collector.generate(
|
||||
"free_WireSyncReturnStruct",
|
||||
&["val: support::WireSyncReturnStruct"],
|
||||
None,
|
||||
"unsafe { let _ = support::vec_from_leak_ptr(val.ptr, val.len); }",
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_wire_func(&mut self, func: &IrFunc, ir_file: &IrFile) -> String {
|
||||
let params = [
|
||||
if func.mode.has_port_argument() {
|
||||
vec!["port_: i64".to_string()]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
func.inputs
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{}: {}{}",
|
||||
field.name.rust_style(),
|
||||
field.ty.rust_wire_modifier(),
|
||||
field.ty.rust_wire_type()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let inner_func_params = [
|
||||
match func.mode {
|
||||
IrFuncMode::Normal | IrFuncMode::Sync => vec![],
|
||||
IrFuncMode::Stream => vec!["task_callback.stream_sink()".to_string()],
|
||||
},
|
||||
func.inputs
|
||||
.iter()
|
||||
.map(|field| format!("api_{}", field.name.rust_style()))
|
||||
.collect::<Vec<_>>(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let wrap_info_obj = format!(
|
||||
"WrapInfo{{ debug_name: \"{}\", port: {}, mode: FfiCallMode::{} }}",
|
||||
func.name,
|
||||
if func.mode.has_port_argument() {
|
||||
"Some(port_)"
|
||||
} else {
|
||||
"None"
|
||||
},
|
||||
func.mode.ffi_call_mode(),
|
||||
);
|
||||
|
||||
let code_wire2api = func
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"let api_{} = {}.wire2api();",
|
||||
field.name.rust_style(),
|
||||
field.name.rust_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
let code_call_inner_func = TypeRustGenerator::new(func.output.clone(), ir_file)
|
||||
.wrap_obj(format!("{}({})", func.name, inner_func_params.join(", ")));
|
||||
let code_call_inner_func_result = if func.fallible {
|
||||
code_call_inner_func
|
||||
} else {
|
||||
format!("Ok({})", code_call_inner_func)
|
||||
};
|
||||
|
||||
let (handler_func_name, return_type, code_closure) = match func.mode {
|
||||
IrFuncMode::Sync => (
|
||||
"wrap_sync",
|
||||
Some("support::WireSyncReturnStruct"),
|
||||
format!(
|
||||
"{}
|
||||
{}",
|
||||
code_wire2api, code_call_inner_func_result,
|
||||
),
|
||||
),
|
||||
IrFuncMode::Normal | IrFuncMode::Stream => (
|
||||
"wrap",
|
||||
None,
|
||||
format!(
|
||||
"{}
|
||||
move |task_callback| {}
|
||||
",
|
||||
code_wire2api, code_call_inner_func_result,
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
self.extern_func_collector.generate(
|
||||
&func.wire_func_name(),
|
||||
¶ms
|
||||
.iter()
|
||||
.map(std::ops::Deref::deref)
|
||||
.collect::<Vec<_>>(),
|
||||
return_type,
|
||||
&format!(
|
||||
"
|
||||
{}.{}({}, move || {{
|
||||
{}
|
||||
}})
|
||||
",
|
||||
HANDLER_NAME, handler_func_name, wrap_info_obj, code_closure,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_wire_struct(&mut self, ty: &IrType, ir_file: &IrFile) -> String {
|
||||
// println!("generate_wire_struct: {:?}", ty);
|
||||
if let Some(fields) = TypeRustGenerator::new(ty.clone(), ir_file).wire_struct_fields() {
|
||||
format!(
|
||||
r###"
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct {} {{
|
||||
{}
|
||||
}}
|
||||
"###,
|
||||
ty.rust_wire_type(),
|
||||
fields.join(",\n"),
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_allocate_funcs(&mut self, ty: &IrType, ir_file: &IrFile) -> String {
|
||||
// println!("generate_allocate_funcs: {:?}", ty);
|
||||
TypeRustGenerator::new(ty.clone(), ir_file).allocate_funcs(&mut self.extern_func_collector)
|
||||
}
|
||||
|
||||
fn generate_wire2api_misc(&self) -> &'static str {
|
||||
r"pub trait Wire2Api<T> {
|
||||
fn wire2api(self) -> T;
|
||||
}
|
||||
|
||||
impl<T, S> Wire2Api<Option<T>> for *mut S
|
||||
where
|
||||
*mut S: Wire2Api<T>
|
||||
{
|
||||
fn wire2api(self) -> Option<T> {
|
||||
if self.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(self.wire2api())
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
}
|
||||
|
||||
fn generate_wire2api_func(&mut self, ty: &IrType, ir_file: &IrFile) -> String {
|
||||
// println!("generate_wire2api_func: {:?}", ty);
|
||||
if let Some(body) = TypeRustGenerator::new(ty.clone(), ir_file).wire2api_body() {
|
||||
format!(
|
||||
"impl Wire2Api<{}> for {} {{
|
||||
fn wire2api(self) -> {} {{
|
||||
{}
|
||||
}}
|
||||
}}
|
||||
",
|
||||
ty.rust_api_type(),
|
||||
ty.rust_wire_modifier() + &ty.rust_wire_type(),
|
||||
ty.rust_api_type(),
|
||||
body,
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_static_checks(&mut self, ty: &IrType, ir_file: &IrFile) -> Option<String> {
|
||||
TypeRustGenerator::new(ty.clone(), ir_file).static_checks()
|
||||
}
|
||||
|
||||
fn generate_wrapper_struct(&mut self, ty: &IrType, ir_file: &IrFile) -> Option<String> {
|
||||
match ty {
|
||||
IrType::StructRef(_) | IrType::EnumRef(_) => {
|
||||
TypeRustGenerator::new(ty.clone(), ir_file)
|
||||
.wrapper_struct()
|
||||
.map(|wrapper| {
|
||||
format!(
|
||||
r###"
|
||||
#[derive(Clone)]
|
||||
struct {}({});
|
||||
"###,
|
||||
wrapper,
|
||||
ty.rust_api_type(),
|
||||
)
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_new_with_nullptr_misc(&self) -> &'static str {
|
||||
"pub trait NewWithNullPtr {
|
||||
fn new_with_null_ptr() -> Self;
|
||||
}
|
||||
|
||||
impl<T> NewWithNullPtr for *mut T {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
"
|
||||
}
|
||||
|
||||
fn generate_new_with_nullptr_func(&mut self, ty: &IrType, ir_file: &IrFile) -> String {
|
||||
TypeRustGenerator::new(ty.clone(), ir_file)
|
||||
.new_with_nullptr(&mut self.extern_func_collector)
|
||||
}
|
||||
|
||||
fn generate_impl_intodart(&mut self, ty: &IrType, ir_file: &IrFile) -> String {
|
||||
// println!("generate_impl_intodart: {:?}", ty);
|
||||
TypeRustGenerator::new(ty.clone(), ir_file).impl_intodart()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_import(api_type: &IrType, ir_file: &IrFile) -> Option<String> {
|
||||
TypeRustGenerator::new(api_type.clone(), ir_file).imports()
|
||||
}
|
||||
|
||||
pub fn generate_list_allocate_func(
|
||||
collector: &mut ExternFuncCollector,
|
||||
safe_ident: &str,
|
||||
list: &impl IrTypeTrait,
|
||||
inner: &IrType,
|
||||
) -> String {
|
||||
collector.generate(
|
||||
&format!("new_{}", safe_ident),
|
||||
&["len: i32"],
|
||||
Some(&[
|
||||
list.rust_wire_modifier().as_str(),
|
||||
list.rust_wire_type().as_str()
|
||||
].concat()),
|
||||
&format!(
|
||||
"let wrap = {} {{ ptr: support::new_leak_vec_ptr(<{}{}>::new_with_null_ptr(), len), len }};
|
||||
support::new_leak_box_ptr(wrap)",
|
||||
list.rust_wire_type(),
|
||||
inner.rust_ptr_modifier(),
|
||||
inner.rust_wire_type()
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub struct ExternFuncCollector {
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
impl ExternFuncCollector {
|
||||
fn new() -> Self {
|
||||
ExternFuncCollector { names: vec![] }
|
||||
}
|
||||
|
||||
fn generate(
|
||||
&mut self,
|
||||
func_name: &str,
|
||||
params: &[&str],
|
||||
return_type: Option<&str>,
|
||||
body: &str,
|
||||
) -> String {
|
||||
self.names.push(func_name.to_string());
|
||||
|
||||
format!(
|
||||
r#"
|
||||
#[no_mangle]
|
||||
pub extern "C" fn {}({}) {} {{
|
||||
{}
|
||||
}}
|
||||
"#,
|
||||
func_name,
|
||||
params.join(", "),
|
||||
return_type.map_or("".to_string(), |r| format!("-> {}", r)),
|
||||
body,
|
||||
)
|
||||
}
|
||||
}
|
||||
96
libs/flutter_rust_bridge_codegen/src/generator/rust/ty.rs
Normal file
96
libs/flutter_rust_bridge_codegen/src/generator/rust/ty.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use crate::generator::rust::*;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait TypeRustGeneratorTrait {
|
||||
fn wire2api_body(&self) -> Option<String>;
|
||||
|
||||
fn wire_struct_fields(&self) -> Option<Vec<String>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn static_checks(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn wrapper_struct(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn self_access(&self, obj: String) -> String {
|
||||
obj
|
||||
}
|
||||
|
||||
fn wrap_obj(&self, obj: String) -> String {
|
||||
obj
|
||||
}
|
||||
|
||||
fn convert_to_dart(&self, obj: String) -> String {
|
||||
format!("{}.into_dart()", obj)
|
||||
}
|
||||
|
||||
fn structs(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn allocate_funcs(&self, _collector: &mut ExternFuncCollector) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn impl_intodart(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn new_with_nullptr(&self, _collector: &mut ExternFuncCollector) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn imports(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeGeneratorContext<'a> {
|
||||
pub ir_file: &'a IrFile,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! type_rust_generator_struct {
|
||||
($cls:ident, $ir_cls:ty) => {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct $cls<'a> {
|
||||
pub ir: $ir_cls,
|
||||
pub context: TypeGeneratorContext<'a>,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[enum_dispatch(TypeRustGeneratorTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeRustGenerator<'a> {
|
||||
Primitive(TypePrimitiveGenerator<'a>),
|
||||
Delegate(TypeDelegateGenerator<'a>),
|
||||
PrimitiveList(TypePrimitiveListGenerator<'a>),
|
||||
Optional(TypeOptionalGenerator<'a>),
|
||||
GeneralList(TypeGeneralListGenerator<'a>),
|
||||
StructRef(TypeStructRefGenerator<'a>),
|
||||
Boxed(TypeBoxedGenerator<'a>),
|
||||
EnumRef(TypeEnumRefGenerator<'a>),
|
||||
}
|
||||
|
||||
impl<'a> TypeRustGenerator<'a> {
|
||||
pub fn new(ty: IrType, ir_file: &'a IrFile) -> Self {
|
||||
let context = TypeGeneratorContext { ir_file };
|
||||
match ty {
|
||||
Primitive(ir) => TypePrimitiveGenerator { ir, context }.into(),
|
||||
Delegate(ir) => TypeDelegateGenerator { ir, context }.into(),
|
||||
PrimitiveList(ir) => TypePrimitiveListGenerator { ir, context }.into(),
|
||||
Optional(ir) => TypeOptionalGenerator { ir, context }.into(),
|
||||
GeneralList(ir) => TypeGeneralListGenerator { ir, context }.into(),
|
||||
StructRef(ir) => TypeStructRefGenerator { ir, context }.into(),
|
||||
Boxed(ir) => TypeBoxedGenerator { ir, context }.into(),
|
||||
EnumRef(ir) => TypeEnumRefGenerator { ir, context }.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::generator::rust::{generate_import, ExternFuncCollector};
|
||||
use crate::ir::IrType::Primitive;
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypeBoxedGenerator, IrTypeBoxed);
|
||||
|
||||
impl TypeRustGeneratorTrait for TypeBoxedGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
let IrTypeBoxed {
|
||||
inner: box_inner,
|
||||
exist_in_real_api,
|
||||
} = &self.ir;
|
||||
Some(match (box_inner.as_ref(), exist_in_real_api) {
|
||||
(IrType::Primitive(_), false) => "unsafe { *support::box_from_leak_ptr(self) }".into(),
|
||||
(IrType::Primitive(_), true) => "unsafe { support::box_from_leak_ptr(self) }".into(),
|
||||
_ => {
|
||||
"let wrap = unsafe { support::box_from_leak_ptr(self) }; (*wrap).wire2api().into()"
|
||||
.into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn wrapper_struct(&self) -> Option<String> {
|
||||
let src = TypeRustGenerator::new(*self.ir.inner.clone(), self.context.ir_file);
|
||||
src.wrapper_struct()
|
||||
}
|
||||
|
||||
fn self_access(&self, obj: String) -> String {
|
||||
format!("(*{})", obj)
|
||||
}
|
||||
|
||||
fn wrap_obj(&self, obj: String) -> String {
|
||||
let src = TypeRustGenerator::new(*self.ir.inner.clone(), self.context.ir_file);
|
||||
src.wrap_obj(self.self_access(obj))
|
||||
}
|
||||
|
||||
fn allocate_funcs(&self, collector: &mut ExternFuncCollector) -> String {
|
||||
match &*self.ir.inner {
|
||||
Primitive(prim) => collector.generate(
|
||||
&format!("new_{}", self.ir.safe_ident()),
|
||||
&[&format!("value: {}", prim.rust_wire_type())],
|
||||
Some(&format!("*mut {}", prim.rust_wire_type())),
|
||||
"support::new_leak_box_ptr(value)",
|
||||
),
|
||||
inner => collector.generate(
|
||||
&format!("new_{}", self.ir.safe_ident()),
|
||||
&[],
|
||||
Some(&[self.ir.rust_wire_modifier(), self.ir.rust_wire_type()].concat()),
|
||||
&format!(
|
||||
"support::new_leak_box_ptr({}::new_with_null_ptr())",
|
||||
inner.rust_wire_type()
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn imports(&self) -> Option<String> {
|
||||
generate_import(&self.ir.inner, self.context.ir_file)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::generator::rust::{
|
||||
generate_list_allocate_func, ExternFuncCollector, TypeGeneralListGenerator,
|
||||
};
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypeDelegateGenerator, IrTypeDelegate);
|
||||
|
||||
impl TypeRustGeneratorTrait for TypeDelegateGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
Some(match &self.ir {
|
||||
IrTypeDelegate::String => "let vec: Vec<u8> = self.wire2api();
|
||||
String::from_utf8_lossy(&vec).into_owned()"
|
||||
.into(),
|
||||
IrTypeDelegate::SyncReturnVecU8 => "/*unsupported*/".into(),
|
||||
IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
|
||||
"ZeroCopyBuffer(self.wire2api())".into()
|
||||
}
|
||||
IrTypeDelegate::StringList => TypeGeneralListGenerator::WIRE2API_BODY.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn wire_struct_fields(&self) -> Option<Vec<String>> {
|
||||
match &self.ir {
|
||||
ty @ IrTypeDelegate::StringList => Some(vec![
|
||||
format!("ptr: *mut *mut {}", ty.get_delegate().rust_wire_type()),
|
||||
"len: i32".to_owned(),
|
||||
]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate_funcs(&self, collector: &mut ExternFuncCollector) -> String {
|
||||
match &self.ir {
|
||||
list @ IrTypeDelegate::StringList => generate_list_allocate_func(
|
||||
collector,
|
||||
&self.ir.safe_ident(),
|
||||
list,
|
||||
&list.get_delegate(),
|
||||
),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
343
libs/flutter_rust_bridge_codegen/src/generator/rust/ty_enum.rs
Normal file
343
libs/flutter_rust_bridge_codegen/src/generator/rust/ty_enum.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::generator::rust::ExternFuncCollector;
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypeEnumRefGenerator, IrTypeEnumRef);
|
||||
|
||||
impl TypeRustGeneratorTrait for TypeEnumRefGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
let enu = self.ir.get(self.context.ir_file);
|
||||
Some(if self.ir.is_struct {
|
||||
let variants = enu
|
||||
.variants()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, variant)| match &variant.kind {
|
||||
IrVariantKind::Value => {
|
||||
format!("{} => {}::{},", idx, enu.name, variant.name)
|
||||
}
|
||||
IrVariantKind::Struct(st) => {
|
||||
let fields: Vec<_> = st
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
if st.is_fields_named {
|
||||
format!("{0}: ans.{0}.wire2api()", field.name.rust_style())
|
||||
} else {
|
||||
format!("ans.{}.wire2api()", field.name.rust_style())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let (left, right) = st.brackets_pair();
|
||||
format!(
|
||||
"{} => unsafe {{
|
||||
let ans = support::box_from_leak_ptr(self.kind);
|
||||
let ans = support::box_from_leak_ptr(ans.{2});
|
||||
{}::{2}{3}{4}{5}
|
||||
}}",
|
||||
idx,
|
||||
enu.name,
|
||||
variant.name,
|
||||
left,
|
||||
fields.join(","),
|
||||
right
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!(
|
||||
"match self.tag {{
|
||||
{}
|
||||
_ => unreachable!(),
|
||||
}}",
|
||||
variants.join("\n"),
|
||||
)
|
||||
} else {
|
||||
let variants = enu
|
||||
.variants()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, variant)| format!("{} => {}::{},", idx, enu.name, variant.name))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
format!(
|
||||
"match self {{
|
||||
{}
|
||||
_ => unreachable!(\"Invalid variant for {}: {{}}\", self),
|
||||
}}",
|
||||
variants, enu.name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn structs(&self) -> String {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
if !src.is_struct() {
|
||||
return "".to_owned();
|
||||
}
|
||||
let variant_structs = src
|
||||
.variants()
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
let fields = match &variant.kind {
|
||||
IrVariantKind::Value => vec![],
|
||||
IrVariantKind::Struct(s) => s
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{}: {}{},",
|
||||
field.name.rust_style(),
|
||||
field.ty.rust_wire_modifier(),
|
||||
field.ty.rust_wire_type()
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
format!(
|
||||
"#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct {}_{} {{ {} }}",
|
||||
self.ir.name,
|
||||
variant.name,
|
||||
fields.join("\n")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let union_fields = src
|
||||
.variants()
|
||||
.iter()
|
||||
.map(|variant| format!("{0}: *mut {1}_{0},", variant.name, self.ir.name))
|
||||
.collect::<Vec<_>>();
|
||||
format!(
|
||||
"#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct {0} {{ tag: i32, kind: *mut {1}Kind }}
|
||||
|
||||
#[repr(C)]
|
||||
pub union {1}Kind {{
|
||||
{2}
|
||||
}}
|
||||
|
||||
{3}",
|
||||
self.ir.rust_wire_type(),
|
||||
self.ir.name,
|
||||
union_fields.join("\n"),
|
||||
variant_structs.join("\n\n")
|
||||
)
|
||||
}
|
||||
|
||||
fn static_checks(&self) -> Option<String> {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
src.wrapper_name.as_ref()?;
|
||||
|
||||
let branches: Vec<_> = src
|
||||
.variants()
|
||||
.iter()
|
||||
.map(|variant| match &variant.kind {
|
||||
IrVariantKind::Value => format!("{}::{} => {{}}", src.name, variant.name),
|
||||
IrVariantKind::Struct(s) => {
|
||||
let pattern = s
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| field.name.rust_style().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let pattern = if s.is_fields_named {
|
||||
format!("{}::{} {{ {} }}", src.name, variant.name, pattern.join(","))
|
||||
} else {
|
||||
format!("{}::{}({})", src.name, variant.name, pattern.join(","))
|
||||
};
|
||||
let checks = s
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"let _: {} = {};\n",
|
||||
field.ty.rust_api_type(),
|
||||
field.name.rust_style(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!("{} => {{ {} }}", pattern, checks.join(""))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(format!(
|
||||
"match None::<{}>.unwrap() {{ {} }}",
|
||||
src.name,
|
||||
branches.join(","),
|
||||
))
|
||||
}
|
||||
|
||||
fn wrapper_struct(&self) -> Option<String> {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
src.wrapper_name.as_ref().cloned()
|
||||
}
|
||||
|
||||
fn self_access(&self, obj: String) -> String {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
match &src.wrapper_name {
|
||||
Some(_) => format!("{}.0", obj),
|
||||
None => obj,
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_obj(&self, obj: String) -> String {
|
||||
match self.wrapper_struct() {
|
||||
Some(wrapper) => format!("{}({})", wrapper, obj),
|
||||
None => obj,
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_intodart(&self) -> String {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
|
||||
let (name, self_path): (&str, &str) = match &src.wrapper_name {
|
||||
Some(wrapper) => (wrapper, &src.name),
|
||||
None => (&src.name, "Self"),
|
||||
};
|
||||
let self_ref = self.self_access("self".to_owned());
|
||||
if self.ir.is_struct {
|
||||
let variants = src
|
||||
.variants()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, variant)| {
|
||||
let tag = format!("{}.into_dart()", idx);
|
||||
match &variant.kind {
|
||||
IrVariantKind::Value => {
|
||||
format!("{}::{} => vec![{}],", self_path, variant.name, tag)
|
||||
}
|
||||
IrVariantKind::Struct(s) => {
|
||||
let fields = Some(tag)
|
||||
.into_iter()
|
||||
.chain(s.fields.iter().map(|field| {
|
||||
let gen = TypeRustGenerator::new(
|
||||
field.ty.clone(),
|
||||
self.context.ir_file,
|
||||
);
|
||||
gen.convert_to_dart(field.name.rust_style().to_owned())
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
let pattern = s
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| field.name.rust_style().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let (left, right) = s.brackets_pair();
|
||||
format!(
|
||||
"{}::{}{}{}{} => vec![{}],",
|
||||
self_path,
|
||||
variant.name,
|
||||
left,
|
||||
pattern.join(","),
|
||||
right,
|
||||
fields.join(",")
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!(
|
||||
"impl support::IntoDart for {} {{
|
||||
fn into_dart(self) -> support::DartCObject {{
|
||||
match {} {{
|
||||
{}
|
||||
}}.into_dart()
|
||||
}}
|
||||
}}
|
||||
impl support::IntoDartExceptPrimitive for {0} {{}}
|
||||
",
|
||||
name,
|
||||
self_ref,
|
||||
variants.join("\n")
|
||||
)
|
||||
} else {
|
||||
let variants = src
|
||||
.variants()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, variant)| format!("{}::{} => {},", self_path, variant.name, idx))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
format!(
|
||||
"impl support::IntoDart for {} {{
|
||||
fn into_dart(self) -> support::DartCObject {{
|
||||
match {} {{
|
||||
{}
|
||||
}}.into_dart()
|
||||
}}
|
||||
}}
|
||||
",
|
||||
name, self_ref, variants
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_with_nullptr(&self, collector: &mut ExternFuncCollector) -> String {
|
||||
if !self.ir.is_struct {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
fn init_of(ty: &IrType) -> &str {
|
||||
if ty.rust_wire_is_pointer() {
|
||||
"core::ptr::null_mut()"
|
||||
} else {
|
||||
"Default::default()"
|
||||
}
|
||||
}
|
||||
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
|
||||
let inflators = src
|
||||
.variants()
|
||||
.iter()
|
||||
.filter_map(|variant| {
|
||||
let typ = format!("{}_{}", self.ir.name, variant.name);
|
||||
let body: Vec<_> = if let IrVariantKind::Struct(st) = &variant.kind {
|
||||
st.fields
|
||||
.iter()
|
||||
.map(|field| format!("{}: {}", field.name.rust_style(), init_of(&field.ty)))
|
||||
.collect()
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(collector.generate(
|
||||
&format!("inflate_{}", typ),
|
||||
&[],
|
||||
Some(&format!("*mut {}Kind", self.ir.name)),
|
||||
&format!(
|
||||
"support::new_leak_box_ptr({}Kind {{
|
||||
{}: support::new_leak_box_ptr({} {{
|
||||
{}
|
||||
}})
|
||||
}})",
|
||||
self.ir.name,
|
||||
variant.name.rust_style(),
|
||||
typ,
|
||||
body.join(",")
|
||||
),
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format!(
|
||||
"impl NewWithNullPtr for {} {{
|
||||
fn new_with_null_ptr() -> Self {{
|
||||
Self {{
|
||||
tag: -1,
|
||||
kind: core::ptr::null_mut(),
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
{}",
|
||||
self.ir.rust_wire_type(),
|
||||
inflators.join("\n\n")
|
||||
)
|
||||
}
|
||||
|
||||
fn imports(&self) -> Option<String> {
|
||||
let api_enum = self.ir.get(self.context.ir_file);
|
||||
Some(format!("use {};", api_enum.path.join("::")))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::generator::rust::{generate_import, generate_list_allocate_func, ExternFuncCollector};
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypeGeneralListGenerator, IrTypeGeneralList);
|
||||
|
||||
impl TypeGeneralListGenerator<'_> {
|
||||
pub const WIRE2API_BODY: &'static str = "
|
||||
let vec = unsafe {
|
||||
let wrap = support::box_from_leak_ptr(self);
|
||||
support::vec_from_leak_ptr(wrap.ptr, wrap.len)
|
||||
};
|
||||
vec.into_iter().map(Wire2Api::wire2api).collect()";
|
||||
}
|
||||
|
||||
impl TypeRustGeneratorTrait for TypeGeneralListGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
Some(TypeGeneralListGenerator::WIRE2API_BODY.to_string())
|
||||
}
|
||||
|
||||
fn wire_struct_fields(&self) -> Option<Vec<String>> {
|
||||
Some(vec![
|
||||
format!(
|
||||
"ptr: *mut {}{}",
|
||||
self.ir.inner.rust_ptr_modifier(),
|
||||
self.ir.inner.rust_wire_type()
|
||||
),
|
||||
"len: i32".to_string(),
|
||||
])
|
||||
}
|
||||
|
||||
fn wrap_obj(&self, obj: String) -> String {
|
||||
let inner = TypeRustGenerator::new(*self.ir.inner.clone(), self.context.ir_file);
|
||||
inner
|
||||
.wrapper_struct()
|
||||
.map(|wrapper| {
|
||||
format!(
|
||||
"{}.into_iter().map(|v| {}({})).collect::<Vec<_>>()",
|
||||
obj,
|
||||
wrapper,
|
||||
inner.self_access("v".to_owned())
|
||||
)
|
||||
})
|
||||
.unwrap_or(obj)
|
||||
}
|
||||
|
||||
fn allocate_funcs(&self, collector: &mut ExternFuncCollector) -> String {
|
||||
generate_list_allocate_func(collector, &self.ir.safe_ident(), &self.ir, &self.ir.inner)
|
||||
}
|
||||
|
||||
fn imports(&self) -> Option<String> {
|
||||
generate_import(&self.ir.inner, self.context.ir_file)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::generator::rust::generate_import;
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypeOptionalGenerator, IrTypeOptional);
|
||||
|
||||
impl TypeRustGeneratorTrait for TypeOptionalGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn convert_to_dart(&self, obj: String) -> String {
|
||||
let inner = TypeRustGenerator::new(*self.ir.inner.clone(), self.context.ir_file);
|
||||
let obj = match inner.wrapper_struct() {
|
||||
Some(wrapper) => format!(
|
||||
"{}.map(|v| {}({}))",
|
||||
obj,
|
||||
wrapper,
|
||||
inner.self_access("v".to_owned())
|
||||
),
|
||||
None => obj,
|
||||
};
|
||||
format!("{}.into_dart()", obj)
|
||||
}
|
||||
|
||||
fn imports(&self) -> Option<String> {
|
||||
generate_import(&self.ir.inner, self.context.ir_file)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypePrimitiveGenerator, IrTypePrimitive);
|
||||
|
||||
impl TypeRustGeneratorTrait for TypePrimitiveGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
Some("self".into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::generator::rust::ExternFuncCollector;
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypePrimitiveListGenerator, IrTypePrimitiveList);
|
||||
|
||||
impl TypeRustGeneratorTrait for TypePrimitiveListGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
Some(
|
||||
"unsafe {
|
||||
let wrap = support::box_from_leak_ptr(self);
|
||||
support::vec_from_leak_ptr(wrap.ptr, wrap.len)
|
||||
}"
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn wire_struct_fields(&self) -> Option<Vec<String>> {
|
||||
Some(vec![
|
||||
format!("ptr: *mut {}", self.ir.primitive.rust_wire_type()),
|
||||
"len: i32".to_string(),
|
||||
])
|
||||
}
|
||||
|
||||
fn allocate_funcs(&self, collector: &mut ExternFuncCollector) -> String {
|
||||
collector.generate(
|
||||
&format!("new_{}", self.ir.safe_ident()),
|
||||
&["len: i32"],
|
||||
Some(&format!(
|
||||
"{}{}",
|
||||
self.ir.rust_wire_modifier(),
|
||||
self.ir.rust_wire_type()
|
||||
)),
|
||||
&format!(
|
||||
"let ans = {} {{ ptr: support::new_leak_vec_ptr(Default::default(), len), len }};
|
||||
support::new_leak_box_ptr(ans)",
|
||||
self.ir.rust_wire_type(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
185
libs/flutter_rust_bridge_codegen/src/generator/rust/ty_struct.rs
Normal file
185
libs/flutter_rust_bridge_codegen/src/generator/rust/ty_struct.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
use crate::generator::rust::ty::*;
|
||||
use crate::generator::rust::ExternFuncCollector;
|
||||
use crate::ir::*;
|
||||
use crate::type_rust_generator_struct;
|
||||
|
||||
type_rust_generator_struct!(TypeStructRefGenerator, IrTypeStructRef);
|
||||
|
||||
impl TypeRustGeneratorTrait for TypeStructRefGenerator<'_> {
|
||||
fn wire2api_body(&self) -> Option<String> {
|
||||
let api_struct = self.ir.get(self.context.ir_file);
|
||||
let fields_str = &api_struct
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{} self.{}.wire2api()",
|
||||
if api_struct.is_fields_named {
|
||||
field.name.rust_style().to_string() + ": "
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
field.name.rust_style()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
let (left, right) = api_struct.brackets_pair();
|
||||
Some(format!(
|
||||
"{}{}{}{}",
|
||||
self.ir.rust_api_type(),
|
||||
left,
|
||||
fields_str,
|
||||
right
|
||||
))
|
||||
}
|
||||
|
||||
fn wire_struct_fields(&self) -> Option<Vec<String>> {
|
||||
let s = self.ir.get(self.context.ir_file);
|
||||
Some(
|
||||
s.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{}: {}{}",
|
||||
field.name.rust_style(),
|
||||
field.ty.rust_wire_modifier(),
|
||||
field.ty.rust_wire_type()
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn static_checks(&self) -> Option<String> {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
src.wrapper_name.as_ref()?;
|
||||
|
||||
let var = if src.is_fields_named {
|
||||
src.name.clone()
|
||||
} else {
|
||||
// let bindings cannot shadow tuple structs
|
||||
format!("{}_", src.name)
|
||||
};
|
||||
let checks = src
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
format!(
|
||||
"let _: {} = {}.{};\n",
|
||||
field.ty.rust_api_type(),
|
||||
var,
|
||||
if src.is_fields_named {
|
||||
field.name.to_string()
|
||||
} else {
|
||||
i.to_string()
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
Some(format!(
|
||||
"{{ let {} = None::<{}>.unwrap(); {} }} ",
|
||||
var, src.name, checks
|
||||
))
|
||||
}
|
||||
|
||||
fn wrapper_struct(&self) -> Option<String> {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
src.wrapper_name.as_ref().cloned()
|
||||
}
|
||||
|
||||
fn wrap_obj(&self, obj: String) -> String {
|
||||
match self.wrapper_struct() {
|
||||
Some(wrapper) => format!("{}({})", wrapper, obj),
|
||||
None => obj,
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_intodart(&self) -> String {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
|
||||
let unwrap = match &src.wrapper_name {
|
||||
Some(_) => ".0",
|
||||
None => "",
|
||||
};
|
||||
let body = src
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
let field_ref = if src.is_fields_named {
|
||||
field.name.rust_style().to_string()
|
||||
} else {
|
||||
i.to_string()
|
||||
};
|
||||
let gen = TypeRustGenerator::new(field.ty.clone(), self.context.ir_file);
|
||||
gen.convert_to_dart(gen.wrap_obj(format!("self{}.{}", unwrap, field_ref)))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(",\n");
|
||||
|
||||
let name = match &src.wrapper_name {
|
||||
Some(wrapper) => wrapper,
|
||||
None => &src.name,
|
||||
};
|
||||
format!(
|
||||
"impl support::IntoDart for {} {{
|
||||
fn into_dart(self) -> support::DartCObject {{
|
||||
vec![
|
||||
{}
|
||||
].into_dart()
|
||||
}}
|
||||
}}
|
||||
impl support::IntoDartExceptPrimitive for {} {{}}
|
||||
",
|
||||
name, body, name,
|
||||
)
|
||||
}
|
||||
|
||||
fn new_with_nullptr(&self, _collector: &mut ExternFuncCollector) -> String {
|
||||
let src = self.ir.get(self.context.ir_file);
|
||||
|
||||
let body = {
|
||||
src.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
format!(
|
||||
"{}: {},",
|
||||
field.name.rust_style(),
|
||||
if field.ty.rust_wire_is_pointer() {
|
||||
"core::ptr::null_mut()"
|
||||
} else {
|
||||
"Default::default()"
|
||||
}
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
format!(
|
||||
r#"impl NewWithNullPtr for {} {{
|
||||
fn new_with_null_ptr() -> Self {{
|
||||
Self {{ {} }}
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
self.ir.rust_wire_type(),
|
||||
body,
|
||||
)
|
||||
}
|
||||
|
||||
fn imports(&self) -> Option<String> {
|
||||
let api_struct = self.ir.get(self.context.ir_file);
|
||||
if api_struct.path.is_some() {
|
||||
Some(format!(
|
||||
"use {};",
|
||||
api_struct.path.as_ref().unwrap().join("::")
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
7
libs/flutter_rust_bridge_codegen/src/ir/annotation.rs
Normal file
7
libs/flutter_rust_bridge_codegen/src/ir/annotation.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use crate::ir::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrDartAnnotation {
|
||||
pub content: String,
|
||||
pub library: Option<IrDartImport>,
|
||||
}
|
||||
26
libs/flutter_rust_bridge_codegen/src/ir/comment.rs
Normal file
26
libs/flutter_rust_bridge_codegen/src/ir/comment.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrComment(String);
|
||||
|
||||
impl IrComment {
|
||||
pub fn comment(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for IrComment {
|
||||
fn from(input: &str) -> Self {
|
||||
if input.contains('\n') {
|
||||
// Dart's formatter has issues with block comments
|
||||
// so we convert them ahead of time.
|
||||
let formatted = input
|
||||
.split('\n')
|
||||
.into_iter()
|
||||
.map(|e| format!("///{}", e))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
Self(formatted)
|
||||
} else {
|
||||
Self(format!("///{}", input))
|
||||
}
|
||||
}
|
||||
}
|
||||
9
libs/flutter_rust_bridge_codegen/src/ir/field.rs
Normal file
9
libs/flutter_rust_bridge_codegen/src/ir/field.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use crate::ir::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrField {
|
||||
pub ty: IrType,
|
||||
pub name: IrIdent,
|
||||
pub is_final: bool,
|
||||
pub comments: Vec<IrComment>,
|
||||
}
|
||||
61
libs/flutter_rust_bridge_codegen/src/ir/file.rs
Normal file
61
libs/flutter_rust_bridge_codegen/src/ir/file.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use crate::ir::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub type IrStructPool = HashMap<String, IrStruct>;
|
||||
pub type IrEnumPool = HashMap<String, IrEnum>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrFile {
|
||||
pub funcs: Vec<IrFunc>,
|
||||
pub struct_pool: IrStructPool,
|
||||
pub enum_pool: IrEnumPool,
|
||||
pub has_executor: bool,
|
||||
}
|
||||
|
||||
impl IrFile {
|
||||
/// [f] returns [true] if it wants to stop going to the *children* of this subtree
|
||||
pub fn visit_types<F: FnMut(&IrType) -> bool>(
|
||||
&self,
|
||||
f: &mut F,
|
||||
include_func_inputs: bool,
|
||||
include_func_output: bool,
|
||||
) {
|
||||
for func in &self.funcs {
|
||||
if include_func_inputs {
|
||||
for field in &func.inputs {
|
||||
field.ty.visit_types(f, self);
|
||||
}
|
||||
}
|
||||
if include_func_output {
|
||||
func.output.visit_types(f, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn distinct_types(
|
||||
&self,
|
||||
include_func_inputs: bool,
|
||||
include_func_output: bool,
|
||||
) -> Vec<IrType> {
|
||||
let mut seen_idents = HashSet::new();
|
||||
let mut ans = Vec::new();
|
||||
self.visit_types(
|
||||
&mut |ty| {
|
||||
let ident = ty.safe_ident();
|
||||
let contains = seen_idents.contains(&ident);
|
||||
if !contains {
|
||||
seen_idents.insert(ident);
|
||||
ans.push(ty.clone());
|
||||
}
|
||||
contains
|
||||
},
|
||||
include_func_inputs,
|
||||
include_func_output,
|
||||
);
|
||||
|
||||
// make the output change less when input change
|
||||
ans.sort_by_key(|ty| ty.safe_ident());
|
||||
|
||||
ans
|
||||
}
|
||||
}
|
||||
60
libs/flutter_rust_bridge_codegen/src/ir/func.rs
Normal file
60
libs/flutter_rust_bridge_codegen/src/ir/func.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::ir::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrFunc {
|
||||
pub name: String,
|
||||
pub inputs: Vec<IrField>,
|
||||
pub output: IrType,
|
||||
pub fallible: bool,
|
||||
pub mode: IrFuncMode,
|
||||
pub comments: Vec<IrComment>,
|
||||
}
|
||||
|
||||
impl IrFunc {
|
||||
pub fn wire_func_name(&self) -> String {
|
||||
format!("wire_{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a function's output type
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IrFuncOutput {
|
||||
ResultType(IrType),
|
||||
Type(IrType),
|
||||
}
|
||||
|
||||
/// Represents the type of an argument to a function
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IrFuncArg {
|
||||
StreamSinkType(IrType),
|
||||
Type(IrType),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialOrd, PartialEq)]
|
||||
pub enum IrFuncMode {
|
||||
Normal,
|
||||
Sync,
|
||||
Stream,
|
||||
}
|
||||
|
||||
impl IrFuncMode {
|
||||
pub fn dart_return_type(&self, inner: &str) -> String {
|
||||
match self {
|
||||
Self::Normal => format!("Future<{}>", inner),
|
||||
Self::Sync => inner.to_string(),
|
||||
Self::Stream => format!("Stream<{}>", inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ffi_call_mode(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Normal => "Normal",
|
||||
Self::Sync => "Sync",
|
||||
Self::Stream => "Stream",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_port_argument(&self) -> bool {
|
||||
self != &Self::Sync
|
||||
}
|
||||
}
|
||||
26
libs/flutter_rust_bridge_codegen/src/ir/ident.rs
Normal file
26
libs/flutter_rust_bridge_codegen/src/ir/ident.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use convert_case::{Case, Casing};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrIdent {
|
||||
pub raw: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for IrIdent {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
fmt.write_str(&self.raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl IrIdent {
|
||||
pub fn new(raw: String) -> IrIdent {
|
||||
IrIdent { raw }
|
||||
}
|
||||
|
||||
pub fn rust_style(&self) -> &str {
|
||||
&self.raw
|
||||
}
|
||||
|
||||
pub fn dart_style(&self) -> String {
|
||||
self.raw.to_case(Case::Camel)
|
||||
}
|
||||
}
|
||||
5
libs/flutter_rust_bridge_codegen/src/ir/import.rs
Normal file
5
libs/flutter_rust_bridge_codegen/src/ir/import.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct IrDartImport {
|
||||
pub uri: String,
|
||||
pub alias: Option<String>,
|
||||
}
|
||||
33
libs/flutter_rust_bridge_codegen/src/ir/mod.rs
Normal file
33
libs/flutter_rust_bridge_codegen/src/ir/mod.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
mod annotation;
|
||||
mod comment;
|
||||
mod field;
|
||||
mod file;
|
||||
mod func;
|
||||
mod ident;
|
||||
mod import;
|
||||
mod ty;
|
||||
mod ty_boxed;
|
||||
mod ty_delegate;
|
||||
mod ty_enum;
|
||||
mod ty_general_list;
|
||||
mod ty_optional;
|
||||
mod ty_primitive;
|
||||
mod ty_primitive_list;
|
||||
mod ty_struct;
|
||||
|
||||
pub use annotation::*;
|
||||
pub use comment::*;
|
||||
pub use field::*;
|
||||
pub use file::*;
|
||||
pub use func::*;
|
||||
pub use ident::*;
|
||||
pub use import::*;
|
||||
pub use ty::*;
|
||||
pub use ty_boxed::*;
|
||||
pub use ty_delegate::*;
|
||||
pub use ty_enum::*;
|
||||
pub use ty_general_list::*;
|
||||
pub use ty_optional::*;
|
||||
pub use ty_primitive::*;
|
||||
pub use ty_primitive_list::*;
|
||||
pub use ty_struct::*;
|
||||
84
libs/flutter_rust_bridge_codegen/src/ir/ty.rs
Normal file
84
libs/flutter_rust_bridge_codegen/src/ir/ty.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::ir::*;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use IrType::*;
|
||||
|
||||
/// Remark: "Ty" instead of "Type", since "type" is a reserved word in Rust.
|
||||
#[enum_dispatch(IrTypeTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IrType {
|
||||
Primitive(IrTypePrimitive),
|
||||
Delegate(IrTypeDelegate),
|
||||
PrimitiveList(IrTypePrimitiveList),
|
||||
Optional(IrTypeOptional),
|
||||
GeneralList(IrTypeGeneralList),
|
||||
StructRef(IrTypeStructRef),
|
||||
Boxed(IrTypeBoxed),
|
||||
EnumRef(IrTypeEnumRef),
|
||||
}
|
||||
|
||||
impl IrType {
|
||||
pub fn visit_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile) {
|
||||
if f(self) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.visit_children_types(f, ir_file);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dart_required_modifier(&self) -> &'static str {
|
||||
match self {
|
||||
Optional(_) => "",
|
||||
_ => "required ",
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional indirection for types put behind a vector
|
||||
#[inline]
|
||||
pub fn rust_ptr_modifier(&self) -> &'static str {
|
||||
match self {
|
||||
Optional(_) | Delegate(IrTypeDelegate::String) => "*mut ",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait IrTypeTrait {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile);
|
||||
|
||||
fn safe_ident(&self) -> String;
|
||||
|
||||
fn dart_api_type(&self) -> String;
|
||||
|
||||
fn dart_wire_type(&self) -> String;
|
||||
|
||||
fn rust_api_type(&self) -> String;
|
||||
|
||||
fn rust_wire_type(&self) -> String;
|
||||
|
||||
fn rust_wire_modifier(&self) -> String {
|
||||
if self.rust_wire_is_pointer() {
|
||||
"*mut ".to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_wire_is_pointer(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn optional_boundary_index(types: &[&IrType]) -> Option<usize> {
|
||||
types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|ty| matches!(ty.1, Optional(_)))
|
||||
.and_then(|(idx, _)| {
|
||||
(&types[idx..])
|
||||
.iter()
|
||||
.all(|ty| matches!(ty, Optional(_)))
|
||||
.then(|| idx)
|
||||
})
|
||||
}
|
||||
56
libs/flutter_rust_bridge_codegen/src/ir/ty_boxed.rs
Normal file
56
libs/flutter_rust_bridge_codegen/src/ir/ty_boxed.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use crate::ir::IrType::Primitive;
|
||||
use crate::ir::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrTypeBoxed {
|
||||
/// if false, means that we automatically add it when transforming it - it does not exist in real api.
|
||||
pub exist_in_real_api: bool,
|
||||
pub inner: Box<IrType>,
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypeBoxed {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile) {
|
||||
self.inner.visit_types(f, ir_file);
|
||||
}
|
||||
|
||||
fn safe_ident(&self) -> String {
|
||||
format!(
|
||||
"box_{}{}",
|
||||
if self.exist_in_real_api {
|
||||
""
|
||||
} else {
|
||||
"autoadd_"
|
||||
},
|
||||
self.inner.safe_ident()
|
||||
)
|
||||
}
|
||||
|
||||
fn dart_api_type(&self) -> String {
|
||||
self.inner.dart_api_type()
|
||||
}
|
||||
|
||||
fn dart_wire_type(&self) -> String {
|
||||
let wire_type = if let Primitive(prim) = &*self.inner {
|
||||
prim.dart_native_type().to_owned()
|
||||
} else {
|
||||
self.inner.dart_wire_type()
|
||||
};
|
||||
format!("ffi.Pointer<{}>", wire_type)
|
||||
}
|
||||
|
||||
fn rust_api_type(&self) -> String {
|
||||
if self.exist_in_real_api {
|
||||
format!("Box<{}>", self.inner.rust_api_type())
|
||||
} else {
|
||||
self.inner.rust_api_type()
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_wire_type(&self) -> String {
|
||||
self.inner.rust_wire_type()
|
||||
}
|
||||
|
||||
fn rust_wire_is_pointer(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
85
libs/flutter_rust_bridge_codegen/src/ir/ty_delegate.rs
Normal file
85
libs/flutter_rust_bridge_codegen/src/ir/ty_delegate.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::ir::*;
|
||||
|
||||
/// types that delegate to another type
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IrTypeDelegate {
|
||||
String,
|
||||
StringList,
|
||||
SyncReturnVecU8,
|
||||
ZeroCopyBufferVecPrimitive(IrTypePrimitive),
|
||||
}
|
||||
|
||||
impl IrTypeDelegate {
|
||||
pub fn get_delegate(&self) -> IrType {
|
||||
match self {
|
||||
IrTypeDelegate::String => IrType::PrimitiveList(IrTypePrimitiveList {
|
||||
primitive: IrTypePrimitive::U8,
|
||||
}),
|
||||
IrTypeDelegate::SyncReturnVecU8 => IrType::PrimitiveList(IrTypePrimitiveList {
|
||||
primitive: IrTypePrimitive::U8,
|
||||
}),
|
||||
IrTypeDelegate::ZeroCopyBufferVecPrimitive(primitive) => {
|
||||
IrType::PrimitiveList(IrTypePrimitiveList {
|
||||
primitive: primitive.clone(),
|
||||
})
|
||||
}
|
||||
IrTypeDelegate::StringList => IrType::Delegate(IrTypeDelegate::String),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypeDelegate {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile) {
|
||||
self.get_delegate().visit_types(f, ir_file);
|
||||
}
|
||||
|
||||
fn safe_ident(&self) -> String {
|
||||
match self {
|
||||
IrTypeDelegate::String => "String".to_owned(),
|
||||
IrTypeDelegate::StringList => "StringList".to_owned(),
|
||||
IrTypeDelegate::SyncReturnVecU8 => "SyncReturnVecU8".to_owned(),
|
||||
IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
|
||||
"ZeroCopyBuffer_".to_owned() + &self.get_delegate().dart_api_type()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dart_api_type(&self) -> String {
|
||||
match self {
|
||||
IrTypeDelegate::String => "String".to_string(),
|
||||
IrTypeDelegate::StringList => "List<String>".to_owned(),
|
||||
IrTypeDelegate::SyncReturnVecU8 | IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
|
||||
self.get_delegate().dart_api_type()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dart_wire_type(&self) -> String {
|
||||
match self {
|
||||
IrTypeDelegate::StringList => "ffi.Pointer<wire_StringList>".to_owned(),
|
||||
_ => self.get_delegate().dart_wire_type(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_api_type(&self) -> String {
|
||||
match self {
|
||||
IrTypeDelegate::String => "String".to_owned(),
|
||||
IrTypeDelegate::SyncReturnVecU8 => "SyncReturn<Vec<u8>>".to_string(),
|
||||
IrTypeDelegate::StringList => "Vec<String>".to_owned(),
|
||||
IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
|
||||
format!("ZeroCopyBuffer<{}>", self.get_delegate().rust_api_type())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_wire_type(&self) -> String {
|
||||
match self {
|
||||
IrTypeDelegate::StringList => "wire_StringList".to_owned(),
|
||||
_ => self.get_delegate().rust_wire_type(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_wire_is_pointer(&self) -> bool {
|
||||
self.get_delegate().rust_wire_is_pointer()
|
||||
}
|
||||
}
|
||||
139
libs/flutter_rust_bridge_codegen/src/ir/ty_enum.rs
Normal file
139
libs/flutter_rust_bridge_codegen/src/ir/ty_enum.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::ir::IrType::{EnumRef, StructRef};
|
||||
use crate::ir::*;
|
||||
use convert_case::{Case, Casing};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrTypeEnumRef {
|
||||
pub name: String,
|
||||
pub is_struct: bool,
|
||||
}
|
||||
|
||||
impl IrTypeEnumRef {
|
||||
pub fn get<'a>(&self, file: &'a IrFile) -> &'a IrEnum {
|
||||
&file.enum_pool[&self.name]
|
||||
}
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypeEnumRef {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile) {
|
||||
let enu = self.get(ir_file);
|
||||
for variant in enu.variants() {
|
||||
if let IrVariantKind::Struct(st) = &variant.kind {
|
||||
st.fields
|
||||
.iter()
|
||||
.for_each(|field| field.ty.visit_types(f, ir_file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn safe_ident(&self) -> String {
|
||||
self.dart_api_type().to_case(Case::Snake)
|
||||
}
|
||||
fn dart_api_type(&self) -> String {
|
||||
self.name.to_string()
|
||||
}
|
||||
fn dart_wire_type(&self) -> String {
|
||||
if self.is_struct {
|
||||
self.rust_wire_type()
|
||||
} else {
|
||||
"int".to_owned()
|
||||
}
|
||||
}
|
||||
fn rust_api_type(&self) -> String {
|
||||
self.name.to_string()
|
||||
}
|
||||
fn rust_wire_type(&self) -> String {
|
||||
if self.is_struct {
|
||||
format!("wire_{}", self.name)
|
||||
} else {
|
||||
"i32".to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrEnum {
|
||||
pub name: String,
|
||||
pub wrapper_name: Option<String>,
|
||||
pub path: Vec<String>,
|
||||
pub comments: Vec<IrComment>,
|
||||
_variants: Vec<IrVariant>,
|
||||
_is_struct: bool,
|
||||
}
|
||||
|
||||
impl IrEnum {
|
||||
pub fn new(
|
||||
name: String,
|
||||
wrapper_name: Option<String>,
|
||||
path: Vec<String>,
|
||||
comments: Vec<IrComment>,
|
||||
mut variants: Vec<IrVariant>,
|
||||
) -> Self {
|
||||
fn wrap_box(ty: IrType) -> IrType {
|
||||
match ty {
|
||||
StructRef(_)
|
||||
| EnumRef(IrTypeEnumRef {
|
||||
is_struct: true, ..
|
||||
}) => IrType::Boxed(IrTypeBoxed {
|
||||
exist_in_real_api: false,
|
||||
inner: Box::new(ty),
|
||||
}),
|
||||
_ => ty,
|
||||
}
|
||||
}
|
||||
let _is_struct = variants
|
||||
.iter()
|
||||
.any(|variant| !matches!(variant.kind, IrVariantKind::Value));
|
||||
if _is_struct {
|
||||
variants = variants
|
||||
.into_iter()
|
||||
.map(|variant| IrVariant {
|
||||
kind: match variant.kind {
|
||||
IrVariantKind::Struct(st) => IrVariantKind::Struct(IrStruct {
|
||||
fields: st
|
||||
.fields
|
||||
.into_iter()
|
||||
.map(|field| IrField {
|
||||
ty: wrap_box(field.ty),
|
||||
..field
|
||||
})
|
||||
.collect(),
|
||||
..st
|
||||
}),
|
||||
_ => variant.kind,
|
||||
},
|
||||
..variant
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
Self {
|
||||
name,
|
||||
wrapper_name,
|
||||
path,
|
||||
comments,
|
||||
_variants: variants,
|
||||
_is_struct,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn variants(&self) -> &[IrVariant] {
|
||||
&self._variants
|
||||
}
|
||||
|
||||
pub fn is_struct(&self) -> bool {
|
||||
self._is_struct
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrVariant {
|
||||
pub name: IrIdent,
|
||||
pub comments: Vec<IrComment>,
|
||||
pub kind: IrVariantKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IrVariantKind {
|
||||
Value,
|
||||
Struct(IrStruct),
|
||||
}
|
||||
36
libs/flutter_rust_bridge_codegen/src/ir/ty_general_list.rs
Normal file
36
libs/flutter_rust_bridge_codegen/src/ir/ty_general_list.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::ir::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrTypeGeneralList {
|
||||
pub inner: Box<IrType>,
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypeGeneralList {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile) {
|
||||
self.inner.visit_types(f, ir_file);
|
||||
}
|
||||
|
||||
fn safe_ident(&self) -> String {
|
||||
format!("list_{}", self.inner.safe_ident())
|
||||
}
|
||||
|
||||
fn dart_api_type(&self) -> String {
|
||||
format!("List<{}>", self.inner.dart_api_type())
|
||||
}
|
||||
|
||||
fn dart_wire_type(&self) -> String {
|
||||
format!("ffi.Pointer<wire_{}>", self.safe_ident())
|
||||
}
|
||||
|
||||
fn rust_api_type(&self) -> String {
|
||||
format!("Vec<{}>", self.inner.rust_api_type())
|
||||
}
|
||||
|
||||
fn rust_wire_type(&self) -> String {
|
||||
format!("wire_{}", self.safe_ident())
|
||||
}
|
||||
|
||||
fn rust_wire_is_pointer(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
65
libs/flutter_rust_bridge_codegen/src/ir/ty_optional.rs
Normal file
65
libs/flutter_rust_bridge_codegen/src/ir/ty_optional.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use crate::ir::IrType::*;
|
||||
use crate::ir::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrTypeOptional {
|
||||
pub inner: Box<IrType>,
|
||||
}
|
||||
|
||||
impl IrTypeOptional {
|
||||
pub fn new_prim(prim: IrTypePrimitive) -> Self {
|
||||
Self {
|
||||
inner: Box::new(Boxed(IrTypeBoxed {
|
||||
inner: Box::new(Primitive(prim)),
|
||||
exist_in_real_api: false,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ptr(ptr: IrType) -> Self {
|
||||
Self {
|
||||
inner: Box::new(ptr),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_primitive(&self) -> bool {
|
||||
matches!(&*self.inner, Boxed(boxed) if matches!(*boxed.inner, IrType::Primitive(_)))
|
||||
}
|
||||
|
||||
pub fn is_list(&self) -> bool {
|
||||
matches!(&*self.inner, GeneralList(_) | PrimitiveList(_))
|
||||
}
|
||||
|
||||
pub fn is_delegate(&self) -> bool {
|
||||
matches!(&*self.inner, Delegate(_))
|
||||
}
|
||||
|
||||
pub fn needs_initialization(&self) -> bool {
|
||||
!(self.is_primitive() || self.is_delegate())
|
||||
}
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypeOptional {
|
||||
fn safe_ident(&self) -> String {
|
||||
format!("opt_{}", self.inner.safe_ident())
|
||||
}
|
||||
fn rust_wire_type(&self) -> String {
|
||||
self.inner.rust_wire_type()
|
||||
}
|
||||
fn rust_api_type(&self) -> String {
|
||||
format!("Option<{}>", self.inner.rust_api_type())
|
||||
}
|
||||
fn dart_wire_type(&self) -> String {
|
||||
self.inner.dart_wire_type()
|
||||
}
|
||||
fn dart_api_type(&self) -> String {
|
||||
format!("{}?", self.inner.dart_api_type())
|
||||
}
|
||||
fn rust_wire_is_pointer(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile) {
|
||||
self.inner.visit_types(f, ir_file);
|
||||
}
|
||||
}
|
||||
114
libs/flutter_rust_bridge_codegen/src/ir/ty_primitive.rs
Normal file
114
libs/flutter_rust_bridge_codegen/src/ir/ty_primitive.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use crate::ir::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IrTypePrimitive {
|
||||
U8,
|
||||
I8,
|
||||
U16,
|
||||
I16,
|
||||
U32,
|
||||
I32,
|
||||
U64,
|
||||
I64,
|
||||
F32,
|
||||
F64,
|
||||
Bool,
|
||||
Unit,
|
||||
Usize,
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypePrimitive {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, _f: &mut F, _ir_file: &IrFile) {}
|
||||
|
||||
fn safe_ident(&self) -> String {
|
||||
self.rust_api_type()
|
||||
}
|
||||
|
||||
fn dart_api_type(&self) -> String {
|
||||
match self {
|
||||
IrTypePrimitive::U8
|
||||
| IrTypePrimitive::I8
|
||||
| IrTypePrimitive::U16
|
||||
| IrTypePrimitive::I16
|
||||
| IrTypePrimitive::U32
|
||||
| IrTypePrimitive::I32
|
||||
| IrTypePrimitive::U64
|
||||
| IrTypePrimitive::I64
|
||||
| IrTypePrimitive::Usize => "int",
|
||||
IrTypePrimitive::F32 | IrTypePrimitive::F64 => "double",
|
||||
IrTypePrimitive::Bool => "bool",
|
||||
IrTypePrimitive::Unit => "void",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn dart_wire_type(&self) -> String {
|
||||
match self {
|
||||
IrTypePrimitive::Bool => "int".to_owned(),
|
||||
_ => self.dart_api_type(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_api_type(&self) -> String {
|
||||
self.rust_wire_type()
|
||||
}
|
||||
|
||||
fn rust_wire_type(&self) -> String {
|
||||
match self {
|
||||
IrTypePrimitive::U8 => "u8",
|
||||
IrTypePrimitive::I8 => "i8",
|
||||
IrTypePrimitive::U16 => "u16",
|
||||
IrTypePrimitive::I16 => "i16",
|
||||
IrTypePrimitive::U32 => "u32",
|
||||
IrTypePrimitive::I32 => "i32",
|
||||
IrTypePrimitive::U64 => "u64",
|
||||
IrTypePrimitive::I64 => "i64",
|
||||
IrTypePrimitive::F32 => "f32",
|
||||
IrTypePrimitive::F64 => "f64",
|
||||
IrTypePrimitive::Bool => "bool",
|
||||
IrTypePrimitive::Unit => "unit",
|
||||
IrTypePrimitive::Usize => "usize",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl IrTypePrimitive {
|
||||
/// Representations of primitives within Dart's pointers, e.g. `ffi.Pointer<ffi.Uint8>`.
|
||||
/// This is enforced on Dart's side, and should be used instead of `dart_wire_type`
|
||||
/// whenever primitives are put behind a pointer.
|
||||
pub fn dart_native_type(&self) -> &'static str {
|
||||
match self {
|
||||
IrTypePrimitive::U8 | IrTypePrimitive::Bool => "ffi.Uint8",
|
||||
IrTypePrimitive::I8 => "ffi.Int8",
|
||||
IrTypePrimitive::U16 => "ffi.Uint16",
|
||||
IrTypePrimitive::I16 => "ffi.Int16",
|
||||
IrTypePrimitive::U32 => "ffi.Uint32",
|
||||
IrTypePrimitive::I32 => "ffi.Int32",
|
||||
IrTypePrimitive::U64 => "ffi.Uint64",
|
||||
IrTypePrimitive::I64 => "ffi.Int64",
|
||||
IrTypePrimitive::F32 => "ffi.Float",
|
||||
IrTypePrimitive::F64 => "ffi.Double",
|
||||
IrTypePrimitive::Unit => "ffi.Void",
|
||||
IrTypePrimitive::Usize => "ffi.Usize",
|
||||
}
|
||||
}
|
||||
pub fn try_from_rust_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"u8" => Some(IrTypePrimitive::U8),
|
||||
"i8" => Some(IrTypePrimitive::I8),
|
||||
"u16" => Some(IrTypePrimitive::U16),
|
||||
"i16" => Some(IrTypePrimitive::I16),
|
||||
"u32" => Some(IrTypePrimitive::U32),
|
||||
"i32" => Some(IrTypePrimitive::I32),
|
||||
"u64" => Some(IrTypePrimitive::U64),
|
||||
"i64" => Some(IrTypePrimitive::I64),
|
||||
"f32" => Some(IrTypePrimitive::F32),
|
||||
"f64" => Some(IrTypePrimitive::F64),
|
||||
"bool" => Some(IrTypePrimitive::Bool),
|
||||
"()" => Some(IrTypePrimitive::Unit),
|
||||
"usize" => Some(IrTypePrimitive::Usize),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
50
libs/flutter_rust_bridge_codegen/src/ir/ty_primitive_list.rs
Normal file
50
libs/flutter_rust_bridge_codegen/src/ir/ty_primitive_list.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::ir::*;
|
||||
use convert_case::{Case, Casing};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrTypePrimitiveList {
|
||||
pub primitive: IrTypePrimitive,
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypePrimitiveList {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, _ir_file: &IrFile) {
|
||||
f(&IrType::Primitive(self.primitive.clone()));
|
||||
}
|
||||
|
||||
fn safe_ident(&self) -> String {
|
||||
self.dart_api_type().to_case(Case::Snake)
|
||||
}
|
||||
|
||||
fn dart_api_type(&self) -> String {
|
||||
match &self.primitive {
|
||||
IrTypePrimitive::U8 => "Uint8List",
|
||||
IrTypePrimitive::I8 => "Int8List",
|
||||
IrTypePrimitive::U16 => "Uint16List",
|
||||
IrTypePrimitive::I16 => "Int16List",
|
||||
IrTypePrimitive::U32 => "Uint32List",
|
||||
IrTypePrimitive::I32 => "Int32List",
|
||||
IrTypePrimitive::U64 => "Uint64List",
|
||||
IrTypePrimitive::I64 => "Int64List",
|
||||
IrTypePrimitive::F32 => "Float32List",
|
||||
IrTypePrimitive::F64 => "Float64List",
|
||||
_ => panic!("does not support {:?} yet", &self.primitive),
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn dart_wire_type(&self) -> String {
|
||||
format!("ffi.Pointer<wire_{}>", self.safe_ident())
|
||||
}
|
||||
|
||||
fn rust_api_type(&self) -> String {
|
||||
format!("Vec<{}>", self.primitive.rust_api_type())
|
||||
}
|
||||
|
||||
fn rust_wire_type(&self) -> String {
|
||||
format!("wire_{}", self.safe_ident())
|
||||
}
|
||||
|
||||
fn rust_wire_is_pointer(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
66
libs/flutter_rust_bridge_codegen/src/ir/ty_struct.rs
Normal file
66
libs/flutter_rust_bridge_codegen/src/ir/ty_struct.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::ir::*;
|
||||
use convert_case::{Case, Casing};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrTypeStructRef {
|
||||
pub name: String,
|
||||
pub freezed: bool,
|
||||
}
|
||||
|
||||
impl IrTypeStructRef {
|
||||
pub fn get<'a>(&self, f: &'a IrFile) -> &'a IrStruct {
|
||||
&f.struct_pool[&self.name]
|
||||
}
|
||||
}
|
||||
|
||||
impl IrTypeTrait for IrTypeStructRef {
|
||||
fn visit_children_types<F: FnMut(&IrType) -> bool>(&self, f: &mut F, ir_file: &IrFile) {
|
||||
for field in &self.get(ir_file).fields {
|
||||
field.ty.visit_types(f, ir_file);
|
||||
}
|
||||
}
|
||||
|
||||
fn safe_ident(&self) -> String {
|
||||
self.dart_api_type().to_case(Case::Snake)
|
||||
}
|
||||
fn dart_api_type(&self) -> String {
|
||||
self.name.to_string()
|
||||
}
|
||||
|
||||
fn dart_wire_type(&self) -> String {
|
||||
self.rust_wire_type()
|
||||
}
|
||||
|
||||
fn rust_api_type(&self) -> String {
|
||||
self.name.to_string()
|
||||
}
|
||||
|
||||
fn rust_wire_type(&self) -> String {
|
||||
format!("wire_{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IrStruct {
|
||||
pub name: String,
|
||||
pub wrapper_name: Option<String>,
|
||||
pub path: Option<Vec<String>>,
|
||||
pub fields: Vec<IrField>,
|
||||
pub is_fields_named: bool,
|
||||
pub dart_metadata: Vec<IrDartAnnotation>,
|
||||
pub comments: Vec<IrComment>,
|
||||
}
|
||||
|
||||
impl IrStruct {
|
||||
pub fn brackets_pair(&self) -> (char, char) {
|
||||
if self.is_fields_named {
|
||||
('{', '}')
|
||||
} else {
|
||||
('(', ')')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn using_freezed(&self) -> bool {
|
||||
self.dart_metadata.iter().any(|it| it.content == "freezed")
|
||||
}
|
||||
}
|
||||
183
libs/flutter_rust_bridge_codegen/src/lib.rs
Normal file
183
libs/flutter_rust_bridge_codegen/src/lib.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use log::info;
|
||||
use pathdiff::diff_paths;
|
||||
|
||||
use crate::commands::ensure_tools_available;
|
||||
pub use crate::config::RawOpts as Opts;
|
||||
use crate::ir::*;
|
||||
use crate::others::*;
|
||||
use crate::utils::*;
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
mod error;
|
||||
mod generator;
|
||||
mod ir;
|
||||
mod markers;
|
||||
mod others;
|
||||
mod parser;
|
||||
mod source_graph;
|
||||
mod transformer;
|
||||
mod utils;
|
||||
use error::*;
|
||||
|
||||
pub fn frb_codegen(raw_opts: Opts) -> anyhow::Result<()> {
|
||||
ensure_tools_available()?;
|
||||
|
||||
let config = config::parse(raw_opts);
|
||||
info!("Picked config: {:?}", &config);
|
||||
|
||||
let rust_output_dir = Path::new(&config.rust_output_path).parent().unwrap();
|
||||
let dart_output_dir = Path::new(&config.dart_output_path).parent().unwrap();
|
||||
|
||||
info!("Phase: Parse source code to AST");
|
||||
let source_rust_content = fs::read_to_string(&config.rust_input_path)?;
|
||||
let file_ast = syn::parse_file(&source_rust_content)?;
|
||||
|
||||
info!("Phase: Parse AST to IR");
|
||||
let raw_ir_file = parser::parse(&source_rust_content, file_ast, &config.manifest_path);
|
||||
|
||||
info!("Phase: Transform IR");
|
||||
let ir_file = transformer::transform(raw_ir_file);
|
||||
|
||||
info!("Phase: Generate Rust code");
|
||||
let generated_rust = generator::rust::generate(
|
||||
&ir_file,
|
||||
&mod_from_rust_path(&config.rust_input_path, &config.rust_crate_dir),
|
||||
);
|
||||
fs::create_dir_all(&rust_output_dir)?;
|
||||
fs::write(&config.rust_output_path, generated_rust.code)?;
|
||||
|
||||
info!("Phase: Generate Dart code");
|
||||
let (generated_dart, needs_freezed) = generator::dart::generate(
|
||||
&ir_file,
|
||||
&config.dart_api_class_name(),
|
||||
&config.dart_api_impl_class_name(),
|
||||
&config.dart_wire_class_name(),
|
||||
config
|
||||
.dart_output_path_name()
|
||||
.ok_or_else(|| Error::str("Invalid dart_output_path_name"))?,
|
||||
);
|
||||
|
||||
info!("Phase: Other things");
|
||||
|
||||
commands::format_rust(&config.rust_output_path)?;
|
||||
|
||||
if !config.skip_add_mod_to_lib {
|
||||
others::try_add_mod_to_lib(&config.rust_crate_dir, &config.rust_output_path);
|
||||
}
|
||||
|
||||
let c_struct_names = ir_file
|
||||
.distinct_types(true, true)
|
||||
.iter()
|
||||
.filter_map(|ty| {
|
||||
if let IrType::StructRef(_) = ty {
|
||||
Some(ty.rust_wire_type())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let temp_dart_wire_file = tempfile::NamedTempFile::new()?;
|
||||
let temp_bindgen_c_output_file = tempfile::Builder::new().suffix(".h").tempfile()?;
|
||||
with_changed_file(
|
||||
&config.rust_output_path,
|
||||
DUMMY_WIRE_CODE_FOR_BINDGEN,
|
||||
|| {
|
||||
commands::bindgen_rust_to_dart(
|
||||
&config.rust_crate_dir,
|
||||
temp_bindgen_c_output_file
|
||||
.path()
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
temp_dart_wire_file.path().as_os_str().to_str().unwrap(),
|
||||
&config.dart_wire_class_name(),
|
||||
c_struct_names,
|
||||
&config.llvm_path[..],
|
||||
&config.llvm_compiler_opts,
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
let effective_func_names = [
|
||||
generated_rust.extern_func_names,
|
||||
EXTRA_EXTERN_FUNC_NAMES.to_vec(),
|
||||
]
|
||||
.concat();
|
||||
let c_dummy_code = generator::c::generate_dummy(&effective_func_names);
|
||||
for output in &config.c_output_path {
|
||||
fs::create_dir_all(Path::new(output).parent().unwrap())?;
|
||||
fs::write(
|
||||
&output,
|
||||
fs::read_to_string(&temp_bindgen_c_output_file)? + "\n" + &c_dummy_code,
|
||||
)?;
|
||||
}
|
||||
|
||||
fs::create_dir_all(&dart_output_dir)?;
|
||||
let generated_dart_wire_code_raw = fs::read_to_string(temp_dart_wire_file)?;
|
||||
let generated_dart_wire = extract_dart_wire_content(&modify_dart_wire_content(
|
||||
&generated_dart_wire_code_raw,
|
||||
&config.dart_wire_class_name(),
|
||||
));
|
||||
|
||||
sanity_check(&generated_dart_wire.body, &config.dart_wire_class_name())?;
|
||||
|
||||
let generated_dart_decl_all = generated_dart.decl_code;
|
||||
let generated_dart_impl_all = &generated_dart.impl_code + &generated_dart_wire;
|
||||
if let Some(dart_decl_output_path) = &config.dart_decl_output_path {
|
||||
let impl_import_decl = DartBasicCode {
|
||||
import: format!(
|
||||
"import \"{}\";",
|
||||
diff_paths(dart_decl_output_path, dart_output_dir)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
),
|
||||
part: String::new(),
|
||||
body: String::new(),
|
||||
};
|
||||
fs::write(
|
||||
&dart_decl_output_path,
|
||||
(&generated_dart.file_prelude + &generated_dart_decl_all).to_text(),
|
||||
)?;
|
||||
fs::write(
|
||||
&config.dart_output_path,
|
||||
(&generated_dart.file_prelude + &impl_import_decl + &generated_dart_impl_all).to_text(),
|
||||
)?;
|
||||
} else {
|
||||
fs::write(
|
||||
&config.dart_output_path,
|
||||
(&generated_dart.file_prelude + &generated_dart_decl_all + &generated_dart_impl_all)
|
||||
.to_text(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let dart_root = &config.dart_root;
|
||||
if needs_freezed && config.build_runner {
|
||||
let dart_root = dart_root.as_ref().ok_or_else(|| {
|
||||
Error::str(
|
||||
"build_runner configured to run, but Dart root could not be inferred.
|
||||
Please specify --dart-root, or disable build_runner with --no-build-runner.",
|
||||
)
|
||||
})?;
|
||||
commands::build_runner(dart_root)?;
|
||||
commands::format_dart(
|
||||
&config
|
||||
.dart_output_freezed_path()
|
||||
.ok_or_else(|| Error::str("Invalid freezed file path"))?,
|
||||
config.dart_format_line_length,
|
||||
)?;
|
||||
}
|
||||
|
||||
commands::format_dart(&config.dart_output_path, config.dart_format_line_length)?;
|
||||
if let Some(dart_decl_output_path) = &config.dart_decl_output_path {
|
||||
commands::format_dart(dart_decl_output_path, config.dart_format_line_length)?;
|
||||
}
|
||||
|
||||
info!("Success!");
|
||||
Ok(())
|
||||
}
|
||||
19
libs/flutter_rust_bridge_codegen/src/main.rs
Normal file
19
libs/flutter_rust_bridge_codegen/src/main.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use env_logger::Env;
|
||||
use log::info;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use lib_flutter_rust_bridge_codegen::{frb_codegen, Opts};
|
||||
|
||||
fn main() {
|
||||
let opts = Opts::from_args();
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or(if opts.verbose {
|
||||
"debug"
|
||||
} else {
|
||||
"info"
|
||||
}))
|
||||
.init();
|
||||
|
||||
frb_codegen(opts).unwrap();
|
||||
|
||||
info!("Now go and use it :)");
|
||||
}
|
||||
39
libs/flutter_rust_bridge_codegen/src/markers.rs
Normal file
39
libs/flutter_rust_bridge_codegen/src/markers.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use syn::*;
|
||||
|
||||
/// Extract a path from marker `#[frb(mirror(path), ..)]`
|
||||
pub fn extract_mirror_marker(attrs: &[Attribute]) -> Option<Path> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.is_ident("frb"))
|
||||
.find_map(|attr| match attr.parse_meta() {
|
||||
Ok(Meta::List(MetaList { nested, .. })) => nested.iter().find_map(|meta| match meta {
|
||||
NestedMeta::Meta(Meta::List(MetaList {
|
||||
path,
|
||||
nested: mirror,
|
||||
..
|
||||
})) if path.is_ident("mirror") && mirror.len() == 1 => {
|
||||
match mirror.first().unwrap() {
|
||||
NestedMeta::Meta(Meta::Path(path)) => Some(path.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the `#[frb(non_final)]` attribute is present.
|
||||
pub fn has_non_final(attrs: &[Attribute]) -> bool {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.is_ident("frb"))
|
||||
.any(|attr| {
|
||||
match attr.parse_meta() {
|
||||
Ok(Meta::List(MetaList { nested, .. })) => nested.iter().any(|meta| {
|
||||
matches!(meta, NestedMeta::Meta(Meta::Path(path)) if path.is_ident("non_final"))
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
}
|
||||
169
libs/flutter_rust_bridge_codegen/src/others.rs
Normal file
169
libs/flutter_rust_bridge_codegen/src/others.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use std::fs;
|
||||
use std::ops::Add;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{info, warn};
|
||||
use pathdiff::diff_paths;
|
||||
use regex::RegexBuilder;
|
||||
|
||||
// NOTE [DartPostCObjectFnType] was originally [*mut DartCObject] but I changed it to [*mut c_void]
|
||||
// because cannot automatically generate things related to [DartCObject]. Anyway this works fine.
|
||||
// NOTE please sync [DUMMY_WIRE_CODE_FOR_BINDGEN] and [EXTRA_EXTERN_FUNC_NAMES]
|
||||
pub const DUMMY_WIRE_CODE_FOR_BINDGEN: &str = r#"
|
||||
// ----------- DUMMY CODE FOR BINDGEN ----------
|
||||
|
||||
// copied from: allo-isolate
|
||||
pub type DartPort = i64;
|
||||
pub type DartPostCObjectFnType = unsafe extern "C" fn(port_id: DartPort, message: *mut std::ffi::c_void) -> bool;
|
||||
#[no_mangle] pub unsafe extern "C" fn store_dart_post_cobject(ptr: DartPostCObjectFnType) { panic!("dummy code") }
|
||||
|
||||
// copied from: frb_rust::support.rs
|
||||
#[repr(C)]
|
||||
pub struct WireSyncReturnStruct {
|
||||
pub ptr: *mut u8,
|
||||
pub len: i32,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
"#;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref EXTRA_EXTERN_FUNC_NAMES: Vec<String> =
|
||||
vec!["store_dart_post_cobject".to_string()];
|
||||
}
|
||||
|
||||
pub const CODE_HEADER: &str = "// AUTO GENERATED FILE, DO NOT EDIT.
|
||||
// Generated by `flutter_rust_bridge`.";
|
||||
|
||||
pub fn modify_dart_wire_content(content_raw: &str, dart_wire_class_name: &str) -> String {
|
||||
let content = content_raw.replace(
|
||||
&format!("class {} {{", dart_wire_class_name),
|
||||
&format!(
|
||||
"class {} implements FlutterRustBridgeWireBase {{",
|
||||
dart_wire_class_name
|
||||
),
|
||||
);
|
||||
|
||||
let content = RegexBuilder::new("class WireSyncReturnStruct extends ffi.Struct \\{.+?\\}")
|
||||
.multi_line(true)
|
||||
.dot_matches_new_line(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
.replace(&content, "");
|
||||
|
||||
content.to_string()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DartBasicCode {
|
||||
pub import: String,
|
||||
pub part: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
impl Add for &DartBasicCode {
|
||||
type Output = DartBasicCode;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
DartBasicCode {
|
||||
import: format!("{}\n{}", self.import, rhs.import),
|
||||
part: format!("{}\n{}", self.part, rhs.part),
|
||||
body: format!("{}\n{}", self.body, rhs.body),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&DartBasicCode> for DartBasicCode {
|
||||
type Output = DartBasicCode;
|
||||
|
||||
fn add(self, rhs: &DartBasicCode) -> Self::Output {
|
||||
(&self).add(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl DartBasicCode {
|
||||
pub fn to_text(&self) -> String {
|
||||
format!("{}\n{}\n{}", self.import, self.part, self.body)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_dart_wire_content(content: &str) -> DartBasicCode {
|
||||
let (mut imports, mut body) = (Vec::new(), Vec::new());
|
||||
for line in content.split('\n') {
|
||||
(if line.starts_with("import ") {
|
||||
&mut imports
|
||||
} else {
|
||||
&mut body
|
||||
})
|
||||
.push(line);
|
||||
}
|
||||
DartBasicCode {
|
||||
import: imports.join("\n"),
|
||||
part: "".to_string(),
|
||||
body: body.join("\n"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sanity_check(
|
||||
generated_dart_wire_code: &str,
|
||||
dart_wire_class_name: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
if !generated_dart_wire_code.contains(dart_wire_class_name) {
|
||||
return Err(crate::error::Error::str(
|
||||
"Nothing is generated for dart wire class. \
|
||||
Maybe you forget to put code like `mod the_generated_bridge_code;` to your `lib.rs`?",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_add_mod_to_lib(rust_crate_dir: &str, rust_output_path: &str) {
|
||||
if let Err(e) = auto_add_mod_to_lib_core(rust_crate_dir, rust_output_path) {
|
||||
warn!(
|
||||
"auto_add_mod_to_lib fail, the generated code may or may not have problems. \
|
||||
Please ensure you have add code like `mod the_generated_bridge_code;` to your `lib.rs`. \
|
||||
Details: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auto_add_mod_to_lib_core(rust_crate_dir: &str, rust_output_path: &str) -> Result<()> {
|
||||
let path_src_folder = Path::new(rust_crate_dir).join("src");
|
||||
let rust_output_path_relative_to_src_folder =
|
||||
diff_paths(rust_output_path, path_src_folder.clone()).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"rust_output_path={} is unrelated to path_src_folder={:?}",
|
||||
rust_output_path,
|
||||
&path_src_folder,
|
||||
)
|
||||
})?;
|
||||
|
||||
let mod_name = rust_output_path_relative_to_src_folder
|
||||
.file_stem()
|
||||
.ok_or_else(|| anyhow!(""))?
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!(""))?
|
||||
.to_string()
|
||||
.replace('/', "::");
|
||||
let expect_code = format!("mod {};", mod_name);
|
||||
|
||||
let path_lib_rs = path_src_folder.join("lib.rs");
|
||||
|
||||
let raw_content_lib_rs = fs::read_to_string(path_lib_rs.clone())?;
|
||||
if !raw_content_lib_rs.contains(&expect_code) {
|
||||
info!("Inject `{}` into {:?}", &expect_code, &path_lib_rs);
|
||||
|
||||
let comments = " /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */";
|
||||
let modified_content_lib_rs =
|
||||
format!("{}{}\n{}", expect_code, comments, raw_content_lib_rs);
|
||||
|
||||
fs::write(&path_lib_rs, modified_content_lib_rs).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
353
libs/flutter_rust_bridge_codegen/src/parser/mod.rs
Normal file
353
libs/flutter_rust_bridge_codegen/src/parser/mod.rs
Normal file
@@ -0,0 +1,353 @@
|
||||
mod ty;
|
||||
|
||||
use std::string::String;
|
||||
|
||||
use log::debug;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::*;
|
||||
|
||||
use crate::ir::*;
|
||||
|
||||
use crate::generator::rust::HANDLER_NAME;
|
||||
use crate::parser::ty::TypeParser;
|
||||
use crate::source_graph::Crate;
|
||||
|
||||
const STREAM_SINK_IDENT: &str = "StreamSink";
|
||||
const RESULT_IDENT: &str = "Result";
|
||||
|
||||
pub fn parse(source_rust_content: &str, file: File, manifest_path: &str) -> IrFile {
|
||||
let crate_map = Crate::new(manifest_path);
|
||||
|
||||
let src_fns = extract_fns_from_file(&file);
|
||||
let src_structs = crate_map.root_module.collect_structs_to_vec();
|
||||
let src_enums = crate_map.root_module.collect_enums_to_vec();
|
||||
|
||||
let parser = Parser::new(TypeParser::new(src_structs, src_enums));
|
||||
parser.parse(source_rust_content, src_fns)
|
||||
}
|
||||
|
||||
struct Parser<'a> {
|
||||
type_parser: TypeParser<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub fn new(type_parser: TypeParser<'a>) -> Self {
|
||||
Parser { type_parser }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
fn parse(mut self, source_rust_content: &str, src_fns: Vec<&ItemFn>) -> IrFile {
|
||||
let funcs = src_fns.iter().map(|f| self.parse_function(f)).collect();
|
||||
|
||||
let has_executor = source_rust_content.contains(HANDLER_NAME);
|
||||
|
||||
let (struct_pool, enum_pool) = self.type_parser.consume();
|
||||
|
||||
IrFile {
|
||||
funcs,
|
||||
struct_pool,
|
||||
enum_pool,
|
||||
has_executor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to parse the type from the return part of a function signature. There is a special
|
||||
/// case for top-level `Result` types.
|
||||
pub fn try_parse_fn_output_type(&mut self, ty: &syn::Type) -> Option<IrFuncOutput> {
|
||||
let inner = ty::SupportedInnerType::try_from_syn_type(ty)?;
|
||||
|
||||
match inner {
|
||||
ty::SupportedInnerType::Path(ty::SupportedPathType {
|
||||
ident,
|
||||
generic: Some(generic),
|
||||
}) if ident == RESULT_IDENT => Some(IrFuncOutput::ResultType(
|
||||
self.type_parser.convert_to_ir_type(*generic)?,
|
||||
)),
|
||||
_ => Some(IrFuncOutput::Type(
|
||||
self.type_parser.convert_to_ir_type(inner)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to parse the type from an argument of a function signature. There is a special
|
||||
/// case for top-level `StreamSink` types.
|
||||
pub fn try_parse_fn_arg_type(&mut self, ty: &syn::Type) -> Option<IrFuncArg> {
|
||||
match ty {
|
||||
syn::Type::Path(syn::TypePath { path, .. }) => {
|
||||
let last_segment = path.segments.last().unwrap();
|
||||
if last_segment.ident == STREAM_SINK_IDENT {
|
||||
match &last_segment.arguments {
|
||||
syn::PathArguments::AngleBracketed(
|
||||
syn::AngleBracketedGenericArguments { args, .. },
|
||||
) if args.len() == 1 => {
|
||||
// Unwrap is safe here because args.len() == 1
|
||||
match args.last().unwrap() {
|
||||
syn::GenericArgument::Type(t) => {
|
||||
Some(IrFuncArg::StreamSinkType(self.type_parser.parse_type(t)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
Some(IrFuncArg::Type(self.type_parser.parse_type(ty)))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_function(&mut self, func: &ItemFn) -> IrFunc {
|
||||
debug!("parse_function function name: {:?}", func.sig.ident);
|
||||
|
||||
let sig = &func.sig;
|
||||
let func_name = sig.ident.to_string();
|
||||
|
||||
let mut inputs = Vec::new();
|
||||
let mut output = None;
|
||||
let mut mode = None;
|
||||
let mut fallible = true;
|
||||
|
||||
for sig_input in &sig.inputs {
|
||||
if let FnArg::Typed(ref pat_type) = sig_input {
|
||||
let name = if let Pat::Ident(ref pat_ident) = *pat_type.pat {
|
||||
format!("{}", pat_ident.ident)
|
||||
} else {
|
||||
panic!("unexpected pat_type={:?}", pat_type)
|
||||
};
|
||||
|
||||
match self.try_parse_fn_arg_type(&pat_type.ty).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to parse function argument type `{}`",
|
||||
type_to_string(&pat_type.ty)
|
||||
)
|
||||
}) {
|
||||
IrFuncArg::StreamSinkType(ty) => {
|
||||
output = Some(ty);
|
||||
mode = Some(IrFuncMode::Stream);
|
||||
}
|
||||
IrFuncArg::Type(ty) => {
|
||||
inputs.push(IrField {
|
||||
name: IrIdent::new(name),
|
||||
ty,
|
||||
is_final: true,
|
||||
comments: extract_comments(&pat_type.attrs),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("unexpected sig_input={:?}", sig_input);
|
||||
}
|
||||
}
|
||||
|
||||
if output.is_none() {
|
||||
output = Some(match &sig.output {
|
||||
ReturnType::Type(_, ty) => {
|
||||
match self.try_parse_fn_output_type(ty).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to parse function output type `{}`",
|
||||
type_to_string(ty)
|
||||
)
|
||||
}) {
|
||||
IrFuncOutput::ResultType(ty) => ty,
|
||||
IrFuncOutput::Type(ty) => {
|
||||
fallible = false;
|
||||
ty
|
||||
}
|
||||
}
|
||||
}
|
||||
ReturnType::Default => {
|
||||
fallible = false;
|
||||
IrType::Primitive(IrTypePrimitive::Unit)
|
||||
}
|
||||
});
|
||||
mode = Some(
|
||||
if let Some(IrType::Delegate(IrTypeDelegate::SyncReturnVecU8)) = output {
|
||||
IrFuncMode::Sync
|
||||
} else {
|
||||
IrFuncMode::Normal
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// let comments = func.attrs.iter().filter_map(extract_comments).collect();
|
||||
|
||||
IrFunc {
|
||||
name: func_name,
|
||||
inputs,
|
||||
output: output.expect("unsupported output"),
|
||||
fallible,
|
||||
mode: mode.expect("unsupported mode"),
|
||||
comments: extract_comments(&func.attrs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_fns_from_file(file: &File) -> Vec<&ItemFn> {
|
||||
let mut src_fns = Vec::new();
|
||||
|
||||
for item in file.items.iter() {
|
||||
if let Item::Fn(ref item_fn) = item {
|
||||
if let Visibility::Public(_) = &item_fn.vis {
|
||||
src_fns.push(item_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
src_fns
|
||||
}
|
||||
|
||||
fn extract_comments(attrs: &[Attribute]) -> Vec<IrComment> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter_map(|attr| match attr.parse_meta() {
|
||||
Ok(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
lit: Lit::Str(lit),
|
||||
..
|
||||
})) if path.is_ident("doc") => Some(IrComment::from(lit.value().as_ref())),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub mod frb_keyword {
|
||||
syn::custom_keyword!(mirror);
|
||||
syn::custom_keyword!(non_final);
|
||||
syn::custom_keyword!(dart_metadata);
|
||||
syn::custom_keyword!(import);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NamedOption<K, V> {
|
||||
pub name: K,
|
||||
pub value: V,
|
||||
}
|
||||
|
||||
impl<K: Parse + std::fmt::Debug, V: Parse> Parse for NamedOption<K, V> {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let name: K = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let value = input.parse()?;
|
||||
Ok(Self { name, value })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MirrorOption(Path);
|
||||
|
||||
impl Parse for MirrorOption {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
let path: Path = content.parse()?;
|
||||
Ok(Self(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MetadataAnnotations(Vec<IrDartAnnotation>);
|
||||
|
||||
impl Parse for IrDartAnnotation {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let annotation: LitStr = input.parse()?;
|
||||
let library = if input.peek(frb_keyword::import) {
|
||||
let _ = input.parse::<frb_keyword::import>()?;
|
||||
let library: IrDartImport = input.parse()?;
|
||||
Some(library)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
content: annotation.value(),
|
||||
library,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Parse for MetadataAnnotations {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
let annotations =
|
||||
Punctuated::<IrDartAnnotation, syn::Token![,]>::parse_terminated(&content)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok(Self(annotations))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DartImports(Vec<IrDartImport>);
|
||||
|
||||
impl Parse for IrDartImport {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let uri: LitStr = input.parse()?;
|
||||
let alias: Option<String> = if input.peek(token::As) {
|
||||
let _ = input.parse::<token::As>()?;
|
||||
let alias: Ident = input.parse()?;
|
||||
Some(alias.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
uri: uri.value(),
|
||||
alias,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Parse for DartImports {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
let imports = Punctuated::<IrDartImport, syn::Token![,]>::parse_terminated(&content)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok(Self(imports))
|
||||
}
|
||||
}
|
||||
|
||||
enum FrbOption {
|
||||
Mirror(MirrorOption),
|
||||
NonFinal,
|
||||
Metadata(NamedOption<frb_keyword::dart_metadata, MetadataAnnotations>),
|
||||
}
|
||||
|
||||
impl Parse for FrbOption {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(frb_keyword::mirror) {
|
||||
input.parse().map(FrbOption::Mirror)
|
||||
} else if lookahead.peek(frb_keyword::non_final) {
|
||||
input
|
||||
.parse::<frb_keyword::non_final>()
|
||||
.map(|_| FrbOption::NonFinal)
|
||||
} else if lookahead.peek(frb_keyword::dart_metadata) {
|
||||
input.parse().map(FrbOption::Metadata)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn extract_metadata(attrs: &[Attribute]) -> Vec<IrDartAnnotation> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.is_ident("frb"))
|
||||
.map(|attr| attr.parse_args::<FrbOption>())
|
||||
.flat_map(|frb_option| match frb_option {
|
||||
Ok(FrbOption::Metadata(NamedOption {
|
||||
name: _,
|
||||
value: MetadataAnnotations(annotations),
|
||||
})) => annotations,
|
||||
_ => vec![],
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// syn -> string https://github.com/dtolnay/syn/issues/294
|
||||
fn type_to_string(ty: &Type) -> String {
|
||||
quote!(#ty).to_string().replace(' ', "")
|
||||
}
|
||||
392
libs/flutter_rust_bridge_codegen/src/parser/ty.rs
Normal file
392
libs/flutter_rust_bridge_codegen/src/parser/ty.rs
Normal file
@@ -0,0 +1,392 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::string::String;
|
||||
|
||||
use syn::*;
|
||||
|
||||
use crate::ir::IrType::*;
|
||||
use crate::ir::*;
|
||||
|
||||
use crate::markers;
|
||||
|
||||
use crate::source_graph::{Enum, Struct};
|
||||
|
||||
use crate::parser::{extract_comments, extract_metadata, type_to_string};
|
||||
|
||||
pub struct TypeParser<'a> {
|
||||
src_structs: HashMap<String, &'a Struct>,
|
||||
src_enums: HashMap<String, &'a Enum>,
|
||||
|
||||
parsing_or_parsed_struct_names: HashSet<String>,
|
||||
struct_pool: IrStructPool,
|
||||
|
||||
parsed_enums: HashSet<String>,
|
||||
enum_pool: IrEnumPool,
|
||||
}
|
||||
|
||||
impl<'a> TypeParser<'a> {
|
||||
pub fn new(
|
||||
src_structs: HashMap<String, &'a Struct>,
|
||||
src_enums: HashMap<String, &'a Enum>,
|
||||
) -> Self {
|
||||
TypeParser {
|
||||
src_structs,
|
||||
src_enums,
|
||||
struct_pool: HashMap::new(),
|
||||
enum_pool: HashMap::new(),
|
||||
parsing_or_parsed_struct_names: HashSet::new(),
|
||||
parsed_enums: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(self) -> (IrStructPool, IrEnumPool) {
|
||||
(self.struct_pool, self.enum_pool)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic intermediate representation of a type that can appear inside a function signature.
|
||||
#[derive(Debug)]
|
||||
pub enum SupportedInnerType {
|
||||
/// Path types with up to 1 generic type argument on the final segment. All segments before
|
||||
/// the last segment are ignored. The generic type argument must also be a valid
|
||||
/// `SupportedInnerType`.
|
||||
Path(SupportedPathType),
|
||||
/// Array type
|
||||
Array(Box<Self>, usize),
|
||||
/// The unit type `()`.
|
||||
Unit,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SupportedInnerType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Path(p) => write!(f, "{}", p),
|
||||
Self::Array(u, len) => write!(f, "[{}; {}]", u, len),
|
||||
Self::Unit => write!(f, "()"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a named type, with an optional path and up to 1 generic type argument.
|
||||
#[derive(Debug)]
|
||||
pub struct SupportedPathType {
|
||||
pub ident: syn::Ident,
|
||||
pub generic: Option<Box<SupportedInnerType>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SupportedPathType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let ident = self.ident.to_string();
|
||||
if let Some(generic) = &self.generic {
|
||||
write!(f, "{}<{}>", ident, generic)
|
||||
} else {
|
||||
write!(f, "{}", ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SupportedInnerType {
|
||||
/// Given a `syn::Type`, returns a simplified representation of the type if it's supported,
|
||||
/// or `None` otherwise.
|
||||
pub fn try_from_syn_type(ty: &syn::Type) -> Option<Self> {
|
||||
match ty {
|
||||
syn::Type::Path(syn::TypePath { path, .. }) => {
|
||||
let last_segment = path.segments.last().unwrap().clone();
|
||||
match last_segment.arguments {
|
||||
syn::PathArguments::None => Some(SupportedInnerType::Path(SupportedPathType {
|
||||
ident: last_segment.ident,
|
||||
generic: None,
|
||||
})),
|
||||
syn::PathArguments::AngleBracketed(a) => {
|
||||
let generic = match a.args.into_iter().next() {
|
||||
Some(syn::GenericArgument::Type(t)) => {
|
||||
Some(Box::new(SupportedInnerType::try_from_syn_type(&t)?))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Some(SupportedInnerType::Path(SupportedPathType {
|
||||
ident: last_segment.ident,
|
||||
generic,
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
syn::Type::Array(syn::TypeArray { elem, len, .. }) => {
|
||||
let len: usize = match len {
|
||||
syn::Expr::Lit(lit) => match &lit.lit {
|
||||
syn::Lit::Int(x) => x.base10_parse().unwrap(),
|
||||
_ => panic!("Cannot parse array length"),
|
||||
},
|
||||
_ => panic!("Cannot parse array length"),
|
||||
};
|
||||
Some(SupportedInnerType::Array(
|
||||
Box::new(SupportedInnerType::try_from_syn_type(elem)?),
|
||||
len,
|
||||
))
|
||||
}
|
||||
syn::Type::Tuple(syn::TypeTuple { elems, .. }) if elems.is_empty() => {
|
||||
Some(SupportedInnerType::Unit)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TypeParser<'a> {
|
||||
pub fn parse_type(&mut self, ty: &syn::Type) -> IrType {
|
||||
let supported_type = SupportedInnerType::try_from_syn_type(ty)
|
||||
.unwrap_or_else(|| panic!("Unsupported type `{}`", type_to_string(ty)));
|
||||
|
||||
self.convert_to_ir_type(supported_type)
|
||||
.unwrap_or_else(|| panic!("parse_type failed for ty={}", type_to_string(ty)))
|
||||
}
|
||||
|
||||
/// Converts an inner type into an `IrType` if possible.
|
||||
pub fn convert_to_ir_type(&mut self, ty: SupportedInnerType) -> Option<IrType> {
|
||||
match ty {
|
||||
SupportedInnerType::Path(p) => self.convert_path_to_ir_type(p),
|
||||
SupportedInnerType::Array(p, len) => self.convert_array_to_ir_type(*p, len),
|
||||
SupportedInnerType::Unit => Some(IrType::Primitive(IrTypePrimitive::Unit)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an array type into an `IrType` if possible.
|
||||
pub fn convert_array_to_ir_type(
|
||||
&mut self,
|
||||
generic: SupportedInnerType,
|
||||
_len: usize,
|
||||
) -> Option<IrType> {
|
||||
self.convert_to_ir_type(generic).map(|inner| match inner {
|
||||
Primitive(primitive) => PrimitiveList(IrTypePrimitiveList { primitive }),
|
||||
others => GeneralList(IrTypeGeneralList {
|
||||
inner: Box::new(others),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts a path type into an `IrType` if possible.
|
||||
pub fn convert_path_to_ir_type(&mut self, p: SupportedPathType) -> Option<IrType> {
|
||||
let p_as_str = format!("{}", &p);
|
||||
let ident_string = &p.ident.to_string();
|
||||
if let Some(generic) = p.generic {
|
||||
match ident_string.as_str() {
|
||||
"SyncReturn" => {
|
||||
// Special-case SyncReturn<Vec<u8>>. SyncReturn for any other type is not
|
||||
// supported.
|
||||
match *generic {
|
||||
SupportedInnerType::Path(SupportedPathType {
|
||||
ident,
|
||||
generic: Some(generic),
|
||||
}) if ident == "Vec" => match *generic {
|
||||
SupportedInnerType::Path(SupportedPathType {
|
||||
ident,
|
||||
generic: None,
|
||||
}) if ident == "u8" => {
|
||||
Some(IrType::Delegate(IrTypeDelegate::SyncReturnVecU8))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
"Vec" => {
|
||||
// Special-case Vec<String> as StringList
|
||||
if matches!(*generic, SupportedInnerType::Path(SupportedPathType { ref ident, .. }) if ident == "String")
|
||||
{
|
||||
Some(IrType::Delegate(IrTypeDelegate::StringList))
|
||||
} else {
|
||||
self.convert_to_ir_type(*generic).map(|inner| match inner {
|
||||
Primitive(primitive) => {
|
||||
PrimitiveList(IrTypePrimitiveList { primitive })
|
||||
}
|
||||
others => GeneralList(IrTypeGeneralList {
|
||||
inner: Box::new(others),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
"ZeroCopyBuffer" => {
|
||||
let inner = self.convert_to_ir_type(*generic);
|
||||
if let Some(IrType::PrimitiveList(IrTypePrimitiveList { primitive })) = inner {
|
||||
Some(IrType::Delegate(
|
||||
IrTypeDelegate::ZeroCopyBufferVecPrimitive(primitive),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
"Box" => self.convert_to_ir_type(*generic).map(|inner| {
|
||||
Boxed(IrTypeBoxed {
|
||||
exist_in_real_api: true,
|
||||
inner: Box::new(inner),
|
||||
})
|
||||
}),
|
||||
"Option" => {
|
||||
// Disallow nested Option
|
||||
if matches!(*generic, SupportedInnerType::Path(SupportedPathType { ref ident, .. }) if ident == "Option")
|
||||
{
|
||||
panic!(
|
||||
"Nested optionals without indirection are not supported. (Option<Option<{}>>)",
|
||||
p_as_str
|
||||
);
|
||||
}
|
||||
self.convert_to_ir_type(*generic).map(|inner| match inner {
|
||||
Primitive(prim) => IrType::Optional(IrTypeOptional::new_prim(prim)),
|
||||
st @ StructRef(_) => {
|
||||
IrType::Optional(IrTypeOptional::new_ptr(Boxed(IrTypeBoxed {
|
||||
inner: Box::new(st),
|
||||
exist_in_real_api: false,
|
||||
})))
|
||||
}
|
||||
other => IrType::Optional(IrTypeOptional::new_ptr(other)),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
IrTypePrimitive::try_from_rust_str(ident_string)
|
||||
.map(Primitive)
|
||||
.or_else(|| {
|
||||
if ident_string == "String" {
|
||||
Some(IrType::Delegate(IrTypeDelegate::String))
|
||||
} else if self.src_structs.contains_key(ident_string) {
|
||||
if !self.parsing_or_parsed_struct_names.contains(ident_string) {
|
||||
self.parsing_or_parsed_struct_names
|
||||
.insert(ident_string.to_owned());
|
||||
let api_struct = self.parse_struct_core(&p.ident);
|
||||
self.struct_pool.insert(ident_string.to_owned(), api_struct);
|
||||
}
|
||||
|
||||
Some(StructRef(IrTypeStructRef {
|
||||
name: ident_string.to_owned(),
|
||||
freezed: self
|
||||
.struct_pool
|
||||
.get(ident_string)
|
||||
.map(IrStruct::using_freezed)
|
||||
.unwrap_or(false),
|
||||
}))
|
||||
} else if self.src_enums.contains_key(ident_string) {
|
||||
if self.parsed_enums.insert(ident_string.to_owned()) {
|
||||
let enu = self.parse_enum_core(&p.ident);
|
||||
self.enum_pool.insert(ident_string.to_owned(), enu);
|
||||
}
|
||||
|
||||
Some(EnumRef(IrTypeEnumRef {
|
||||
name: ident_string.to_owned(),
|
||||
is_struct: self
|
||||
.enum_pool
|
||||
.get(ident_string)
|
||||
.map(IrEnum::is_struct)
|
||||
.unwrap_or(true),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TypeParser<'a> {
|
||||
fn parse_enum_core(&mut self, ident: &syn::Ident) -> IrEnum {
|
||||
let src_enum = self.src_enums[&ident.to_string()];
|
||||
let name = src_enum.ident.to_string();
|
||||
let wrapper_name = if src_enum.mirror {
|
||||
Some(format!("mirror_{}", name))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let path = src_enum.path.clone();
|
||||
let comments = extract_comments(&src_enum.src.attrs);
|
||||
let variants = src_enum
|
||||
.src
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant| IrVariant {
|
||||
name: IrIdent::new(variant.ident.to_string()),
|
||||
comments: extract_comments(&variant.attrs),
|
||||
kind: match variant.fields.iter().next() {
|
||||
None => IrVariantKind::Value,
|
||||
Some(Field {
|
||||
attrs,
|
||||
ident: field_ident,
|
||||
..
|
||||
}) => {
|
||||
let variant_ident = variant.ident.to_string();
|
||||
IrVariantKind::Struct(IrStruct {
|
||||
name: variant_ident,
|
||||
wrapper_name: None,
|
||||
path: None,
|
||||
is_fields_named: field_ident.is_some(),
|
||||
dart_metadata: extract_metadata(attrs),
|
||||
comments: extract_comments(attrs),
|
||||
fields: variant
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, field)| IrField {
|
||||
name: IrIdent::new(
|
||||
field
|
||||
.ident
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(|| format!("field{}", idx)),
|
||||
),
|
||||
ty: self.parse_type(&field.ty),
|
||||
is_final: true,
|
||||
comments: extract_comments(&field.attrs),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
IrEnum::new(name, wrapper_name, path, comments, variants)
|
||||
}
|
||||
|
||||
fn parse_struct_core(&mut self, ident: &syn::Ident) -> IrStruct {
|
||||
let src_struct = self.src_structs[&ident.to_string()];
|
||||
let mut fields = Vec::new();
|
||||
|
||||
let (is_fields_named, struct_fields) = match &src_struct.src.fields {
|
||||
Fields::Named(FieldsNamed { named, .. }) => (true, named),
|
||||
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => (false, unnamed),
|
||||
_ => panic!("unsupported type: {:?}", src_struct.src.fields),
|
||||
};
|
||||
|
||||
for (idx, field) in struct_fields.iter().enumerate() {
|
||||
let field_name = field
|
||||
.ident
|
||||
.as_ref()
|
||||
.map_or(format!("field{}", idx), ToString::to_string);
|
||||
let field_type = self.parse_type(&field.ty);
|
||||
fields.push(IrField {
|
||||
name: IrIdent::new(field_name),
|
||||
ty: field_type,
|
||||
is_final: !markers::has_non_final(&field.attrs),
|
||||
comments: extract_comments(&field.attrs),
|
||||
});
|
||||
}
|
||||
|
||||
let name = src_struct.ident.to_string();
|
||||
let wrapper_name = if src_struct.mirror {
|
||||
Some(format!("mirror_{}", name))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let path = Some(src_struct.path.clone());
|
||||
let metadata = extract_metadata(&src_struct.src.attrs);
|
||||
let comments = extract_comments(&src_struct.src.attrs);
|
||||
IrStruct {
|
||||
name,
|
||||
wrapper_name,
|
||||
path,
|
||||
fields,
|
||||
is_fields_named,
|
||||
dart_metadata: metadata,
|
||||
comments,
|
||||
}
|
||||
}
|
||||
}
|
||||
553
libs/flutter_rust_bridge_codegen/src/source_graph.rs
Normal file
553
libs/flutter_rust_bridge_codegen/src/source_graph.rs
Normal file
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
Things this doesn't currently support that it might need to later:
|
||||
|
||||
- Import parsing is unfinished and so is currently disabled
|
||||
- When import parsing is enabled:
|
||||
- Import renames (use a::b as c) - these are silently ignored
|
||||
- Imports that start with two colons (use ::a::b) - these are also silently ignored
|
||||
*/
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, fs, path::PathBuf};
|
||||
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use log::{debug, warn};
|
||||
use syn::{Attribute, Ident, ItemEnum, ItemStruct, UseTree};
|
||||
|
||||
use crate::markers;
|
||||
|
||||
/// Represents a crate, including a map of its modules, imports, structs and
|
||||
/// enums.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Crate {
|
||||
pub name: String,
|
||||
pub manifest_path: PathBuf,
|
||||
pub root_src_file: PathBuf,
|
||||
pub root_module: Module,
|
||||
}
|
||||
|
||||
impl Crate {
|
||||
pub fn new(manifest_path: &str) -> Self {
|
||||
let mut cmd = MetadataCommand::new();
|
||||
cmd.manifest_path(&manifest_path);
|
||||
|
||||
let metadata = cmd.exec().unwrap();
|
||||
|
||||
let root_package = metadata.root_package().unwrap();
|
||||
let root_src_file = {
|
||||
let lib_file = root_package
|
||||
.manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("src/lib.rs");
|
||||
let main_file = root_package
|
||||
.manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("src/main.rs");
|
||||
|
||||
if lib_file.exists() {
|
||||
fs::canonicalize(lib_file).unwrap()
|
||||
} else if main_file.exists() {
|
||||
fs::canonicalize(main_file).unwrap()
|
||||
} else {
|
||||
panic!("No src/lib.rs or src/main.rs found for this Cargo.toml file");
|
||||
}
|
||||
};
|
||||
|
||||
let source_rust_content = fs::read_to_string(&root_src_file).unwrap();
|
||||
let file_ast = syn::parse_file(&source_rust_content).unwrap();
|
||||
|
||||
let mut result = Crate {
|
||||
name: root_package.name.clone(),
|
||||
manifest_path: fs::canonicalize(manifest_path).unwrap(),
|
||||
root_src_file: root_src_file.clone(),
|
||||
root_module: Module {
|
||||
visibility: Visibility::Public,
|
||||
file_path: root_src_file,
|
||||
module_path: vec!["crate".to_string()],
|
||||
source: Some(ModuleSource::File(file_ast)),
|
||||
scope: None,
|
||||
},
|
||||
};
|
||||
|
||||
result.resolve();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Create a map of the modules for this crate
|
||||
pub fn resolve(&mut self) {
|
||||
self.root_module.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors syn::Visibility, but can be created without a token
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Visibility {
|
||||
Public,
|
||||
Crate,
|
||||
Restricted, // Not supported
|
||||
Inherited, // Usually means private
|
||||
}
|
||||
|
||||
fn syn_vis_to_visibility(vis: &syn::Visibility) -> Visibility {
|
||||
match vis {
|
||||
syn::Visibility::Public(_) => Visibility::Public,
|
||||
syn::Visibility::Crate(_) => Visibility::Crate,
|
||||
syn::Visibility::Restricted(_) => Visibility::Restricted,
|
||||
syn::Visibility::Inherited => Visibility::Inherited,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Import {
|
||||
pub path: Vec<String>,
|
||||
pub visibility: Visibility,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ModuleSource {
|
||||
File(syn::File),
|
||||
ModuleInFile(Vec<syn::Item>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Struct {
|
||||
pub ident: Ident,
|
||||
pub src: ItemStruct,
|
||||
pub visibility: Visibility,
|
||||
pub path: Vec<String>,
|
||||
pub mirror: bool,
|
||||
}
|
||||
|
||||
impl Debug for Struct {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Struct")
|
||||
.field("ident", &self.ident)
|
||||
.field("src", &"omitted")
|
||||
.field("visibility", &self.visibility)
|
||||
.field("path", &self.path)
|
||||
.field("mirror", &self.mirror)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Enum {
|
||||
pub ident: Ident,
|
||||
pub src: ItemEnum,
|
||||
pub visibility: Visibility,
|
||||
pub path: Vec<String>,
|
||||
pub mirror: bool,
|
||||
}
|
||||
|
||||
impl Debug for Enum {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Enum")
|
||||
.field("ident", &self.ident)
|
||||
.field("src", &"omitted")
|
||||
.field("visibility", &self.visibility)
|
||||
.field("path", &self.path)
|
||||
.field("mirror", &self.mirror)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleScope {
|
||||
pub modules: Vec<Module>,
|
||||
pub enums: Vec<Enum>,
|
||||
pub structs: Vec<Struct>,
|
||||
pub imports: Vec<Import>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Module {
|
||||
pub visibility: Visibility,
|
||||
pub file_path: PathBuf,
|
||||
pub module_path: Vec<String>,
|
||||
pub source: Option<ModuleSource>,
|
||||
pub scope: Option<ModuleScope>,
|
||||
}
|
||||
|
||||
impl Debug for Module {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Module")
|
||||
.field("visibility", &self.visibility)
|
||||
.field("module_path", &self.module_path)
|
||||
.field("file_path", &self.file_path)
|
||||
.field("source", &"omitted")
|
||||
.field("scope", &self.scope)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a struct or enum ident, possibly remapped by a mirror marker
|
||||
fn get_ident(ident: &Ident, attrs: &[Attribute]) -> (Ident, bool) {
|
||||
markers::extract_mirror_marker(attrs)
|
||||
.and_then(|path| path.get_ident().map(|ident| (ident.clone(), true)))
|
||||
.unwrap_or_else(|| (ident.clone(), false))
|
||||
}
|
||||
|
||||
impl Module {
|
||||
pub fn resolve(&mut self) {
|
||||
self.resolve_modules();
|
||||
// self.resolve_imports();
|
||||
}
|
||||
|
||||
/// Maps out modules, structs and enums within the scope of this module
|
||||
fn resolve_modules(&mut self) {
|
||||
let mut scope_modules = Vec::new();
|
||||
let mut scope_structs = Vec::new();
|
||||
let mut scope_enums = Vec::new();
|
||||
|
||||
let items = match self.source.as_ref().unwrap() {
|
||||
ModuleSource::File(file) => &file.items,
|
||||
ModuleSource::ModuleInFile(items) => items,
|
||||
};
|
||||
|
||||
for item in items.iter() {
|
||||
match item {
|
||||
syn::Item::Struct(item_struct) => {
|
||||
let (ident, mirror) = get_ident(&item_struct.ident, &item_struct.attrs);
|
||||
let ident_str = ident.to_string();
|
||||
scope_structs.push(Struct {
|
||||
ident,
|
||||
src: item_struct.clone(),
|
||||
visibility: syn_vis_to_visibility(&item_struct.vis),
|
||||
path: {
|
||||
let mut path = self.module_path.clone();
|
||||
path.push(ident_str);
|
||||
path
|
||||
},
|
||||
mirror,
|
||||
});
|
||||
}
|
||||
syn::Item::Enum(item_enum) => {
|
||||
let (ident, mirror) = get_ident(&item_enum.ident, &item_enum.attrs);
|
||||
let ident_str = ident.to_string();
|
||||
scope_enums.push(Enum {
|
||||
ident,
|
||||
src: item_enum.clone(),
|
||||
visibility: syn_vis_to_visibility(&item_enum.vis),
|
||||
path: {
|
||||
let mut path = self.module_path.clone();
|
||||
path.push(ident_str);
|
||||
path
|
||||
},
|
||||
mirror,
|
||||
});
|
||||
}
|
||||
syn::Item::Mod(item_mod) => {
|
||||
let ident = item_mod.ident.clone();
|
||||
|
||||
let mut module_path = self.module_path.clone();
|
||||
module_path.push(ident.to_string());
|
||||
|
||||
scope_modules.push(match &item_mod.content {
|
||||
Some(content) => {
|
||||
let mut child_module = Module {
|
||||
visibility: syn_vis_to_visibility(&item_mod.vis),
|
||||
file_path: self.file_path.clone(),
|
||||
module_path,
|
||||
source: Some(ModuleSource::ModuleInFile(content.1.clone())),
|
||||
scope: None,
|
||||
};
|
||||
|
||||
child_module.resolve();
|
||||
|
||||
child_module
|
||||
}
|
||||
None => {
|
||||
let folder_path =
|
||||
self.file_path.parent().unwrap().join(ident.to_string());
|
||||
let folder_exists = folder_path.exists();
|
||||
|
||||
let file_path = if folder_exists {
|
||||
folder_path.join("mod.rs")
|
||||
} else {
|
||||
self.file_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(ident.to_string() + ".rs")
|
||||
};
|
||||
|
||||
let file_exists = file_path.exists();
|
||||
|
||||
if !file_exists {
|
||||
warn!(
|
||||
"Skipping unresolvable module {} (tried {})",
|
||||
&ident,
|
||||
file_path.to_string_lossy()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let source = if file_exists {
|
||||
let source_rust_content = fs::read_to_string(&file_path).unwrap();
|
||||
debug!("Trying to parse {:?}", file_path);
|
||||
Some(ModuleSource::File(
|
||||
syn::parse_file(&source_rust_content).unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut child_module = Module {
|
||||
visibility: syn_vis_to_visibility(&item_mod.vis),
|
||||
file_path,
|
||||
module_path,
|
||||
source,
|
||||
scope: None,
|
||||
};
|
||||
|
||||
if file_exists {
|
||||
child_module.resolve();
|
||||
}
|
||||
|
||||
child_module
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.scope = Some(ModuleScope {
|
||||
modules: scope_modules,
|
||||
enums: scope_enums,
|
||||
structs: scope_structs,
|
||||
imports: vec![], // Will be filled in by resolve_imports()
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn resolve_imports(&mut self) {
|
||||
let imports = &mut self.scope.as_mut().unwrap().imports;
|
||||
|
||||
let items = match self.source.as_ref().unwrap() {
|
||||
ModuleSource::File(file) => &file.items,
|
||||
ModuleSource::ModuleInFile(items) => items,
|
||||
};
|
||||
|
||||
for item in items.iter() {
|
||||
if let syn::Item::Use(item_use) = item {
|
||||
let flattened_imports = flatten_use_tree(&item_use.tree);
|
||||
|
||||
for import in flattened_imports {
|
||||
imports.push(Import {
|
||||
path: import,
|
||||
visibility: syn_vis_to_visibility(&item_use.vis),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_structs<'a>(&'a self, container: &mut HashMap<String, &'a Struct>) {
|
||||
let scope = self.scope.as_ref().unwrap();
|
||||
for scope_struct in &scope.structs {
|
||||
container.insert(scope_struct.ident.to_string(), scope_struct);
|
||||
}
|
||||
for scope_module in &scope.modules {
|
||||
scope_module.collect_structs(container);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_structs_to_vec(&self) -> HashMap<String, &Struct> {
|
||||
let mut ans = HashMap::new();
|
||||
self.collect_structs(&mut ans);
|
||||
ans
|
||||
}
|
||||
|
||||
pub fn collect_enums<'a>(&'a self, container: &mut HashMap<String, &'a Enum>) {
|
||||
let scope = self.scope.as_ref().unwrap();
|
||||
for scope_enum in &scope.enums {
|
||||
container.insert(scope_enum.ident.to_string(), scope_enum);
|
||||
}
|
||||
for scope_module in &scope.modules {
|
||||
scope_module.collect_enums(container);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_enums_to_vec(&self) -> HashMap<String, &Enum> {
|
||||
let mut ans = HashMap::new();
|
||||
self.collect_enums(&mut ans);
|
||||
ans
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_use_tree_rename_abort_warning(use_tree: &UseTree) {
|
||||
debug!("WARNING: flatten_use_tree() found an import rename (use a::b as c). flatten_use_tree() will now abort.");
|
||||
debug!("WARNING: This happened while parsing {:?}", use_tree);
|
||||
debug!("WARNING: This use statement will be ignored.");
|
||||
}
|
||||
|
||||
/// Takes a use tree and returns a flat list of use paths (list of string tokens)
|
||||
///
|
||||
/// Example:
|
||||
/// use a::{b::c, d::e};
|
||||
/// becomes
|
||||
/// [
|
||||
/// ["a", "b", "c"],
|
||||
/// ["a", "d", "e"]
|
||||
/// ]
|
||||
///
|
||||
/// Warning: As of writing, import renames (import a::b as c) are silently
|
||||
/// ignored.
|
||||
fn flatten_use_tree(use_tree: &UseTree) -> Vec<Vec<String>> {
|
||||
// Vec<(path, is_complete)>
|
||||
let mut result = vec![(vec![], false)];
|
||||
|
||||
let mut counter: usize = 0;
|
||||
|
||||
loop {
|
||||
counter += 1;
|
||||
|
||||
if counter > 10000 {
|
||||
panic!("flatten_use_tree: Use statement complexity limit exceeded. This is probably a bug.");
|
||||
}
|
||||
|
||||
// If all paths are complete, break from the loop
|
||||
if result.iter().all(|result_item| result_item.1) {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut items_to_push = Vec::new();
|
||||
|
||||
for path_tuple in &mut result {
|
||||
let path = &mut path_tuple.0;
|
||||
let is_complete = &mut path_tuple.1;
|
||||
|
||||
if *is_complete {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut tree_cursor = use_tree;
|
||||
|
||||
for path_item in path.iter() {
|
||||
match tree_cursor {
|
||||
UseTree::Path(use_path) => {
|
||||
let ident = use_path.ident.to_string();
|
||||
if *path_item != ident {
|
||||
panic!("This ident did not match the one we already collected. This is a bug.");
|
||||
}
|
||||
tree_cursor = use_path.tree.as_ref();
|
||||
}
|
||||
UseTree::Group(use_group) => {
|
||||
let mut moved_tree_cursor = false;
|
||||
|
||||
for tree in use_group.items.iter() {
|
||||
match tree {
|
||||
UseTree::Path(use_path) => {
|
||||
if path_item == &use_path.ident.to_string() {
|
||||
tree_cursor = use_path.tree.as_ref();
|
||||
moved_tree_cursor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Since we're not matching UseTree::Group here, a::b::{{c}, {d}} might
|
||||
// break. But also why would anybody do that
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
if !moved_tree_cursor {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
match tree_cursor {
|
||||
UseTree::Name(use_name) => {
|
||||
path.push(use_name.ident.to_string());
|
||||
*is_complete = true;
|
||||
}
|
||||
UseTree::Path(use_path) => {
|
||||
path.push(use_path.ident.to_string());
|
||||
}
|
||||
UseTree::Glob(_) => {
|
||||
path.push("*".to_string());
|
||||
*is_complete = true;
|
||||
}
|
||||
UseTree::Group(use_group) => {
|
||||
// We'll modify the first one in-place, and make clones for
|
||||
// all subsequent ones
|
||||
let mut first: bool = true;
|
||||
// Capture the path in this state, since we're about to
|
||||
// modify it
|
||||
let path_copy = path.clone();
|
||||
for tree in use_group.items.iter() {
|
||||
let mut new_path_tuple = if first {
|
||||
None
|
||||
} else {
|
||||
let new_path = path_copy.clone();
|
||||
items_to_push.push((new_path, false));
|
||||
Some(items_to_push.iter_mut().last().unwrap())
|
||||
};
|
||||
|
||||
match tree {
|
||||
UseTree::Path(use_path) => {
|
||||
let ident = use_path.ident.to_string();
|
||||
|
||||
if first {
|
||||
path.push(ident);
|
||||
} else {
|
||||
new_path_tuple.unwrap().0.push(ident);
|
||||
}
|
||||
}
|
||||
UseTree::Name(use_name) => {
|
||||
let ident = use_name.ident.to_string();
|
||||
|
||||
if first {
|
||||
path.push(ident);
|
||||
*is_complete = true;
|
||||
} else {
|
||||
let path_tuple = new_path_tuple.as_mut().unwrap();
|
||||
path_tuple.0.push(ident);
|
||||
path_tuple.1 = true;
|
||||
}
|
||||
}
|
||||
UseTree::Glob(_) => {
|
||||
if first {
|
||||
path.push("*".to_string());
|
||||
*is_complete = true;
|
||||
} else {
|
||||
let path_tuple = new_path_tuple.as_mut().unwrap();
|
||||
path_tuple.0.push("*".to_string());
|
||||
path_tuple.1 = true;
|
||||
}
|
||||
}
|
||||
UseTree::Group(_) => {
|
||||
panic!(
|
||||
"Directly-nested use groups ({}) are not supported by flutter_rust_bridge. Use {} instead.",
|
||||
"use a::{{b}, c}",
|
||||
"a::{b, c}"
|
||||
);
|
||||
}
|
||||
// UseTree::Group(_) => panic!(),
|
||||
UseTree::Rename(_) => {
|
||||
flatten_use_tree_rename_abort_warning(use_tree);
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
UseTree::Rename(_) => {
|
||||
flatten_use_tree_rename_abort_warning(use_tree);
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for item in items_to_push {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
result.into_iter().map(|val| val.0).collect()
|
||||
}
|
||||
46
libs/flutter_rust_bridge_codegen/src/transformer.rs
Normal file
46
libs/flutter_rust_bridge_codegen/src/transformer.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use log::debug;
|
||||
|
||||
use crate::ir::IrType::*;
|
||||
use crate::ir::*;
|
||||
|
||||
pub fn transform(src: IrFile) -> IrFile {
|
||||
let dst_funcs = src
|
||||
.funcs
|
||||
.into_iter()
|
||||
.map(|src_func| IrFunc {
|
||||
inputs: src_func
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(transform_func_input_add_boxed)
|
||||
.collect(),
|
||||
..src_func
|
||||
})
|
||||
.collect();
|
||||
|
||||
IrFile {
|
||||
funcs: dst_funcs,
|
||||
..src
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_func_input_add_boxed(input: IrField) -> IrField {
|
||||
match &input.ty {
|
||||
StructRef(_)
|
||||
| EnumRef(IrTypeEnumRef {
|
||||
is_struct: true, ..
|
||||
}) => {
|
||||
debug!(
|
||||
"transform_func_input_add_boxed wrap Boxed to field={:?}",
|
||||
input
|
||||
);
|
||||
IrField {
|
||||
ty: Boxed(IrTypeBoxed {
|
||||
exist_in_real_api: false, // <--
|
||||
inner: Box::new(input.ty.clone()),
|
||||
}),
|
||||
..input
|
||||
}
|
||||
}
|
||||
_ => input,
|
||||
}
|
||||
}
|
||||
26
libs/flutter_rust_bridge_codegen/src/utils.rs
Normal file
26
libs/flutter_rust_bridge_codegen/src/utils.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn mod_from_rust_path(code_path: &str, crate_path: &str) -> String {
|
||||
Path::new(code_path)
|
||||
.strip_prefix(Path::new(crate_path).join("src"))
|
||||
.unwrap()
|
||||
.with_extension("")
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.replace('/', "::")
|
||||
}
|
||||
|
||||
pub fn with_changed_file<F: FnOnce() -> anyhow::Result<()>>(
|
||||
path: &str,
|
||||
append_content: &str,
|
||||
f: F,
|
||||
) -> anyhow::Result<()> {
|
||||
let content_original = fs::read_to_string(&path)?;
|
||||
fs::write(&path, content_original.clone() + append_content)?;
|
||||
|
||||
f()?;
|
||||
|
||||
Ok(fs::write(&path, content_original)?)
|
||||
}
|
||||
Reference in New Issue
Block a user