Skip to content

Commit

Permalink
feat: export hash map reference (#21)
Browse files Browse the repository at this point in the history
Adds the ability to export a reference to a hash map field.
So far only `HashMap<u8, u8>` has been tested in order to have the generals worked out.
More types will be added later as this PR is now large enough :)

In order to allow iterating the _keys_ of the hash_map we use the first case of applying
`[rid::export]` recursively. Thus this access is simply implemented by emitting the following:

```rs
let keys_impl = quote_spanned! { fn_keys_ident.span() =>
    #[rid::export]
    fn #fn_keys_ident(map: &HashMap<#key_ty, #val_ty>) -> Vec<&#key_ty> {
        map.keys().collect()
    }
};
```

I first tried a different route (visible from some of the commits) to fully render the method
returning a `RidVec` and rendering the access to `RidVec`s via _nested accesses_.

However the final solution turned out to be so much simpler and basically is the first example
of using rid features we can already render like a Lego piece in order to render higher level
types and implementations. I plan to use this approach in more cases going forward.

However to make this happen I had to implement standalone `[rid::export]`s. Up til now only
exports that were part of an `impl` were allowed since I couldn't think of a clean way to
prevent a `rid::export` to be rendered twice (once as part of the impl and once treated
separately).

I now track _exports_ that we rendered as an _impl_ method and skip rendering it again, see
631701d.

This works, but would hide a user error in which a method/function with the same name
is once exported as an _impl_ and then again as a separate method, i.e. it doesn't raise an
error, but just ignores the second one, as in this example:

```rs

impl Store {
    #[rid::export]
    pub fn get_u8(&self) -> u8 {
        self.s_id
    }
}

// The below method is not exported, i.e. no rid ffi wrapper generated, nor any error/warning
// for the user is emitted.
pub fn get_u8() -> u8 {
  1
}
```

If this causes lots of issues (which I don't really doubt) we'll have to take the tokens of the
function into account in order to alert the user when two functions have a name colission.
  • Loading branch information
thlorenz committed Sep 1, 2021
1 parent c1a2e0a commit a927c3d
Show file tree
Hide file tree
Showing 32 changed files with 1,164 additions and 450 deletions.
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
ROOT:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
test:
cargo test --all && \

test: test_rust test_integration

test_rust:
cargo test --all

test_integration:
cd $(ROOT)/tests/dart/field_access && $(MAKE) test-all && \
cd $(ROOT)/tests/dart/export && $(MAKE) test-all && \
cd $(ROOT)/tests/dart/apps && $(MAKE) test-all

.PHONY: test_rust test_integration test
65 changes: 50 additions & 15 deletions rid-macro-impl/src/common/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@ use syn::Ident;

pub struct ExpandState {
initialized: bool,
/// Implementations for supporting functions, i.e. frees or collection accesses
/// that have been emitted
emitted_implementations: Option<HashSet<String>>,

/// Identifiers emitted, i.e. to name wrapping modules.
/// Used to ensure unique identifier names.
emitted_idents: Option<HashMap<Ident, u8>>,

/// Function/Method exports that have been processed as part of an impl.
/// Needed to avoid processing a method export inside an impl twice, once
/// as part of the impl and then again separately as a function.
handled_impl_method_exports: Option<HashSet<String>>,
}

pub enum ImplementationType {
Expand All @@ -32,7 +42,7 @@ impl fmt::Display for ImplementationType {
write!(f, "Free")
}
ImplementationType::UtilsModule => {
write!(f, "UtilsModle")
write!(f, "UtilsModel")
}
}
}
Expand All @@ -44,9 +54,13 @@ impl ExpandState {
self.initialized = true;
self.emitted_implementations = Some(HashSet::new());
self.emitted_idents = Some(HashMap::new());
self.handled_impl_method_exports = Some(HashSet::new());
}
}

// -----------------
// Implementations
// -----------------
pub fn needs_implementation(
&mut self,
impl_type: &ImplementationType,
Expand All @@ -68,6 +82,25 @@ impl ExpandState {
}
}

pub fn need_implemtation<K: fmt::Display, V>(
&mut self,
impl_type: &ImplementationType,
all: HashMap<K, V>,
) -> Vec<V> {
all.into_iter()
.filter_map(|(k, v)| {
if self.needs_implementation(impl_type, &k.to_string()) {
Some(v)
} else {
None
}
})
.collect()
}

// -----------------
// Idents
// -----------------
#[cfg(not(test))]
pub fn unique_ident(&mut self, ident: Ident) -> Ident {
let idents = self.emitted_idents.as_mut().unwrap();
Expand All @@ -84,27 +117,29 @@ impl ExpandState {
id
}

pub fn need_implemtation<K: fmt::Display, V>(
&mut self,
impl_type: &ImplementationType,
all: HashMap<K, V>,
) -> Vec<V> {
all.into_iter()
.filter_map(|(k, v)| {
if self.needs_implementation(impl_type, &k.to_string()) {
Some(v)
} else {
None
}
})
.collect()
// -----------------
// Exports
// -----------------
pub fn register_handled_impl_method_export(&mut self, ident: &Ident) {
self.handled_impl_method_exports
.as_mut()
.unwrap()
.insert(ident.to_string());
}

pub fn handled_impl_method_export(&self, ident: &Ident) -> bool {
self.handled_impl_method_exports
.as_ref()
.unwrap()
.contains(&ident.to_string())
}
}

static mut STATE: ExpandState = ExpandState {
initialized: false,
emitted_implementations: None,
emitted_idents: None,
handled_impl_method_exports: None,
};

pub fn get_state() -> &'static mut ExpandState {
Expand Down
10 changes: 5 additions & 5 deletions rid-macro-impl/src/common/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ pub fn resolve_hash_set_ptr(ty: &syn::Ident) -> TokenStream {
}

pub fn resolve_hash_map_ptr(
arg: &syn::Ident,
key_ty: &syn::Ident,
val_ty: &syn::Ident,
) -> TokenStream {
quote_spanned! { key_ty.span() =>
unsafe {
assert!(!ptr.is_null());
let ptr: *mut ::std::collections::HashMap<#key_ty, #val_ty> = &mut *ptr;
ptr.as_mut().expect("resolve_hash_map_ptr.as_mut failed")
}
let #arg: &HashMap<#key_ty, #val_ty> = unsafe {
assert!(!#arg.is_null());
#arg.as_ref().expect("resolve_hash_map_ptr.as_mut failed")
};
}
}

Expand Down
Loading

0 comments on commit a927c3d

Please sign in to comment.