magnus-macros-0.6.0/.cargo_vcs_info.json0000644000000001530000000000100136120ustar { "git": { "sha1": "ef66b6b650b9ca2566d9c79c9af9c08659c70ffd" }, "path_in_vcs": "magnus-macros" }magnus-macros-0.6.0/Cargo.toml0000644000000015560000000000100116200ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "magnus-macros" version = "0.6.0" description = "Derive and proc macros for magnus" homepage = "https://github.com/matsadler/magnus" license = "MIT" repository = "https://github.com/matsadler/magnus" [lib] proc-macro = true [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "2" features = ["full"] [dev-dependencies] magnus-macros-0.6.0/Cargo.toml.orig000064400000000000000000000006421046102023000152740ustar 00000000000000[package] name = "magnus-macros" version = "0.6.0" edition = "2021" description = "Derive and proc macros for magnus" repository = "https://github.com/matsadler/magnus" homepage = "https://github.com/matsadler/magnus" license = "MIT" [lib] proc-macro = true [dependencies] proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["full"] } [dev-dependencies] magnus = { path = "../", features = ["embed"] } magnus-macros-0.6.0/LICENSE000064400000000000000000000020731046102023000134120ustar 00000000000000MIT License Copyright (c) 2023, 2022, 2021 Matthew Sadler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. magnus-macros-0.6.0/src/init.rs000064400000000000000000000016711046102023000145100ustar 00000000000000use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{Error, ItemFn}; pub fn expand(name: Option, input: ItemFn) -> Result { let crate_name = match name { Some(v) => v, None => match std::env::var("CARGO_PKG_NAME") { Ok(v) => v, Err(_) => { return Err(Error::new( Span::call_site(), r#"missing (name = "...") attribute"#, )) } }, }; let extern_init_name = Ident::new( &format!("Init_{}", crate_name.replace('-', "_")), Span::call_site(), ); let init_name = input.sig.ident.clone(); Ok(quote! { #input #[allow(non_snake_case)] #[no_mangle] pub unsafe extern "C" fn #extern_init_name() { use magnus::method::{Init, RubyInit}; #init_name.call_handle_error() } }) } magnus-macros-0.6.0/src/lib.rs000064400000000000000000000301501046102023000143050ustar 00000000000000//! Derive and proc macros for magnus. //! //! ``` //! #[magnus::wrap(class = "RbPoint", free_immediately, size)] //! struct Point { //! x: isize, //! y: isize, //! } //! //! #[magnus::init] //! fn init() -> Result<(), magnus::Error> { //! magnus::define_class("RbPoint", magnus::class::object())?; //! Ok(()) //! } //! ``` #![warn(missing_docs)] use proc_macro::TokenStream; use syn::parse_macro_input; mod init; mod typed_data; mod util; /// Mark a function as the 'init' function to be run for a library when it is /// `require`d by Ruby code. /// /// The init function is used to define your Ruby modules & classes, bind /// functions as Ruby methods, etc. /// /// # Attributes /// /// * `name = "..."` - sets the name of the init function exported for Ruby. /// This default's to the current crate's name. The name will be prepended /// with `Init_` and `-` will be replaced with `_`. This (minus the `Init_` /// prefix) must match the name of the final `.so`/`.bundle` file. /// /// # Examples /// /// ``` /// fn distance(a: (f64, f64), b: (f64, f64)) -> f64 { /// ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt() /// } /// /// #[magnus::init] /// fn init() { /// magnus::define_global_function("distance", magnus::function!(distance, 2)); /// } /// ``` /// The init function can also return `Result<(), magnus::Error>`. /// ``` /// use magnus::{class, define_module, function, method, prelude::*, Error}; /// /// #[magnus::wrap(class = "Euclid::Point", free_immediately, size)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(x: isize, y: isize) -> Self { /// Self { x, y } /// } /// /// fn x(&self) -> isize { /// self.x /// } /// /// fn y(&self) -> isize { /// self.y /// } /// } /// /// #[magnus::init] /// fn init() -> Result<(), Error> { /// let module = define_module("Euclid")?; /// let class = module.define_class("Point", class::object())?; /// class.define_singleton_method("new", function!(Point::new, 2))?; /// class.define_method("x", method!(Point::x, 0))?; /// class.define_method("y", method!(Point::y, 0))?; /// Ok(()) /// } /// ``` /// Setting the name. /// ``` /// #[magnus::init(name = "example")] /// fn init() { /// () /// } /// ``` #[proc_macro_attribute] pub fn init(attrs: TokenStream, item: TokenStream) -> TokenStream { let mut name = None; if !attrs.is_empty() { let attr_parser = syn::meta::parser(|meta| { if meta.path.is_ident("name") { name = Some(meta.value()?.parse::()?.value()); Ok(()) } else { Err(meta.error("unsupported attribute")) } }); parse_macro_input!(attrs with attr_parser); } match init::expand(name, parse_macro_input!(item)) { Ok(tokens) => tokens, Err(e) => e.into_compile_error(), } .into() } /// Allow a Rust type to be passed to Ruby, automatically wrapped as a Ruby /// object. /// /// For more control over the wrapped object, see [`TypedData`]. /// /// # Attributes /// /// * `class = "..."` - required, sets the Ruby class to wrap the Rust type. /// Supports module paths, e.g. `Foo::Bar::Baz`. /// * `name = "..."` - debug name for the type, must be unique. Defaults to the /// class name. /// * `free_immediately` - Drop the Rust type as soon as the Ruby object has /// been garbage collected. This is only safe to set if the type's [`Drop`] /// implmentation does not call Ruby. /// * `size` - Report the [`std::mem::size_of_val`] of the type to Ruby, used /// to aid in deciding when to run the garbage collector. /// * `unsafe_generics` - The derived implementation of [`TypedData`] is not /// guaranteed to be correct for types with generics. If you are sure it is /// for your type this attribute can be used to override the compile time /// error usually generated for types with generics. /// /// # Variant Attributes /// /// The `#[magnus(...)]` attribute can be set on enum variants with the /// following values: /// /// * `class = "..."` - sets the Ruby class to wrap the variant. Supports /// module paths, e.g. `Foo::Bar::Baz`. /// /// # Examples /// /// ``` /// #[magnus::wrap(class = "RbPoint", free_immediately, size)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// // the `Point` struct is automatically wrapped in a Ruby `RbPoint` object /// // when returned to Ruby. /// fn point(x: isize, y: isize) -> Point { /// Point { x, y } /// } /// /// // Ruby `RbPoint` objects are automatically unwrapped to references to the /// // `Point` structs they are wrapping when this function is called from Ruby. /// fn distance(a: &Point, b: &Point) -> f64 { /// (((b.x - a.x).pow(2) + (b.y - a.y).pow(2)) as f64).sqrt() /// } /// /// #[magnus::init] /// fn init() { /// magnus::define_global_function("point", magnus::function!(point, 2)); /// magnus::define_global_function("distance", magnus::function!(distance, 2)); /// } /// ``` /// /// With subclasses for enum variants: /// /// ``` /// use std::f64::consts::PI; /// /// use magnus::{class, define_class, function, method, prelude::*}; /// /// #[magnus::wrap(class = "Shape")] /// enum Shape { /// #[magnus(class = "Circle")] /// Circle { r: f64 }, /// #[magnus(class = "Rectangle")] /// Rectangle { x: f64, y: f64 }, /// } /// /// impl Shape { /// fn area(&self) -> f64 { /// match self { /// Shape::Circle { r } => PI * r * r, /// Shape::Rectangle { x, y } => x * y, /// } /// } /// } /// /// #[magnus::init] /// fn init() -> Result<(), magnus::Error> { /// let shape = define_class("Shape", class::object())?; /// shape.define_method("area", method!(Shape::area, 0))?; /// /// let circle = define_class("Circle", shape)?; /// circle.define_singleton_method("new", function!(|r| Shape::Circle { r }, 1))?; /// /// let rectangle = define_class("Rectangle", shape)?; /// rectangle.define_singleton_method("new", function!(|x, y| Shape::Rectangle { x, y }, 2))?; /// /// Ok(()) /// } /// ``` #[proc_macro_attribute] pub fn wrap(attrs: TokenStream, item: TokenStream) -> TokenStream { typed_data::expand(parse_macro_input!(attrs), parse_macro_input!(item)).into() } /// Derives `DataTypeFunctions` with default implementations, for simple uses /// of [`TypedData`]. /// /// For cases where no custom `DataTypeFunctions` are required a default /// implementation can be derived. The [`macro@wrap`] macro may be a simpler /// alternative in this use case. #[proc_macro_derive(DataTypeFunctions)] pub fn derive_data_type_functions(input: TokenStream) -> TokenStream { typed_data::expand_derive_data_type_functions(parse_macro_input!(input)).into() } /// Derives `TypedData`, allowing the type to be passed to Ruby automatically /// wrapped as a Ruby object. /// /// For simple cases, see [`macro@wrap`]. /// /// # Attributes /// /// The `#[magnus(...)]` attribute can be set with the following values: /// /// * `class = "..."` - required, sets the Ruby class to wrap the Rust type. /// Supports module paths, e.g. `Foo::Bar::Baz`. /// * `name = "..."` - debug name for the type, must be unique. Defaults to the /// class name. /// * `free_immediately` - Drop the Rust type as soon as the Ruby object has /// been garbage collected. This is only safe to set if the type's [`Drop`] /// and `DataTypeFunctions::free` implementations do not call Ruby. /// * `mark` - Enable Ruby calling the `DataTypeFunctions::mark` function. /// * `size` - Enable Ruby calling the `DataTypeFunctions::size` function. /// * `compact` - Enable Ruby calling the `DataTypeFunctions::compact` function. /// * `wb_protected` - Enable the `wb_protected` flag. /// * `frozen_shareable` - Enable the `frozen_shareable` flag. /// * `unsafe_generics` - The derived implementation of [`TypedData`] is not /// guaranteed to be correct for types with generics. If you are sure it is /// for your type this attribute can be used to override the compile time /// error usually generated for types with generics. /// /// # Field Attributes /// /// The `#[magnus(...)]` attribute can be set on struct fields with the /// following values: /// /// * `opaque_attr_reader` - For a Ruby value wrapped in `Opaque`, creates a /// accessor method that returns the unwrapped Ruby value. /// /// # Variant Attributes /// /// The `#[magnus(...)]` attribute can be set on enum variants with the /// following values: /// /// * `class = "..."` - sets the Ruby class to wrap the variant. Supports /// module paths, e.g. `Foo::Bar::Baz`. /// /// # Examples /// /// ``` /// use magnus::{DataTypeFunctions, TypedData}; /// /// #[derive(DataTypeFunctions, TypedData)] /// #[magnus(class = "RbPoint", size, free_immediately)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// // the `Point` struct is automatically wrapped in a Ruby `RbPoint` object /// // when returned to Ruby. /// fn point(x: isize, y: isize) -> Point { /// Point { x, y } /// } /// /// // Ruby `RbPoint` objects are automatically unwrapped to references to the /// // `Point` structs they are wrapping when this function is called from Ruby. /// fn distance(a: &Point, b: &Point) -> f64 { /// (((b.x - a.x).pow(2) + (b.y - a.y).pow(2)) as f64).sqrt() /// } /// /// #[magnus::init] /// fn init() { /// magnus::define_global_function("point", magnus::function!(point, 2)); /// magnus::define_global_function("distance", magnus::function!(distance, 2)); /// } /// ``` /// /// With subclasses for enum variants: /// /// ``` /// use magnus::{class, define_class}; /// /// #[magnus::wrap(class = "Shape")] /// enum Shape { /// #[magnus(class = "Circle")] /// Circle { r: f64 }, /// #[magnus(class = "Rectangle")] /// Rectangle { x: f64, y: f64 }, /// } /// /// #[magnus::init] /// fn init() -> Result<(), magnus::Error> { /// let shape = define_class("Shape", class::object())?; /// define_class("Circle", shape)?; /// define_class("Rectangle", shape)?; /// Ok(()) /// } /// ``` /// /// Defining a custom `DataType` function: /// /// ``` /// use std::mem::size_of_val; /// /// use magnus::{DataTypeFunctions, TypedData}; /// /// #[derive(TypedData)] /// #[magnus(class = "Name", size, free_immediately)] /// struct Name { /// first: String, /// last: String, /// } /// /// impl DataTypeFunctions for Name { /// fn size(&self) -> usize { /// size_of_val(&self.first) + size_of_val(&self.last) /// } /// } /// ``` /// /// A struct containing Ruby values. /// /// ``` /// use magnus::{ /// class, define_class, function, gc, method, prelude::*, typed_data::Obj, value::Opaque, /// DataTypeFunctions, TypedData, /// }; /// /// # #[magnus::wrap(class = "Point", free_immediately, size)] /// # struct Point { /// # x: isize, /// # y: isize, /// # } /// # /// #[derive(TypedData)] /// #[magnus(class = "Line", free_immediately, mark)] /// struct Line { /// #[magnus(opaque_attr_reader)] /// start: Opaque>, /// #[magnus(opaque_attr_reader)] /// end: Opaque>, /// } /// /// impl Line { /// fn new(start: Obj, end: Obj) -> Self { /// Self { /// start: start.into(), /// end: end.into(), /// } /// } /// /// fn length(&self) -> f64 { /// let start = self.start(); /// let end = self.end(); /// /// (((end.x - start.x).pow(2) + (end.y - start.y).pow(2)) as f64).sqrt() /// } /// } /// /// impl DataTypeFunctions for Line { /// fn mark(&self, marker: &gc::Marker) { /// marker.mark(self.start); /// marker.mark(self.end); /// } /// } /// /// #[magnus::init] /// fn init() -> Result<(), magnus::Error> { /// let line = define_class("Line", class::object())?; /// line.define_singleton_method("new", function!(Line::new, 2))?; /// line.define_method("length", method!(Line::length, 0))?; /// Ok(()) /// } /// ``` #[proc_macro_derive(TypedData, attributes(magnus))] pub fn derive_typed_data(input: TokenStream) -> TokenStream { match typed_data::expand_derive_typed_data(parse_macro_input!(input)) { Ok(tokens) => tokens, Err(e) => e.into_compile_error(), } .into() } magnus-macros-0.6.0/src/typed_data.rs000064400000000000000000000177621046102023000156730ustar 00000000000000use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ spanned::Spanned, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, FieldsNamed, LitStr, }; use crate::util; pub fn expand(attrs: TokenStream, item: TokenStream) -> TokenStream { quote! { #[derive(magnus::DataTypeFunctions, magnus::TypedData)] #[magnus(#attrs)] #item } } pub fn expand_derive_data_type_functions(input: DeriveInput) -> TokenStream { let ident = input.ident; let generics = input.generics; quote! { impl #generics magnus::DataTypeFunctions for #ident #generics {} } } pub fn expand_derive_typed_data(input: DeriveInput) -> Result { let attrs = match util::get_magnus_attrubute(&input.attrs)? { Some(v) => v, None => return Err(Error::new(input.span(), "missing #[magnus] attribute")), }; let mut class = None; let mut name = None; let mut mark = false; let mut size = false; let mut compact = false; let mut free_immediately = false; let mut wb_protected = false; let mut frozen_shareable = false; let mut unsafe_generics = false; attrs.parse_nested_meta(|meta| { if meta.path.is_ident("class") { class = Some(meta.value()?.parse::()?.value()); Ok(()) } else if meta.path.is_ident("name") { name = Some(meta.value()?.parse::()?.value()); Ok(()) } else if meta.path.is_ident("mark") { mark = true; Ok(()) } else if meta.path.is_ident("size") { size = true; Ok(()) } else if meta.path.is_ident("compact") { compact = true; Ok(()) } else if meta.path.is_ident("free_immediately") { free_immediately = true; Ok(()) } else if meta.path.is_ident("wb_protected") { wb_protected = true; Ok(()) } else if meta.path.is_ident("frozen_shareable") { frozen_shareable = true; Ok(()) } else if meta.path.is_ident("unsafe_generics") { unsafe_generics = true; Ok(()) } else if meta.path.is_ident("free_immediatly") { Err(meta.error("unsupported attribute (use free_immediately)")) } else { Err(meta.error("unsupported attribute")) } })?; if !input.generics.to_token_stream().is_empty() && !unsafe_generics { let case = if input.generics.type_params().count() > 0 { "containing generic types" } else if input.generics.lifetimes().count() > 0 { "with lifetimes" } else if input.generics.const_params().count() > 0 { "with const generics" } else { "containing generic types" }; return Err(Error::new_spanned( input.generics, format!("deriving TypedData is not guaranteed to be correct for types {}, consider removing them, or use `#[magnus(unsafe_generics)]` to override this error.", case), )); } let class = match class { Some(v) => v, None => return Err(Error::new(attrs.span(), "missing attribute: `class = ...`")), }; let name = name.unwrap_or_else(|| class.clone()); let ident = &input.ident; let generics = &input.generics; let mut arms = Vec::new(); if let Data::Enum(DataEnum { ref variants, .. }) = input.data { for variant in variants.into_iter() { let attrs = match util::get_magnus_attrubute(&variant.attrs)? { Some(v) => v, None => continue, }; let mut class = None; attrs.parse_nested_meta(|meta| { if meta.path.is_ident("class") { class = Some(meta.value()?.parse::()?.value()); Ok(()) } else { Err(meta.error("unsupported attribute")) } })?; let class = match class { Some(v) => v, None => return Err(Error::new(attrs.span(), "missing attribute: `class = ...`")), }; let ident = &variant.ident; let fetch_class = quote! { static CLASS: Lazy = Lazy::new(|ruby| { let class: RClass = ruby.class_object().funcall("const_get", (#class,)).unwrap(); class.undef_default_alloc_func(); class }); ruby.get_inner(&CLASS) }; arms.push(match variant.fields { Fields::Named(_) => quote! { Self::#ident { .. } => { #fetch_class } }, Fields::Unnamed(_) => quote! { Self::#ident(_) => { #fetch_class } }, Fields::Unit => quote! { Self::#ident => #fetch_class }, }); } } let class_for = if !arms.is_empty() { quote! { fn class_for(ruby: &magnus::Ruby, value: &Self) -> magnus::RClass { use magnus::{class, Module, Class, RClass, value::{Lazy, ReprValue}}; #[allow(unreachable_patterns)] match value { #(#arms,)* _ => Self::class(ruby), } } } } else { quote! {} }; let mut accessors = Vec::new(); if let Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { ref named, .. }), .. }) = input.data { for field in named { let attrs = match util::get_magnus_attrubute(&field.attrs)? { Some(v) => v, None => continue, }; let mut read = false; attrs.parse_nested_meta(|meta| { if meta.path.is_ident("opaque_attr_reader") { read = true; Ok(()) } else { Err(meta.error("unsupported attribute")) } })?; let ident = field.ident.as_ref().unwrap(); let ty = &field.ty; if read { accessors.push(quote! { #[inline] fn #ident(&self) -> <#ty as magnus::value::OpaqueVal>::Val { let handle = magnus::Ruby::get().unwrap(); handle.get_inner(self.#ident) } }); } } } let accessor_impl = if !accessors.is_empty() { quote! { impl #ident { #(#accessors)* } } } else { quote! {} }; let mut builder = Vec::new(); builder.push(quote! { magnus::data_type_builder!(#ident, #name) }); if mark { builder.push(quote! { .mark() }); } if size { builder.push(quote! { .size() }); } if compact { builder.push(quote! { .compact() }); } if free_immediately { builder.push(quote! { .free_immediately() }); } if wb_protected { builder.push(quote! { .wb_protected() }); } if frozen_shareable { builder.push(quote! { .frozen_shareable() }); } builder.push(quote! { .build() }); let builder = builder.into_iter().collect::(); let tokens = quote! { #accessor_impl unsafe impl #generics magnus::TypedData for #ident #generics { fn class(ruby: &magnus::Ruby) -> magnus::RClass { use magnus::{class, Module, Class, RClass, value::{Lazy, ReprValue}}; static CLASS: Lazy = Lazy::new(|ruby| { let class: RClass = ruby.class_object().funcall("const_get", (#class,)).unwrap(); class.undef_default_alloc_func(); class }); ruby.get_inner(&CLASS) } fn data_type() -> &'static magnus::DataType { static DATA_TYPE: magnus::DataType = #builder; &DATA_TYPE } #class_for } }; Ok(tokens) } magnus-macros-0.6.0/src/util.rs000064400000000000000000000011411046102023000145120ustar 00000000000000use syn::{spanned::Spanned, Attribute, Error}; pub fn get_magnus_attrubute(attrs: &[Attribute]) -> Result, Error> { let attrs = attrs .iter() .filter(|attr| attr.path().is_ident("magnus")) .collect::>(); if attrs.is_empty() { return Ok(None); } else if attrs.len() > 1 { return Err(attrs .into_iter() .map(|a| Error::new(a.span(), "duplicate attribute")) .reduce(|mut a, b| { a.combine(b); a }) .unwrap()); } Ok(Some(attrs[0])) }