Skip to content

Commit

Permalink
Merge pull request #24 from jerel/add-borrowing
Browse files Browse the repository at this point in the history
Add borrowing
  • Loading branch information
jerel committed Sep 29, 2022
2 parents a990e53 + 97bb2e6 commit 0106809
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 23 deletions.
10 changes: 10 additions & 0 deletions dart_example/test/main_test.dart
Expand Up @@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'package:dart_example/accounts.dart';
import 'package:dart_example/locations.dart';
import 'package:dart_example/orgs.dart';

void main() {
test('can take one item from a stream', () async {
Expand Down Expand Up @@ -452,4 +453,13 @@ void main() {
expect(err.e, "The sync rust code panicked");
}
});

test('test that borrowing from other namespaces works', () async {
final accounts = AccountsApi();
await accounts.borrowedTypes(id: 10);

final orgs = OrgsApi();
await orgs.getOrgWithBorrowedType(
id: Filter(value: [Match(field: "id", value: "1")]));
});
}
39 changes: 39 additions & 0 deletions example/src/application/advanced.rs
Expand Up @@ -466,6 +466,15 @@ pub fn slow_stream(sleep_for: i64) -> impl Stream<Item = Result<i32, String>> {
}
}

#[async_dart(namespace = "accounts", borrow = "locations::Location")]
pub async fn borrowed_types(id: i64) -> Result<data::Location, String> {
let _id = id;

Ok(data::Location {
polyline_coords: vec![(-104.0185546875, 43.004647127794435)],
})
}

#[async_dart(namespace = "locations")]
pub async fn get_location(id: i64) -> Result<data::Location, String> {
let _id = id;
Expand All @@ -478,3 +487,33 @@ pub async fn get_location(id: i64) -> Result<data::Location, String> {
],
})
}

#[async_dart(
namespace = "orgs",
borrow = "locations::Location",
borrow = "accounts::Contact",
borrow = "accounts::Filter",
borrow = "accounts::Match",
borrow = "accounts::Status"
)]
pub async fn get_org_with_borrowed_type(id: data::Filter) -> Result<data::Organization, String> {
let _ = id;
Ok(data::Organization {
id: 1,
owner: data::Contact::default(),
location: data::Location {
polyline_coords: vec![(-104.0185546875, 43.004647127794435)],
},
})
}

// this is only to duplicate the above borrows to test import generation, it's never called
#[async_dart(
namespace = "orgs",
borrow = "locations::Location",
borrow = "accounts::Contact",
borrow = "accounts::Contact"
)]
pub async fn unused_duplicate_borrows(_id: i64) -> Result<data::Organization, String> {
todo!()
}
7 changes: 7 additions & 0 deletions example/src/data.rs
Expand Up @@ -112,3 +112,10 @@ pub struct MoreTypes {
pub struct Location {
pub polyline_coords: Vec<(f64, f64)>,
}

#[derive(Deserialize, Serialize)]
pub struct Organization {
pub id: i64,
pub owner: Contact,
pub location: Location,
}
163 changes: 144 additions & 19 deletions membrane/src/lib.rs
Expand Up @@ -82,12 +82,13 @@ pub mod utils;
mod generators;

use generators::functions::{Builder, Writable};
use membrane_types::heck::CamelCase;
use membrane_types::heck::{CamelCase, SnakeCase};
use serde_reflection::{
ContainerFormat, Error, Registry, Samples, Tracer, TracerConfig, VariantFormat,
};
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
fs::{read_to_string, remove_file},
io::Write,
path::{Path, PathBuf},
};
Expand All @@ -105,21 +106,25 @@ pub struct Function {
pub namespace: String,
pub disable_logging: bool,
pub timeout: Option<i32>,
pub borrow: Vec<&'static str>,
pub output: String,
pub dart_outer_params: String,
pub dart_transforms: String,
pub dart_inner_args: String,
}

#[doc(hidden)]
#[derive(Clone)]
pub struct DeferredTrace {
pub function: Function,
pub namespace: String,
pub trace: fn(tracer: &mut serde_reflection::Tracer, samples: &mut serde_reflection::Samples),
}

#[doc(hidden)]
#[derive(Clone)]
pub struct DeferredEnumTrace {
pub name: String,
pub namespace: String,
pub trace: fn(tracer: &mut serde_reflection::Tracer),
}
Expand All @@ -138,28 +143,72 @@ pub struct Membrane {
generated: bool,
c_style_enums: bool,
timeout: Option<i32>,
borrows: HashMap<String, HashMap<String, HashSet<String>>>,
}

impl<'a> Membrane {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let mut namespaces = vec![];
let enums = inventory::iter::<DeferredEnumTrace>();
let functions = inventory::iter::<DeferredTrace>();
let mut namespaces = vec![
enums
.clone()
.map(|x| x.namespace.clone())
.collect::<Vec<String>>(),
functions
.clone()
.map(|x| x.namespace.clone())
.collect::<Vec<String>>(),
]
.concat();

namespaces.sort();
namespaces.dedup();

let mut namespaced_enum_registry = HashMap::new();
let mut namespaced_samples = HashMap::new();
let mut namespaced_fn_registry = HashMap::new();
for item in inventory::iter::<DeferredEnumTrace> {
namespaces.push(item.namespace.clone());
let mut borrows: HashMap<String, HashMap<String, HashSet<String>>> = HashMap::new();

// collect all the metadata about functions (without tracing them yet)
functions.clone().for_each(|item| {
namespaced_fn_registry
.entry(item.namespace.clone())
.or_insert_with(Vec::new)
.push(item.function.clone());
});

// work out which namespaces borrow which types from other namespaces
namespaces.iter().for_each(|namespace| {
Self::create_borrows(&namespaced_fn_registry, namespace.to_string(), &mut borrows);
});

// trace all the enums at least once
enums.for_each(|item| {
// trace the enum into the borrowing namespace's registry
borrows.iter().for_each(|(for_namespace, from_namespaces)| {
if let Some(types) = from_namespaces.get(&item.namespace) {
if types.contains(&item.name) {
let tracer = namespaced_enum_registry
.entry(for_namespace.to_string())
.or_insert_with(|| Tracer::new(TracerConfig::default()));

(item.trace)(tracer);
}
}
});

// trace the enum into the owning namespace's registry
let tracer = namespaced_enum_registry
.entry(item.namespace.clone())
.or_insert_with(|| Tracer::new(TracerConfig::default()));

(item.trace)(tracer);
}

for item in inventory::iter::<DeferredTrace> {
namespaces.push(item.namespace.clone());
});

// now that we have the enums in the registry we'll trace each of the functions
functions.for_each(|item| {
let tracer = namespaced_enum_registry
.entry(item.namespace.clone())
.or_insert_with(|| Tracer::new(TracerConfig::default()));
Expand All @@ -169,15 +218,7 @@ impl<'a> Membrane {
.or_insert_with(Samples::new);

(item.trace)(tracer, samples);

namespaced_fn_registry
.entry(item.namespace.clone())
.or_insert_with(Vec::new)
.push(item.function.clone());
}

namespaces.sort();
namespaces.dedup();
});

Self {
package_name: match std::env::var_os("MEMBRANE_PACKAGE_NAME") {
Expand Down Expand Up @@ -210,6 +251,7 @@ impl<'a> Membrane {
generated: false,
c_style_enums: true,
timeout: None,
borrows,
}
}

Expand Down Expand Up @@ -298,7 +340,7 @@ impl<'a> Membrane {
Ok(reg) => reg,
Err(Error::MissingVariants(names)) => {
panic!(
"An enum was used that has not had the membrane::dart_enum macro applied. Please add #[dart_enum(namespace = \"{}\")] to the {} enum.",
"An enum was used that has not had the membrane::dart_enum macro applied. Please `borrow` it from an existing namespace or add #[dart_enum(namespace = \"{}\")] to the {} enum.",
namespace,
names.first().unwrap()
);
Expand Down Expand Up @@ -411,6 +453,8 @@ uint8_t membrane_free_membrane_vec(int64_t len, const void *ptr);
self.create_class(x.to_string());
});

self.create_imports();

if self.generated {
self.create_loader();
self.format_package();
Expand Down Expand Up @@ -810,6 +854,87 @@ class {class_name}Api {{
fn namespace_path(&mut self, namespace: String) -> PathBuf {
self.destination.join("lib").join("src").join(&namespace)
}

fn create_borrows(
namespaced_fn_registry: &HashMap<String, Vec<Function>>,
namespace: String,
borrows: &mut HashMap<String, HashMap<String, HashSet<String>>>,
) {
let fns = namespaced_fn_registry.get(&namespace).unwrap();

fns.iter().for_each(move |fun| {
fun
.borrow
.iter()
.map(|borrow| borrow.split("::").map(|x| x.trim()).collect::<Vec<&str>>())
.for_each(|borrow_list| {
if let [from_namespace, r#type] = borrow_list[..] {
let imports = borrows.entry(namespace.to_string()).or_default();
let types = imports.entry(from_namespace.to_string()).or_default();
types.insert(r#type.to_string());
} else {
panic!("Found an invalid `borrow`: `{:?}`. Borrows must be of form `borrow = \"namespace::Type\"`", fun.borrow);
}
});

});
}

fn create_imports(&mut self) -> &mut Self {
self.borrows.iter().for_each(|(namespace, imports)| {
imports.iter().for_each(|(from_namespace, borrowed_types)| {
let borrowed_types: Vec<String> = borrowed_types.iter().map(|x| x.to_string()).collect();
let file_name = format!("{ns}.dart", ns = namespace);
let namespace_path = self.destination.join("lib/src").join(namespace);
let barrel_file_path = namespace_path.join(file_name);

let barrel_file = read_to_string(&barrel_file_path)
.unwrap()
.lines()
.filter_map(|line| {
if borrowed_types.contains(
&line
.replace("part '", "")
.replace(".dart';", "")
.to_camel_case(),
) {
None
} else if line.starts_with("import '../bincode") {
Some(vec![
line.to_string(),
format!(
"import '../{ns}/{ns}.dart' show {types};",
ns = from_namespace,
types = borrowed_types.join(",")
),
])
} else if line.starts_with("export '../serde") {
Some(vec![
line.to_string(),
format!(
"export '../{ns}/{ns}.dart' show {types};",
ns = from_namespace,
types = borrowed_types.join(",")
),
])
} else {
Some(vec![line.to_string()])
}
})
.flatten()
.collect::<Vec<String>>();

borrowed_types.iter().for_each(|borrowed_type| {
let filename = format!("{}.dart", borrowed_type.to_snake_case());
let _ = remove_file(namespace_path.join(filename));
});

std::fs::write(barrel_file_path, barrel_file.join("\n")).unwrap();
});
});

self
}
}

#[doc(hidden)]
Expand Down
12 changes: 12 additions & 0 deletions membrane/tests/integration_tests.rs
Expand Up @@ -99,6 +99,18 @@ class Contact {
"MembraneResponse membrane_accounts_contact(int64_t port, const char *user_id);",
);

// verify that borrowed types are no longer created in the borrowing namespace
assert!(path.join("lib/src/locations/location.dart").exists() == true);
assert!(path.join("lib/src/accounts/contact.dart").exists() == true);
assert!(path.join("lib/src/accounts/status.dart").exists() == true);
assert!(path.join("lib/src/accounts/filter.dart").exists() == true);
assert!(path.join("lib/src/accounts/match.dart").exists() == true);
assert!(path.join("lib/src/orgs/location.dart").exists() == false);
assert!(path.join("lib/src/orgs/contact.dart").exists() == false);
assert!(path.join("lib/src/orgs/status.dart").exists() == false);
assert!(path.join("lib/src/orgs/filter.dart").exists() == false);
assert!(path.join("lib/src/orgs/match.dart").exists() == false);

build_lib(&path.to_path_buf(), &mut vec![]);
run_dart(&path.to_path_buf(), vec!["pub", "add", "test"], false);
run_dart(
Expand Down
2 changes: 1 addition & 1 deletion membrane/tests/ui/single.stderr
Expand Up @@ -6,7 +6,7 @@ error: #[async_dart] expects a `namespace` attribute
|
= note: this error originates in the attribute macro `async_dart` (in Nightly builds, run with -Z macro-backtrace for more info)

error: only `namespace=""`, `disable_logging=true`, `os_thread=true`, and `timeout=1000` are valid options
error: only `namespace=""`, `borrow="namespace::Type"`, `disable_logging=true`, `os_thread=true`, and `timeout=1000` are valid options
--> tests/ui/single.rs:27:1
|
27 | #[async_dart(namespace = "a", foo = true)]
Expand Down

0 comments on commit 0106809

Please sign in to comment.