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

4
Cargo.lock generated
View File

@ -1476,9 +1476,7 @@ dependencies = [
[[package]]
name = "flutter_rust_bridge_codegen"
version = "1.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3209735fd687b06b8d770ec008874119b91f7f46b4a73d17226d5c337435bb74"
version = "1.32.0"
dependencies = [
"anyhow",
"cargo_metadata",

View File

@ -105,7 +105,7 @@ jni = "0.19.0"
flutter_rust_bridge = "1.30.0"
[workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display"]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/flutter_rust_bridge_codegen"]
[package.metadata.winres]
LegalCopyright = "Copyright © 2022 Purslane, Inc."
@ -119,7 +119,7 @@ winapi = { version = "0.3", features = [ "winnt" ] }
[build-dependencies]
cc = "1.0"
hbb_common = { path = "libs/hbb_common" }
flutter_rust_bridge_codegen = "1.30.0"
flutter_rust_bridge_codegen = { path = "libs/flutter_rust_bridge_codegen" }
[dev-dependencies]
hound = "3.4"

1
flutter/.gitignore vendored
View File

@ -45,6 +45,7 @@ jniLibs
# flutter rust bridge
lib/generated_bridge.dart
lib/generated_bridge.freezed.dart
# Flutter Generated Files
linux/flutter/generated_plugin_registrant.cc

View File

@ -1,6 +1,20 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "40.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
archive:
dependency: transitive
description:
@ -64,6 +78,62 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.9"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.11"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "7.2.3"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.3.2"
characters:
dependency: transitive
description:
@ -78,6 +148,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
clock:
dependency: transitive
description:
@ -85,6 +162,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
collection:
dependency: transitive
description:
@ -92,6 +176,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
cross_file:
dependency: transitive
description:
@ -113,6 +204,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
dart_style:
dependency: transitive
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.3"
dash_chat:
dependency: "direct main"
description:
@ -319,6 +417,41 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
freezed:
dependency: "direct dev"
description:
name: freezed
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3+1"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
graphs:
dependency: transitive
description:
name: graphs
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
http:
dependency: "direct main"
description:
@ -326,6 +459,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.0"
http_parser:
dependency: transitive
description:
@ -382,6 +522,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
js:
dependency: transitive
description:
@ -389,6 +536,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.5.0"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
matcher:
dependency: transitive
description:
@ -410,6 +571,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
nested:
dependency: transitive
description:
@ -417,6 +585,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
package_info_plus:
dependency: "direct main"
description:
@ -543,6 +718,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
process:
dependency: transitive
description:
@ -557,6 +739,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
qr_code_scanner:
dependency: "direct main"
description:
@ -636,11 +832,32 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.2"
source_span:
dependency: transitive
description:
@ -662,6 +879,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
@ -683,6 +907,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.9"
timing:
dependency: transitive
description:
name: timing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
toggle_switch:
dependency: "direct main"
description:
@ -816,6 +1047,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
win32:
dependency: transitive
description:

View File

@ -3,7 +3,7 @@ description: Your Remote Desktop Software
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
publish_to: "none" # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
@ -19,102 +19,102 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.1.10+27
environment:
sdk: ">=2.16.1"
sdk: ">=2.16.1"
dependencies:
flutter:
sdk: flutter
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.3
ffi: ^1.1.2
path_provider: ^2.0.2
external_path: ^1.0.1
provider: ^5.0.0
tuple: ^2.0.0
wakelock: ^0.5.2
device_info_plus: ^3.2.3
firebase_analytics: ^9.1.5
package_info_plus: ^1.4.2
url_launcher: ^6.0.9
shared_preferences: ^2.0.6
toggle_switch: ^1.4.0
dash_chat: ^1.1.16
draggable_float_widget: ^0.0.2
settings_ui: ^2.0.2
flutter_breadcrumb: ^1.0.1
http: ^0.13.4
qr_code_scanner:
git:
url: https://github.com/Heap-Hop/qr_code_scanner.git
ref: fix_break_changes_platform
zxing2: ^0.1.0
image_picker: ^0.8.5
image: ^3.1.3
flutter_smart_dialog: ^4.3.1
flutter_rust_bridge: ^1.30.0
window_manager: ^0.2.3
desktop_multi_window:
git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: master
bitsdojo_window: ^0.1.2
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.3
ffi: ^1.1.2
path_provider: ^2.0.2
external_path: ^1.0.1
provider: ^5.0.0
tuple: ^2.0.0
wakelock: ^0.5.2
device_info_plus: ^3.2.3
firebase_analytics: ^9.1.5
package_info_plus: ^1.4.2
url_launcher: ^6.0.9
shared_preferences: ^2.0.6
toggle_switch: ^1.4.0
dash_chat: ^1.1.16
draggable_float_widget: ^0.0.2
settings_ui: ^2.0.2
flutter_breadcrumb: ^1.0.1
http: ^0.13.4
qr_code_scanner:
git:
url: https://github.com/Heap-Hop/qr_code_scanner.git
ref: fix_break_changes_platform
zxing2: ^0.1.0
image_picker: ^0.8.5
image: ^3.1.3
flutter_smart_dialog: ^4.3.1
flutter_rust_bridge: ^1.30.0
window_manager: ^0.2.3
desktop_multi_window:
git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: master
bitsdojo_window: ^0.1.2
freezed_annotation: ^2.0.3
dev_dependencies:
flutter_launcher_icons: ^0.9.1
flutter_test:
sdk: flutter
flutter_launcher_icons: ^0.9.1
flutter_test:
sdk: flutter
build_runner: ^2.1.11
freezed: ^2.0.3
# rerun: flutter pub run flutter_launcher_icons:main
flutter_icons:
android: "ic_launcher"
ios: true
image_path: "../1024-rec.png"
android: "ic_launcher"
ios: true
image_path: "../1024-rec.png"
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/
# To add assets to your application, add an assets section, like this:
assets:
- assets/
fonts:
- family: GestureIcons
fonts:
- asset: assets/gestures.ttf
fonts:
- family: GestureIcons
fonts:
- asset: assets/gestures.ttf
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

View File

@ -0,0 +1,37 @@
[package]
name = "flutter_rust_bridge_codegen"
version = "1.32.0"
edition = "2018"
description = "High-level memory-safe bindgen for Dart/Flutter <-> Rust"
license = "MIT"
repository = "https://github.com/fzyzcjy/flutter_rust_bridge"
keywords = ["flutter", "dart", "ffi", "code-generation", "bindings"]
categories = ["development-tools::ffi"]
[lib]
name = "lib_flutter_rust_bridge_codegen"
path = "src/lib.rs"
[[bin]]
name = "flutter_rust_bridge_codegen"
path = "src/main.rs"
[dependencies]
syn = { version = "1.0.77", features = ["full", "extra-traits"] }
quote = "1.0"
regex = "1.5.4"
lazy_static = "1.4.0"
convert_case = "0.5.0"
tempfile = "3.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
log = "0.4"
env_logger = "0.9.0"
structopt = "0.3"
toml = "0.5.8"
anyhow = "1.0.44"
pathdiff = "0.2.1"
cargo_metadata = "0.14.1"
enum_dispatch = "0.3.8"
thiserror = "1"
cbindgen = "0.23"

View File

@ -0,0 +1,95 @@
# [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge): High-level memory-safe binding generator for Flutter/Dart <-> Rust
[![Rust Package](https://img.shields.io/crates/v/flutter_rust_bridge.svg)](https://crates.io/crates/flutter_rust_bridge)
[![Flutter Package](https://img.shields.io/pub/v/flutter_rust_bridge.svg)](https://pub.dev/packages/flutter_rust_bridge)
[![Stars](https://img.shields.io/github/stars/fzyzcjy/flutter_rust_bridge)](https://github.com/fzyzcjy/flutter_rust_bridge)
[![CI](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/ci.yaml/badge.svg)](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/ci.yaml)
[![Example](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/post_release.yaml/badge.svg)](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/post_release.yaml)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/6afbdad19e7245adbf9e9771777be3d7)](https://app.codacy.com/gh/fzyzcjy/flutter_rust_bridge?utm_source=github.com&utm_medium=referral&utm_content=fzyzcjy/flutter_rust_bridge&utm_campaign=Badge_Grade_Settings)
![Logo](https://github.com/fzyzcjy/flutter_rust_bridge/raw/master/book/logo.png)
Want to combine the best between [Flutter](https://flutter.dev/), a cross-platform hot-reload rapid-development UI toolkit, and [Rust](https://www.rust-lang.org/), a language empowering everyone to build reliable and efficient software? Here it comes!
## 🚀 Advantages
* **Memory-safe**: Never need to think about malloc/free.
* **Feature-rich**: `enum`s with values, platform-optimized `Vec`, possibly recursive `struct`, zero-copy big arrays, `Stream` (iterator) abstraction, error (`Result`) handling, cancellable tasks, concurrency control, and more. See full features [here](https://fzyzcjy.github.io/flutter_rust_bridge/feature.html).
* **Async programming**: Rust code will never block the Flutter. Call Rust naturally from Flutter's main isolate (thread).
* **Lightweight**: This is not a huge framework that includes everything, so you are free to use your favorite Flutter and Rust libraries. <sub>For example, state-management with Flutter library (e.g. MobX) can be elegant and simple (contrary to implementing in Rust); implementing a photo manipulation algorithm in Rust will be fast and safe (countrary to implementing in Flutter).</sub>
* **Cross-platform**: Android, iOS, Windows, Linux, MacOS ([Web](https://github.com/fzyzcjy/flutter_rust_bridge/issues/315) coming soon)
* **Easy to code-review & convince yourself**: This package simply simulates how humans write boilerplate code. If you want to convince yourself (or your team) that it is safe, there is not much code to look at. No magic at all! ([More about](https://fzyzcjy.github.io/flutter_rust_bridge/safety.html) safety concerns.)
* **Fast**: It is only a thin (though feature-rich) wrapper, without overhead such as protobuf serialization, thus performant. (More [benchmarks](https://github.com/fzyzcjy/flutter_rust_bridge/issues/318#issuecomment-1034536815) later) <small>(Throw away components like thread-pool to make it even faster)</small>
* **Pure-Dart compatible:** Despite the name, this package is 100% compatible with [pure](https://github.com/fzyzcjy/flutter_rust_bridge/blob/master/frb_example/pure_dart/README.md) Dart.
## 💡 User Guide
Check out [the user guide](https://fzyzcjy.github.io/flutter_rust_bridge/) for [show-me-the-code](https://fzyzcjy.github.io/flutter_rust_bridge/quickstart.html), [tutorials](https://fzyzcjy.github.io/flutter_rust_bridge/tutorial_with_flutter.html), [features](https://fzyzcjy.github.io/flutter_rust_bridge/feature.html) and much more.
## 📎 P.S. Convenient Flutter tests
If you want to write and debug tests in Flutter conveniently, with action history, time travelling, screenshots, rapid re-execution, video recordings, interactive mode and more, here is my another open-source library: https://github.com/fzyzcjy/flutter_convenient_test.
## ✨ Contributors
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key) following [all-contributors](https://github.com/all-contributors/all-contributors) specification):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://github.com/fzyzcjy"><img src="https://avatars.githubusercontent.com/u/5236035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fzyzcjy</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=fzyzcjy" title="Code">💻</a> <a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=fzyzcjy" title="Documentation">📖</a> <a href="#example-fzyzcjy" title="Examples">💡</a> <a href="#ideas-fzyzcjy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-fzyzcjy" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/Desdaemon"><img src="https://avatars.githubusercontent.com/u/36768030?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Viet Dinh</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=Desdaemon" title="Code">💻</a> <a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=Desdaemon" title="Tests">⚠️</a> <a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=Desdaemon" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/SecondFlight"><img src="https://avatars.githubusercontent.com/u/6700184?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joshua Wade</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=SecondFlight" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/smw-wagnerma"><img src="https://avatars.githubusercontent.com/u/66412697?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marcel</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=smw-wagnerma" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/rustui"><img src="https://avatars.githubusercontent.com/u/90625190?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rustui</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=rustui" title="Documentation">📖</a></td>
<td align="center"><a href="https://adventures.michaelfbryan.com/"><img src="https://avatars.githubusercontent.com/u/17380079?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Bryan</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=Michael-F-Bryan" title="Code">💻</a></td>
<td align="center"><a href="https://bus710.net"><img src="https://avatars.githubusercontent.com/u/8920680?v=4?s=100" width="100px;" alt=""/><br /><sub><b>bus710</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=bus710" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://scholar.google.com/citations?user=RbAto7EAAAAJ"><img src="https://avatars.githubusercontent.com/u/1213857?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sebastian Urban</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=surban" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/trobanga"><img src="https://avatars.githubusercontent.com/u/8888869?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=trobanga" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/AlienKevin"><img src="https://avatars.githubusercontent.com/u/22850071?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Li</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=AlienKevin" title="Code">💻</a> <a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=AlienKevin" title="Documentation">📖</a></td>
<td align="center"><a href="https://valeth.me"><img src="https://avatars.githubusercontent.com/u/3198362?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Patrick Auernig</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=valeth" title="Code">💻</a></td>
<td align="center"><a href="https://antonok.com"><img src="https://avatars.githubusercontent.com/u/22821309?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anton Lazarev</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=antonok-edm" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Unoqwy"><img src="https://avatars.githubusercontent.com/u/65187632?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Unoqwy</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=Unoqwy" title="Code">💻</a></td>
<td align="center"><a href="https://feber.dev"><img src="https://avatars.githubusercontent.com/u/1727318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Febrian Setianto</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=feber" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/Syndim"><img src="https://avatars.githubusercontent.com/u/835035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>syndim</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=syndim" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/sagudev"><img src="https://avatars.githubusercontent.com/u/16504129?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sagu</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=sagudev" title="Code">💻</a> <a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=sagudev" title="Documentation">📖</a></td>
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=eltociear" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/alanlzhang"><img src="https://avatars.githubusercontent.com/u/59032810?v=4?s=100" width="100px;" alt=""/><br /><sub><b>alanlzhang</b></sub></a><br /><a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=alanlzhang" title="Code">💻</a> <a href="https://github.com/fzyzcjy/flutter_rust_bridge/commits?author=alanlzhang" title="Documentation">📖</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
More specifically, thanks for all these contributions:
* [Desdaemon](https://github.com/Desdaemon): Support not only simple enums but also enums with fields which gets translated to native enum or freezed class in Dart. Support the Option type as nullable types in Dart. Support Vec of Strings type. Support comments in code. Add marker attributes for future usage. Add Linux and Windows support for with-flutter example, and make CI works for that. Avoid parameter collision. Overhaul the documentation and add several chapters to demonstrate configuring a Flutter+Rust project in all five platforms. Refactor command module.
* [SecondFlight](https://github.com/SecondFlight): Allow structs and enums to be imported from other files within the crate by creating source graph. Auto-create relavent dir.
* [Unoqwy](https://github.com/Unoqwy): Add struct mirrors, such that types in the external crates can be imported and used without redefining and copying.
* [antonok-edm](https://github.com/antonok-edm): Avoid converting syn types to strings before parsing to improve code and be more robust.
* [sagudev](https://github.com/sagudev): Make code generator a `lib`. Add error types. Depend on `cbindgen`. Fix LLVM paths. Update deps. Fix CI errors.
* [surban](https://github.com/surban): Support unit return type. Skip unresolvable modules. Ignore prefer_const_constructors. Non-final Dart fields.
* [trobanga](https://github.com/trobanga): Add support for `[T;N]` structs. Add `usize` support. Add a cmd argument. Separate dart tests.
* [AlienKevin](https://github.com/AlienKevin): Add flutter example for macOS. Add doc for Android NDK bug.
* [alanlzhang](https://github.com/alanlzhang): Add generation for Dart metadata.
* [efc-mw](https://github.com/efc-mw): Improve Windows encoding handling.
* [valeth](https://github.com/valeth): Rename callFfi's port.
* [Michael-F-Bryan](https://github.com/Michael-F-Bryan): Detect broken bindings.
* [bus710](https://github.com/bus710): Add a case in troubleshooting.
* [Syndim](https://github.com/Syndim): Add a bracket to box.
* [feber](https://github.com/feber): Fix doc link.
* [rustui](https://github.com/rustui): Fix a typo.
* [eltociear](https://github.com/eltociear): Fix a typo.

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