Skip to content

Commit

Permalink
Implement TryClone macro
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Sep 28, 2023
1 parent bf44ceb commit 4878f01
Show file tree
Hide file tree
Showing 101 changed files with 812 additions and 483 deletions.
22 changes: 22 additions & 0 deletions crates/rune-alloc-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "rune-alloc-macros"
version = "0.12.3"
authors = ["John-John Tedro <[email protected]>"]
edition = "2021"
rust-version = "1.70"
description = "Macros for the Rune Language, an embeddable dynamic programming language for Rust."
documentation = "https://docs.rs/rune"
readme = "README.md"
homepage = "https://github.com/rune-rs/rune"
repository = "https://github.com/rune-rs/rune"
license = "MIT OR Apache-2.0"
keywords = ["language", "scripting", "scripting-language"]
categories = ["parser-implementations"]

[dependencies]
syn = { version = "2.0.16", features = ["full"] }
quote = "1.0.27"
proc-macro2 = "1.0.56"

[lib]
proc-macro = true
78 changes: 78 additions & 0 deletions crates/rune-alloc-macros/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::cell::RefCell;

use proc_macro2::Span;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned as _;

#[derive(Default)]
pub(crate) struct Context {
pub(crate) errors: RefCell<Vec<syn::Error>>,
pub(crate) module: Option<syn::Path>,
}

impl Context {
/// Construct a new context.
pub(crate) fn new() -> Self {
Self::default()
}

/// Register an error.
pub(crate) fn error(&self, error: syn::Error) {
self.errors.borrow_mut().push(error)
}

/// Test if context has any errors.
pub(crate) fn has_errors(&self) -> bool {
!self.errors.borrow().is_empty()
}

/// Convert into errors.
pub(crate) fn into_errors(self) -> Vec<syn::Error> {
self.errors.into_inner()
}

pub(crate) fn tokens_with_module(&self, module: Option<&syn::Path>) -> Tokens {
let mut default_module;

let m = match module {
Some(module) => module,
None => match &self.module {
Some(module) => module,
None => {
default_module = syn::Path {
leading_colon: None,
segments: Punctuated::default(),
};
default_module
.segments
.push(syn::PathSegment::from(syn::Ident::new(
"rune",
Span::call_site(),
)));
&default_module
}
},
};

Tokens {
try_clone: path(m, ["alloc", "clone", "TryClone"]),
alloc: path(m, ["alloc"]),
}
}
}

fn path<const N: usize>(base: &syn::Path, path: [&'static str; N]) -> syn::Path {
let mut base = base.clone();

for s in path {
let ident = syn::Ident::new(s, base.span());
base.segments.push(syn::PathSegment::from(ident));
}

base
}

pub(crate) struct Tokens {
pub(crate) try_clone: syn::Path,
pub(crate) alloc: syn::Path,
}
47 changes: 47 additions & 0 deletions crates/rune-alloc-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! <img alt="rune logo" src="https://raw.githubusercontent.com/rune-rs/rune/main/assets/icon.png" />
//! <br>
//! <a href="https://github.com/rune-rs/rune"><img alt="github" src="https://img.shields.io/badge/github-rune--rs/rune-8da0cb?style=for-the-badge&logo=github" height="20"></a>
//! <a href="https://crates.io/crates/rune-macros"><img alt="crates.io" src="https://img.shields.io/crates/v/rune-macros.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20"></a>
//! <a href="https://docs.rs/rune-macros"><img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-rune--macros-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20"></a>
//! <a href="https://discord.gg/v5AeNkT"><img alt="chat on discord" src="https://img.shields.io/discord/558644981137670144.svg?logo=discord&style=flat-square" height="20"></a>
//! <br>
//! Minimum support: Rust <b>1.70+</b>.
//! <br>
//! <br>
//! <a href="https://rune-rs.github.io"><b>Visit the site 🌐</b></a>
//! &mdash;
//! <a href="https://rune-rs.github.io/book/"><b>Read the book 📖</b></a>
//! <br>
//! <br>
//!
//! Macros for the Rune Language, an embeddable dynamic programming language for Rust.
//!
//! <br>
//!
//! ## Usage
//!
//! This is part of the [Rune Language](https://rune-rs.github.io).
#![allow(clippy::manual_map)]

extern crate proc_macro;

mod context;
mod try_clone;

#[proc_macro_derive(TryClone, attributes(try_clone))]
pub fn try_clone(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);

try_clone::expand(input)
.unwrap_or_else(to_compile_errors)
.into()
}

fn to_compile_errors<I>(errors: I) -> proc_macro2::TokenStream
where
I: IntoIterator<Item = syn::Error>,
{
let compile_errors = errors.into_iter().map(syn::Error::into_compile_error);
::quote::quote!(#(#compile_errors)*)
}
144 changes: 144 additions & 0 deletions crates/rune-alloc-macros/src/try_clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::Parse;

use crate::context::{Context, Tokens};

pub(super) fn expand(mut input: syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
let cx = Context::new();
let tokens = cx.tokens_with_module(None);

for a in input.attrs {
if !a.path().is_ident("try_clone") {
continue;
}

let result = a.parse_nested_meta(|parser| {
if parser.path.is_ident("bound") {
parser.input.parse::<syn::Token![=]>()?;
let content;
syn::braced!(content in parser.input);
let extra = content.parse_terminated(syn::WherePredicate::parse, syn::Token![,])?;
input.generics.make_where_clause().predicates.extend(extra);
return Ok(());
}

Err(syn::Error::new(
parser.input.span(),
"unsupported attribute",
))
});

if let Err(error) = result {
cx.error(error);
}
}

let Tokens {
try_clone, alloc, ..
} = &tokens;

let implementation = match input.data {
syn::Data::Struct(st) => {
let fields = st.fields.into_iter().enumerate().map(|(index, f)| {
let member = match &f.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(syn::Index::from(index)),
};

let expr = syn::Expr::Verbatim(quote! { #try_clone::try_clone(&self.#member)? });

syn::FieldValue {
attrs: Vec::new(),
member,
colon_token: Some(<syn::Token![:]>::default()),
expr,
}
});

let name = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

quote! {
impl #impl_generics #try_clone for #name #ty_generics #where_clause {
fn try_clone(&self) -> #alloc::Result<Self> {
Ok(Self { #(#fields),* })
}
}
}
}
syn::Data::Enum(en) => {
let name = input.ident;

let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

let variants = en.variants.into_iter().map(|v| {
let name = v.ident;

let members = v
.fields
.iter()
.enumerate()
.map(|(index, f)| match &f.ident {
Some(ident) => (
syn::Member::Named(ident.clone()),
quote::format_ident!("{}", ident),
),
None => (
syn::Member::Unnamed(syn::Index::from(index)),
quote::format_ident!("_{}", index),
),
});

let assigns = members.clone().map(|(member, var)| {
let expr = syn::Expr::Path(syn::ExprPath {
attrs: Vec::new(),
qself: None,
path: syn::Path::from(var),
});

syn::FieldValue {
attrs: Vec::new(),
member,
colon_token: Some(<syn::Token![:]>::default()),
expr,
}
});

let fields = members.clone().map(|(member, var)| {
let expr = syn::Expr::Verbatim(quote! { #try_clone::try_clone(#var)? });

syn::FieldValue {
attrs: Vec::new(),
member,
colon_token: Some(<syn::Token![:]>::default()),
expr,
}
});

quote! {
Self::#name { #(#assigns),* } => {
Self::#name { #(#fields),* }
}
}
});

quote! {
impl #impl_generics #try_clone for #name #ty_generics #where_clause {
fn try_clone(&self) -> #alloc::Result<Self> {
Ok(match self {
#(#variants),*
})
}
}
}
}
syn::Data::Union(_) => todo!(),
};

if !cx.has_errors() {
return Err(cx.into_errors());
}

Ok(implementation)
}
2 changes: 2 additions & 0 deletions crates/rune-alloc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ std = ["alloc", "ahash/std"]
alloc = []

[dependencies]
rune-alloc-macros = { version = "=0.12.3", path = "../rune-alloc-macros" }

serde = { version = "1.0", optional = true }
ahash = { version = "0.8.3", default-features = false }
pin-project = "1.1.0"
Expand Down
9 changes: 9 additions & 0 deletions crates/rune-alloc/src/borrow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ where
}
}

#[cfg(feature = "std")]
impl TryToOwned for ::rust_std::path::Path {
type Owned = ::rust_std::path::PathBuf;

fn try_to_owned(&self) -> Result<Self::Owned, Error> {
Ok(self.to_path_buf())
}
}

/// A clone-on-write smart pointer.
///
/// The type `Cow` is a smart pointer providing clone-on-write functionality: it
Expand Down
10 changes: 10 additions & 0 deletions crates/rune-alloc/src/clone.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::error::Error;

#[doc(inline)]
pub use rune_alloc_macros::TryClone;

/// Fallible `Clone` trait.
pub trait TryClone: Sized {
/// Try to clone the current value, raising an allocation error if it's unsuccessful.
Expand Down Expand Up @@ -176,3 +179,10 @@ where
Ok(out)
}
}

#[cfg(all(test, feature = "std"))]
impl TryClone for ::rust_std::path::PathBuf {
fn try_clone(&self) -> Result<Self, Error> {
Ok(self.clone())
}
}
14 changes: 14 additions & 0 deletions crates/rune-alloc/src/serde/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,17 @@ where
deserializer.deserialize_seq(VecInPlaceVisitor(place))
}
}

impl<'de, T> Deserialize<'de> for Box<[T]>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Vec::<T>::deserialize(deserializer)?
.try_into_boxed_slice()
.map_err(D::Error::custom)
}
}
16 changes: 8 additions & 8 deletions crates/rune-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,14 +270,6 @@ pub fn inst_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive.expand().unwrap_or_else(to_compile_errors).into()
}

fn to_compile_errors<I>(errors: I) -> proc_macro2::TokenStream
where
I: IntoIterator<Item = syn::Error>,
{
let compile_errors = errors.into_iter().map(syn::Error::into_compile_error);
::quote::quote!(#(#compile_errors)*)
}

/// Adds the `path` as trait bound to each generic
fn add_trait_bounds(generics: &mut Generics, path: &Path) {
for ty in &mut generics.type_params_mut() {
Expand All @@ -289,3 +281,11 @@ fn add_trait_bounds(generics: &mut Generics, path: &Path) {
}));
}
}

fn to_compile_errors<I>(errors: I) -> proc_macro2::TokenStream
where
I: IntoIterator<Item = syn::Error>,
{
let compile_errors = errors.into_iter().map(syn::Error::into_compile_error);
::quote::quote!(#(#compile_errors)*)
}
6 changes: 2 additions & 4 deletions crates/rune-macros/src/to_tokens.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::{
add_trait_bounds,
context::{Context, Tokens},
};
use crate::add_trait_bounds;
use crate::context::{Context, Tokens};
use proc_macro2::TokenStream;
use quote::quote_spanned;
use syn::spanned::Spanned as _;
Expand Down
Loading

0 comments on commit 4878f01

Please sign in to comment.