//! 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() }