Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wit_import attribute macro to import functions from Wasm modules #944

Merged
merged 22 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions linera-witty-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ edition = "2021"
proc-macro = true

[dependencies]
heck = { workspace = true }
proc-macro-error = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
Expand Down
17 changes: 16 additions & 1 deletion linera-witty-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
#![deny(missing_docs)]

mod util;
mod wit_import;
mod wit_load;
mod wit_store;
mod wit_type;

use self::util::extract_namespace;
use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro_error::{abort, proc_macro_error};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Data, DeriveInput, Ident};
use syn::{parse_macro_input, Data, DeriveInput, Ident, ItemTrait};

/// Derives `WitType` for a Rust type.
///
Expand Down Expand Up @@ -91,3 +93,16 @@ fn derive_trait(input: DeriveInput, body: impl ToTokens, trait_name: Ident) -> T
}
.into()
}

/// Generates a generic type from a trait.
///
/// The generic type has a type parameter for the Wasm guest instance to use, and allows calling
/// functions that the instance exports through the trait's methods.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn wit_import(attribute: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemTrait);
let namespace = extract_namespace(attribute, &input.ident);

wit_import::generate(input, &namespace).into()
}
115 changes: 111 additions & 4 deletions linera-witty-macros/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Helper functions shared between different macro implementations.
//! Helper types and functions shared between different macro implementations.

use proc_macro2::TokenStream;
use quote::quote;
use syn::{Fields, Ident};
use heck::ToKebabCase;
use proc_macro2::{Span, TokenStream};
use proc_macro_error::abort;
use quote::{quote, ToTokens};
use std::hash::{Hash, Hasher};
use syn::{
parse::{self, Parse, ParseStream},
punctuated::Punctuated,
Fields, Ident, Lit, LitStr, MetaNameValue, Token,
};

/// Returns the code with a pattern to match a heterogenous list using the `field_names` as
/// bindings.
Expand All @@ -22,3 +29,103 @@ pub fn hlist_type_for(fields: &Fields) -> TokenStream {
let field_types = fields.iter().map(|field| &field.ty);
quote! { linera_witty::HList![#( #field_types ),*] }
}

/// Returns the package and namespace for the WIT interface generated by an attribute macro.
///
/// Requires a `package` to be specified in `attribute_parameters` and can use a specified
/// `namespace` or infer it from the `type_name`.
pub fn extract_namespace(
attribute_parameters: proc_macro::TokenStream,
type_name: &Ident,
) -> LitStr {
let span = Span::call_site();
let parameters = syn::parse::<AttributeParameters>(attribute_parameters).unwrap_or_else(|_| {
abort!(
span,
r#"Failed to parse attribute parameters, expected either `root = true` \
or `package = "namespace:package""#
)
});

let package_name = parameters.parameter("package").unwrap_or_else(|| {
abort!(
span,
r#"Missing package name specifier in attribute parameters \
(package = "namespace:package")"#
)
});

let interface_name = parameters
.parameter("interface")
.unwrap_or_else(|| type_name.to_string().to_kebab_case());

LitStr::new(&format!("{package_name}/{interface_name}"), span)
}

/// A type representing the parameters for an attribute procedural macro.
struct AttributeParameters {
metadata: Punctuated<MetaNameValue, Token![,]>,
}

impl Parse for AttributeParameters {
fn parse(input: ParseStream) -> parse::Result<Self> {
Ok(AttributeParameters {
metadata: Punctuated::parse_terminated(input)?,
})
}
}

impl AttributeParameters {
/// Returns the string value of a parameter named `name`, if it exists.
pub fn parameter(&self, name: &str) -> Option<String> {
self.metadata
.iter()
.find(|pair| pair.path.is_ident(name))
.map(|pair| {
let Lit::Str(lit_str) = &pair.lit else {
abort!(&pair.lit, "Expected a string literal");
};

lit_str.value()
})
}
}

/// A helper type to allow comparing [`TokenStream`] instances, allowing it to be used in a
/// [`HashSet`].
pub struct TokensSetItem<'input> {
string: String,
tokens: &'input TokenStream,
}

impl<'input> From<&'input TokenStream> for TokensSetItem<'input> {
fn from(tokens: &'input TokenStream) -> Self {
TokensSetItem {
string: tokens.to_string(),
tokens,
}
}
}

impl PartialEq for TokensSetItem<'_> {
fn eq(&self, other: &Self) -> bool {
self.string.eq(&other.string)
}
}

impl Eq for TokensSetItem<'_> {}

impl Hash for TokensSetItem<'_> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.string.hash(state)
}
}

impl ToTokens for TokensSetItem<'_> {
fn to_tokens(&self, stream: &mut TokenStream) {
self.tokens.to_tokens(stream)
}
}
Loading
Loading