fix: compile error when using enum in flutter

This commit is contained in:
SoLongAndThanksForAllThePizza
2022-05-31 16:28:12 +08:00
parent 00ba7cad81
commit 5825ae4531
59 changed files with 6133 additions and 87 deletions

View 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(())
}

View 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(),
)
}
}

View 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)
}
}

View 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"),
)
}

View 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
}

View 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(),
}
}
}

View File

@@ -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()),
}
}
}

View File

@@ -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()
}
}
}
}

View 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
)
}
}
}

View File

@@ -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()
)
}
}

View File

@@ -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()
)
}
}

View File

@@ -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()),
}
}
}

View File

@@ -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())
}
}

View 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
)
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod c;
pub mod dart;
pub mod rust;

View 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(),
&params
.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,
)
}
}

View 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(),
}
}
}

View File

@@ -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)
}
}

View 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(),
}
}
}

View 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("::")))
}
}

View File

@@ -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)
}
}

View 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)
}
}

View 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())
}
}

View File

@@ -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(),
),
)
}
}

View 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
}
}
}

View File

@@ -0,0 +1,7 @@
use crate::ir::*;
#[derive(Debug, Clone)]
pub struct IrDartAnnotation {
pub content: String,
pub library: Option<IrDartImport>,
}

View 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))
}
}
}

View 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>,
}

View 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
}
}

View 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
}
}

View 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)
}
}

View File

@@ -0,0 +1,5 @@
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct IrDartImport {
pub uri: String,
pub alias: Option<String>,
}

View 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::*;

View 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)
})
}

View 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
}
}

View 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()
}
}

View 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),
}

View 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
}
}

View 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);
}
}

View 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,
}
}
}

View 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
}
}

View 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")
}
}

View 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(())
}

View 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 :)");
}

View 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,
}
})
}

View 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(())
}

View 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(' ', "")
}

View 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,
}
}
}

View 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()
}

View 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,
}
}

View 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)?)
}