diff --git a/Cargo.toml b/Cargo.toml index 493d74427..308167c4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/dhardy/kas" [features] # Enables usage of unstable Rust features -nightly = [] +nightly = ["kas-macros/nightly"] [dependencies.kas-macros] version = "0.1.0-pre.1" @@ -27,6 +27,10 @@ version = "0.8" version = "0.20.0-alpha5" optional = true +[dependencies] +proc-macro-hack = "0.5" +proc-macro-nested = "0.1" + [workspace] members = ["kas-macros", "kas-wgpu"] diff --git a/kas-macros/Cargo.toml b/kas-macros/Cargo.toml index 72bdc2d9f..9c69ee3be 100644 --- a/kas-macros/Cargo.toml +++ b/kas-macros/Cargo.toml @@ -12,9 +12,14 @@ repository = "https://github.com/dhardy/kas" [lib] proc-macro = true +[features] +# Enables usage of unstable Rust features +nightly = [] + [dependencies] quote = "1.0" proc-macro2 = { version = "1.0", features = ["nightly"] } +proc-macro-hack = "0.5" [dependencies.syn] version = "1.0" diff --git a/kas-macros/src/args.rs b/kas-macros/src/args.rs index 4b25eac81..1d8433f6b 100644 --- a/kas-macros/src/args.rs +++ b/kas-macros/src/args.rs @@ -63,6 +63,7 @@ pub fn read_attrs(ast: &mut DeriveInput) -> Result { if core.is_none() { core = Some(member(i, field.ident.clone())); } else { + #[cfg(feature = "nightly")] attr.span() .unwrap() .error("multiple fields marked with #[core]") @@ -73,6 +74,7 @@ pub fn read_attrs(ast: &mut DeriveInput) -> Result { if field.ty != parse_quote! { ::Data } && field.ty != parse_quote! { ::Data } { + #[cfg(feature = "nightly")] field .ty .span() @@ -82,6 +84,7 @@ pub fn read_attrs(ast: &mut DeriveInput) -> Result { } layout_data = Some(member(i, field.ident.clone())); } else { + #[cfg(feature = "nightly")] attr.span() .unwrap() .error("multiple fields marked with #[layout_data]") @@ -103,6 +106,7 @@ pub fn read_attrs(ast: &mut DeriveInput) -> Result { if widget.is_none() { widget = Some(syn::parse2(attr.tokens)?); } else { + #[cfg(feature = "nightly")] attr.span() .unwrap() .error("multiple #[widget(..)] attributes on type") @@ -112,6 +116,7 @@ pub fn read_attrs(ast: &mut DeriveInput) -> Result { if handler.is_none() { handler = Some(syn::parse2(attr.tokens)?); } else { + #[cfg(feature = "nightly")] attr.span() .unwrap() .error("multiple #[handler(..)] attributes on type") diff --git a/kas-macros/src/lib.rs b/kas-macros/src/lib.rs index f005dff03..30721f408 100644 --- a/kas-macros/src/lib.rs +++ b/kas-macros/src/lib.rs @@ -4,16 +4,18 @@ // https://www.apache.org/licenses/LICENSE-2.0 #![recursion_limit = "128"] -#![feature(proc_macro_diagnostic)] +#![cfg_attr(feature = "nightly", feature(proc_macro_diagnostic))] extern crate proc_macro; mod args; use proc_macro2::{Span, TokenStream}; +use proc_macro_hack::proc_macro_hack; use quote::{quote, TokenStreamExt}; use std::fmt::Write; use syn::punctuated::Punctuated; +#[cfg(feature = "nightly")] use syn::spanned::Spanned; use syn::token::Comma; use syn::{parse_macro_input, parse_quote}; @@ -201,9 +203,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Macro to create a widget with anonymous type /// /// See the [`kas::macros`](../kas/macros/index.html) module documentation. -/// -/// Currently usage of this macro requires `#![feature(proc_macro_hygiene)]`. -#[proc_macro] +#[proc_macro_hack] pub fn make_widget(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let mut find_handler_ty_buf: Vec<(Ident, Type)> = vec![]; // find type of handler's message; return None on error @@ -222,16 +222,19 @@ pub fn make_widget(input: proc_macro::TokenStream) -> proc_macro::TokenStream { for impl_block in impls { for f in &impl_block.1 { if f.sig.ident == *handler { - if let Some(x) = x { + if let Some(_x) = x { + #[cfg(feature = "nightly")] handler .span() .unwrap() .error("multiple methods with this name") .emit(); - x.0.span() + #[cfg(feature = "nightly")] + _x.0.span() .unwrap() .error("first method with this name") .emit(); + #[cfg(feature = "nightly")] f.sig .ident .span() @@ -241,6 +244,7 @@ pub fn make_widget(input: proc_macro::TokenStream) -> proc_macro::TokenStream { return None; } if f.sig.inputs.len() != 3 { + #[cfg(feature = "nightly")] f.sig.span() .unwrap() .error("handler functions must have signature: fn handler(&mut self, tk: &mut dyn TkWindow, msg: T)") @@ -260,6 +264,7 @@ pub fn make_widget(input: proc_macro::TokenStream) -> proc_macro::TokenStream { find_handler_ty_buf.push((handler.clone(), x.1.clone())); Some(x.1) } else { + #[cfg(feature = "nightly")] handler .span() .unwrap() diff --git a/kas-wgpu/examples/calculator.rs b/kas-wgpu/examples/calculator.rs index b205d0a6d..338987902 100644 --- a/kas-wgpu/examples/calculator.rs +++ b/kas-wgpu/examples/calculator.rs @@ -4,7 +4,7 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Simple calculator example (lots of buttons, grid layout) -#![feature(proc_macro_hygiene)] +#![recursion_limit = "1024"] use std::num::ParseFloatError; use std::str::FromStr; @@ -12,9 +12,9 @@ use std::str::FromStr; use kas::class::HasText; use kas::event::VirtualKeyCode as VK; use kas::event::{Response, VoidMsg}; -use kas::macros::{make_widget, VoidMsg}; +use kas::macros::VoidMsg; use kas::widget::{EditBox, TextButton, Window}; -use kas::TkWindow; +use kas::{make_widget, TkWindow}; #[derive(Clone, Debug, VoidMsg)] enum Key { diff --git a/kas-wgpu/examples/clock.rs b/kas-wgpu/examples/clock.rs index 4e5c62782..1a0b4a853 100644 --- a/kas-wgpu/examples/clock.rs +++ b/kas-wgpu/examples/clock.rs @@ -4,7 +4,6 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Clock example (simple periodically updated display) -#![feature(proc_macro_hygiene)] extern crate chrono; @@ -13,9 +12,8 @@ use std::time::Duration; use kas::class::HasText; use kas::event::{Callback, VoidMsg}; -use kas::macros::make_widget; use kas::widget::{Label, Window}; -use kas::{TkWindow, WidgetCore}; +use kas::{make_widget, TkWindow, WidgetCore}; fn main() { let mut window = Window::new( diff --git a/kas-wgpu/examples/clock_expanded.rs b/kas-wgpu/examples/clock_expanded.rs index 7eacb08d0..b6ba5f571 100644 --- a/kas-wgpu/examples/clock_expanded.rs +++ b/kas-wgpu/examples/clock_expanded.rs @@ -4,7 +4,6 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Clock example, with the make_widget macro expanded -#![feature(proc_macro_hygiene)] extern crate chrono; diff --git a/kas-wgpu/examples/counter.rs b/kas-wgpu/examples/counter.rs index 6048026dd..c3d3c00f0 100644 --- a/kas-wgpu/examples/counter.rs +++ b/kas-wgpu/examples/counter.rs @@ -4,13 +4,13 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Counter example (simple button) -#![feature(proc_macro_hygiene)] +#![recursion_limit = "256"] use kas::class::HasText; use kas::event::{VoidMsg, VoidResponse}; -use kas::macros::{make_widget, VoidMsg}; +use kas::macros::VoidMsg; use kas::widget::{Label, TextButton, Window}; -use kas::TkWindow; +use kas::{make_widget, TkWindow}; #[derive(Clone, Debug, VoidMsg)] enum Message { diff --git a/kas-wgpu/examples/dynamic.rs b/kas-wgpu/examples/dynamic.rs index b74eca065..a989e51cd 100644 --- a/kas-wgpu/examples/dynamic.rs +++ b/kas-wgpu/examples/dynamic.rs @@ -4,14 +4,14 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Dynamic widget example -#![feature(proc_macro_hygiene)] +#![recursion_limit = "256"] use kas::class::HasText; use kas::event::{Callback, Response, VoidMsg}; use kas::layout::Vertical; -use kas::macros::{make_widget, VoidMsg}; +use kas::macros::VoidMsg; use kas::widget::{DynVec, EditBox, Label, ScrollRegion, TextButton, Window}; -use kas::TkWindow; +use kas::{make_widget, TkWindow}; #[derive(Clone, Debug, VoidMsg)] enum Control { diff --git a/kas-wgpu/examples/gallery.rs b/kas-wgpu/examples/gallery.rs index 3027d9325..11445b193 100644 --- a/kas-wgpu/examples/gallery.rs +++ b/kas-wgpu/examples/gallery.rs @@ -4,12 +4,12 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Gallery of all widgets -#![feature(proc_macro_hygiene)] +#![recursion_limit = "512"] use kas::event::{VoidMsg, VoidResponse}; -use kas::macros::{make_widget, VoidMsg}; +use kas::macros::VoidMsg; use kas::widget::*; -use kas::TkWindow; +use kas::{make_widget, TkWindow}; #[derive(Clone, Debug, VoidMsg)] enum Item { diff --git a/kas-wgpu/examples/layout.rs b/kas-wgpu/examples/layout.rs index 08b7f66c2..a81616206 100644 --- a/kas-wgpu/examples/layout.rs +++ b/kas-wgpu/examples/layout.rs @@ -4,10 +4,10 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Gallery of all widgets -#![feature(proc_macro_hygiene)] +#![recursion_limit = "256"] use kas::event::VoidMsg; -use kas::macros::make_widget; +use kas::make_widget; use kas::widget::{CheckBox, EditBox, Label, Window}; fn main() -> Result<(), winit::error::OsError> { diff --git a/kas-wgpu/examples/stopwatch.rs b/kas-wgpu/examples/stopwatch.rs index 2de80f934..bc7b7fd82 100644 --- a/kas-wgpu/examples/stopwatch.rs +++ b/kas-wgpu/examples/stopwatch.rs @@ -4,16 +4,16 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Counter example (simple button) -#![feature(proc_macro_hygiene)] +#![recursion_limit = "512"] use std::fmt::Write; use std::time::{Duration, Instant}; use kas::class::HasText; use kas::event::{Callback, Response, VoidMsg}; -use kas::macros::{make_widget, VoidMsg}; +use kas::macros::VoidMsg; use kas::widget::{Label, TextButton, Window}; -use kas::TkWindow; +use kas::{make_widget, TkWindow}; #[derive(Clone, Debug, VoidMsg)] enum Control { diff --git a/kas-wgpu/examples/theme.rs b/kas-wgpu/examples/theme.rs index 086a4b825..863cd1366 100644 --- a/kas-wgpu/examples/theme.rs +++ b/kas-wgpu/examples/theme.rs @@ -4,16 +4,16 @@ // https://www.apache.org/licenses/LICENSE-2.0 //! Custom theme demo -#![feature(proc_macro_hygiene)] +#![recursion_limit = "256"] use std::cell::Cell; use kas::draw::Colour; use kas::event::{VoidMsg, VoidResponse}; -use kas::macros::{make_widget, VoidMsg}; +use kas::macros::VoidMsg; use kas::theme::Theme; use kas::widget::*; -use kas::TkWindow; +use kas::{make_widget, TkWindow}; use kas_wgpu::draw::*; use kas_wgpu::glyph::Font; diff --git a/src/lib.rs b/src/lib.rs index 16162da1f..ad545b6b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,3 +45,9 @@ pub mod macros; pub use crate::data::*; pub use crate::toolkit::*; pub use crate::traits::*; + +use proc_macro_hack::proc_macro_hack; + +/// See [The `make_widget` macro]: #the-make_widget-macro +#[proc_macro_hack(support_nested)] +pub use kas_macros::make_widget; diff --git a/src/macros.rs b/src/macros.rs index 26a0b21d1..ab435406b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -21,12 +21,6 @@ //! because procedural macros must be defined in a special crate. The //! `kas-macros` crate should not be used directly. //! -//! Note further that these macros require gated functionality only available -//! in nightly `rustc` builds: -//! ``` -//! #![feature(proc_macro_hygiene)] -//! ``` -//! //! [`make_widget`]: #the-make_widget-macro //! [`derive(Widget)`]: #the-derivewidget-macro //! [`derive(VoidMsg)`]: #the-derivevoidmsg-macro @@ -215,9 +209,7 @@ //! ### Example //! //! ``` -//! #![feature(proc_macro_hygiene)] -//! -//! use kas::macros::{make_widget}; +//! use kas::make_widget; //! use kas::widget::TextButton; //! //! #[derive(Copy, Clone, Debug)] @@ -257,4 +249,4 @@ //! [`Handler`]: crate::event::Handler //! [`Handler::Msg`]: crate::event::Handler::Msg -pub use kas_macros::{make_widget, VoidMsg, Widget}; +pub use kas_macros::{VoidMsg, Widget};