1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;
/// Wraps the given code block into an anonymous const code block. Useful when
/// the code block `use`s stuff that should not leak into the containing scope.
/// See also https://github.com/serde-rs/serde/issues/159#issuecomment-214002626
pub fn wrap_in_const(tokens: TokenStream) -> TokenStream {
quote! {
const _: () = {
#tokens
};
}
}
/// Determines what crate name should be used to refer to `oasis_runtime_sdk` types.
/// Required for use within the SDK itself (crates cannot refer to their own names).
pub fn sdk_crate_path() -> syn::Path {
let is_internal = std::env::var("CARGO_PKG_NAME")
.map(|pkg_name| pkg_name == "oasis-runtime-sdk")
.unwrap_or_default();
if is_internal {
// Doctests are their own crates, but they share the name of the primary crate.
// Thus, the primary crate needs to refer to itself. Either that or depend on unstable
// rustdoc env vars.
syn::parse_quote!(crate::oasis_runtime_sdk)
} else {
syn::parse_quote!(::oasis_runtime_sdk)
}
}
pub trait CodedVariant {
/// The field in the helper attribute that yields the value provided by `code`.
/// For instance, in `#[sdk_event(code = 0)]`, the `FIELD_NAME` would be `code`.
const FIELD_NAME: &'static str;
/// The variant ident.
fn ident(&self) -> &Ident;
/// The code to which the variant should be converted.
fn code(&self) -> Option<u32>;
}
/// Returns a `match` expression that encodes an enum's variants as integral codes.
pub fn enum_code_converter<V: CodedVariant>(
enum_binding: &Ident,
variants: &[&V],
autonumber: bool,
) -> TokenStream {
if variants.is_empty() {
return quote!(0); // Early return with default if there are no variants.
}
let mut next_autonumber = 0u32;
let mut reserved_numbers = std::collections::BTreeSet::new();
let match_arms = variants.iter().map(|variant| {
let variant_ident = variant.ident();
let code = match variant.code() {
Some(code) => {
if reserved_numbers.contains(&code) {
variant_ident
.span()
.unwrap()
.error(format!("code {code} already used"))
.emit();
return quote!({});
}
reserved_numbers.insert(code);
code
}
None if autonumber => {
let mut reserved_successors = reserved_numbers.range(next_autonumber..);
while reserved_successors.next() == Some(&next_autonumber) {
next_autonumber += 1;
}
let code = next_autonumber;
reserved_numbers.insert(code);
next_autonumber += 1;
code
}
None => {
variant_ident
.span()
.unwrap()
.error(format!("missing `{}` for variant", V::FIELD_NAME))
.emit();
return quote!();
}
};
quote!(Self::#variant_ident { .. } => { #code })
});
quote! {
match #enum_binding {
#(#match_arms)*
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_empty_enum_converter() {
struct DummyVariant {}
impl CodedVariant for DummyVariant {
const FIELD_NAME: &'static str = "code";
fn ident(&self) -> &Ident {
unimplemented!()
}
fn code(&self) -> Option<u32> {
unimplemented!()
}
}
let variants: &[&DummyVariant] = &[];
let expected: syn::Expr = syn::parse_quote!(0);
let converter = enum_code_converter("e::format_ident!("the_enum"), variants, false);
let actual: syn::Expr = syn::parse2(converter).unwrap();
assert_eq!(expected, actual);
}
}