From 65c048e27a1d34704d5b6b8a2cdfb54fd465c263 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 6 Oct 2022 11:20:36 +0100 Subject: [PATCH 1/6] Use impl-tools-lib 0.5.2; update doc for impl_scope --- crates/kas-macros/Cargo.toml | 2 +- crates/kas-macros/src/lib.rs | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/kas-macros/Cargo.toml b/crates/kas-macros/Cargo.toml index d1ca8885e..58d215b69 100644 --- a/crates/kas-macros/Cargo.toml +++ b/crates/kas-macros/Cargo.toml @@ -26,7 +26,7 @@ proc-macro-error = "1.0" bitflags = "1.3.1" [dependencies.impl-tools-lib] -version = "0.5.1" # version used in doc links +version = "0.5.2" # version used in doc links [dependencies.syn] version = "1.0.14" diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 32706f63f..dba185551 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -113,10 +113,31 @@ pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { const IMPL_SCOPE_RULES: [&'static dyn ScopeAttr; 2] = [&AttrImplDefault, &widget::AttrImplWidget]; -/// Implementation scope +/// Scope supporting `impl Self` and advanced attribute macros +/// +/// This macro facilitates definition of a type (struct, enum or union) plus +/// implementations via `impl Self { .. }` syntax: `Self` is expanded to the +/// type's name, including generics and bounds (as defined on the type). +/// +/// Caveat: `rustfmt` can not yet format contents (see +/// [rustfmt#5254](https://github.com/rust-lang/rustfmt/issues/5254), +/// [rustfmt#5538](https://github.com/rust-lang/rustfmt/pull/5538)). /// /// See [`impl_tools::impl_scope`](https://docs.rs/impl-tools/0.5/impl_tools/macro.impl_scope.html) /// for full documentation. +/// +/// ## Special attribute macros +/// +/// Additionally, `impl_scope!` supports special attribute macros evaluated +/// within its scope: +/// +/// - [`#[impl_default]`](macro@impl_default): implement [`Default`] using +/// field initializers (which are not legal syntax outside of `impl_scope!`) +/// - [`#[widget]`](macro@widget): implement `kas::Widget` trait family +/// +/// Note: matching these macros within `impl_scope!` does not use path +/// resolution. Using `#[impl_tools::impl_default]` would resolve the variant +/// of this macro which *doesn't support* field initializers. #[proc_macro_error] #[proc_macro] pub fn impl_scope(input: TokenStream) -> TokenStream { From 4235084360e0da8649fa070b19f4bce093834bb2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 6 Oct 2022 12:14:09 +0100 Subject: [PATCH 2/6] Rename impl_singleton! -> singleton! --- crates/kas-core/src/prelude.rs | 2 +- crates/kas-macros/src/lib.rs | 8 ++++---- examples/data-list-view.rs | 4 ++-- examples/data-list.rs | 4 ++-- examples/gallery.rs | 14 +++++++------- examples/layout.rs | 3 +-- examples/splitter.rs | 3 +-- examples/stopwatch.rs | 3 +-- examples/times-tables.rs | 2 +- 9 files changed, 20 insertions(+), 23 deletions(-) diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index 7c94bf13a..79ddead9f 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -21,7 +21,7 @@ pub use crate::geom::{Coord, Offset, Rect, Size}; #[doc(no_inline)] pub use crate::layout::{Align, AlignPair, AxisInfo, SizeRules, Stretch}; #[doc(no_inline)] -pub use crate::macros::{autoimpl, impl_default, impl_scope, impl_singleton, widget, widget_index}; +pub use crate::macros::{autoimpl, impl_default, impl_scope, singleton, widget, widget_index}; #[doc(no_inline)] pub use crate::text::AccelString; #[doc(no_inline)] diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index dba185551..31c95e4c5 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -391,9 +391,9 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { /// Example: /// ``` /// use std::fmt; -/// use kas_macros::impl_singleton; +/// use kas_macros::singleton; /// fn main() { -/// let says_hello_world = impl_singleton! { +/// let says_hello_world = singleton! { /// struct(impl fmt::Display = "world"); /// impl fmt::Display for Self { /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -424,10 +424,10 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { /// type. In the special case that the field has attribute `#[widget]` and type /// `_`, the generic for this type has bound `::kas::Widget` applied. /// -/// Refer to [examples](https://github.com/search?q=impl_singleton+repo%3Akas-gui%2Fkas+path%3Aexamples&type=Code) for usage. +/// Refer to [examples](https://github.com/search?q=singleton+repo%3Akas-gui%2Fkas+path%3Aexamples&type=Code) for usage. #[proc_macro_error] #[proc_macro] -pub fn impl_singleton(input: TokenStream) -> TokenStream { +pub fn singleton(input: TokenStream) -> TokenStream { let args = parse_macro_input!(input as args::ImplSingleton); impl_singleton::impl_singleton(args) .unwrap_or_else(|err| err.to_compile_error()) diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index 260959ff5..bd0ade2c2 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -215,7 +215,7 @@ impl Driver<(bool, String), MySharedData> for MyDriver { fn main() -> kas::shell::Result<()> { env_logger::init(); - let controls = impl_singleton! { + let controls = singleton! { #[widget{ layout = row: [ "Number of rows:", @@ -266,7 +266,7 @@ fn main() -> kas::shell::Result<()> { type MyList = ListView; let list = ListView::new_with_dir_driver(Direction::Down, driver, data); - let window = impl_singleton! { + let window = singleton! { #[widget{ layout = column: [ "Demonstration of dynamic widget creation / deletion", diff --git a/examples/data-list.rs b/examples/data-list.rs index 21995e2b9..f7c360437 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -95,7 +95,7 @@ impl ListEntry { fn main() -> kas::shell::Result<()> { env_logger::init(); - let controls = impl_singleton! { + let controls = singleton! { #[widget{ layout = row: [ "Number of rows:", @@ -146,7 +146,7 @@ fn main() -> kas::shell::Result<()> { ]; let list = List::new_dir_vec(Direction::Down, entries); - let window = impl_singleton! { + let window = singleton! { #[widget{ layout = column: [ "Demonstration of dynamic widget creation / deletion", diff --git a/examples/gallery.rs b/examples/gallery.rs index e617372e7..1dea835c2 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -85,7 +85,7 @@ fn widgets() -> Box { #[derive(Clone, Debug)] struct MsgEdit; - let popup_edit_box = impl_singleton! { + let popup_edit_box = singleton! { #[widget{ layout = row: [ self.label, @@ -116,7 +116,7 @@ fn widgets() -> Box { let radio = RadioGroup::new(); - let widgets = impl_singleton! { + let widgets = singleton! { #[widget{ layout = aligned_column: [ row: ["ScrollLabel", self.sl], @@ -236,7 +236,7 @@ Demonstration of *as-you-type* formatting from **Markdown**. ``` "; - Box::new(impl_singleton! { + Box::new(singleton! { #[widget{ layout = float: [ pack(right, top): TextButton::new_msg("↻", MsgDirection), @@ -307,7 +307,7 @@ fn filter_list() -> Box { let r = RadioGroup::new(); - Box::new(impl_singleton! { + Box::new(singleton! { #[widget{ layout = column: [ row: ["Selection:", self.r0, self.r1, self.r2], @@ -417,7 +417,7 @@ fn canvas() -> Box { } } - Box::new(impl_singleton! { + Box::new(singleton! { #[widget{ layout = column: [ Label::new("Animated canvas demo @@ -453,7 +453,7 @@ KAS_CONFIG_MODE=readwrite ``` "; - Box::new(ScrollBarRegion::new(impl_singleton! { + Box::new(ScrollBarRegion::new(singleton! { #[widget{ layout = column: [ ScrollLabel::new(Markdown::new(DESC).unwrap()), @@ -538,7 +538,7 @@ fn main() -> Result<(), Box> { }) .build(); - let window = impl_singleton! { + let window = singleton! { #[widget{ layout = column: [ self.menubar, diff --git a/examples/layout.rs b/examples/layout.rs index f8d7429fe..8429f2cf4 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -5,7 +5,6 @@ //! Demonstration of widget and text layouts -use kas::macros::impl_singleton; use kas::widgets::{CheckBox, EditBox, ScrollLabel}; const LIPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nunc mi, consequat eget urna ut, auctor luctus mi. Sed molestie mi est. Sed non ligula ante. Curabitur ac molestie ante, nec sodales eros. In non arcu at turpis euismod bibendum ut tincidunt eros. Suspendisse blandit maximus nisi, viverra hendrerit elit efficitur et. Morbi ut facilisis eros. Vivamus dignissim, sapien sed mattis consectetur, libero leo imperdiet turpis, ac pulvinar libero purus eu lorem. Etiam quis sollicitudin urna. Integer vitae erat vel neque gravida blandit ac non quam."; @@ -14,7 +13,7 @@ const CRASIT: &str = "Cras sit amet justo ipsum. Aliquam in nunc posuere leo ege fn main() -> kas::shell::Result<()> { env_logger::init(); - let window = impl_singleton! { + let window = kas::macros::singleton! { #[widget{ layout = grid: { 1, 0: "Layout demo"; diff --git a/examples/splitter.rs b/examples/splitter.rs index da083dd3b..359df42f2 100644 --- a/examples/splitter.rs +++ b/examples/splitter.rs @@ -6,7 +6,6 @@ //! Counter example (simple button) use kas::event::EventMgr; -use kas::macros::impl_singleton; use kas::widgets::{EditField, RowSplitter, TextButton}; use kas::{Widget, Window}; @@ -22,7 +21,7 @@ fn main() -> kas::shell::Result<()> { let panes = (0..2).map(|n| EditField::new(format!("Pane {}", n + 1)).with_multi_line(true)); let panes = RowSplitter::::new(panes.collect()); - let window = impl_singleton! { + let window = kas::macros::singleton! { #[widget{ layout = column: [ row: [ diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index 127cc7027..6dacc4968 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -9,7 +9,6 @@ use std::time::{Duration, Instant}; use kas::class::HasString; use kas::event::{ConfigMgr, Event, EventMgr, Response}; -use kas::macros::impl_singleton; use kas::widgets::{Frame, Label, TextButton}; use kas::{Widget, WidgetCore, WidgetExt, Window}; @@ -21,7 +20,7 @@ struct MsgStart; // Unlike most examples, we encapsulate the GUI configuration into a function. // There's no reason for this, but it demonstrates usage of Toolkit::add_boxed fn make_window() -> Box { - Box::new(impl_singleton! { + Box::new(kas::macros::singleton! { #[widget{ layout = row: [ self.display, diff --git a/examples/times-tables.rs b/examples/times-tables.rs index af70e4b88..dd1dc49bd 100644 --- a/examples/times-tables.rs +++ b/examples/times-tables.rs @@ -67,7 +67,7 @@ fn main() -> kas::shell::Result<()> { .with_selection_mode(SelectionMode::Single); let table = ScrollBars::new(table); - let window = impl_singleton! { + let window = singleton! { #[widget{ layout = column: [ row: ["From 1 to", self.max], From 88e358dcc1d3855422e3906ed66aa94040cabd2d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 6 Oct 2022 12:39:16 +0100 Subject: [PATCH 3/6] Clean up kas_macros::args module --- crates/kas-macros/src/args.rs | 323 +----------------------- crates/kas-macros/src/impl_singleton.rs | 228 ----------------- crates/kas-macros/src/lib.rs | 50 ++-- 3 files changed, 30 insertions(+), 571 deletions(-) delete mode 100644 crates/kas-macros/src/impl_singleton.rs diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index 236896ee9..173db090e 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -5,166 +5,24 @@ use crate::make_layout; use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort; use quote::quote_spanned; use syn::parse::{Error, Parse, ParseStream, Result}; -use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::token::{Brace, Colon, Comma, Eq, Paren, Semi}; -use syn::{braced, bracketed, parenthesized, parse_quote}; -use syn::{Attribute, Expr, Generics, Ident, ItemImpl, Member, Path, Token, Type}; +use syn::{token::Eq, Member, Token}; #[derive(Debug)] pub struct Child { pub ident: Member, } -fn parse_impl(in_ident: Option<&Ident>, input: ParseStream) -> Result { - let mut attrs = input.call(Attribute::parse_outer)?; - let defaultness: Option = input.parse()?; - let unsafety: Option = input.parse()?; - let impl_token: Token![impl] = input.parse()?; - - let has_generics = input.peek(Token![<]) - && (input.peek2(Token![>]) - || input.peek2(Token![#]) - || (input.peek2(Ident) || input.peek2(syn::Lifetime)) - && (input.peek3(Token![:]) - || input.peek3(Token![,]) - || input.peek3(Token![>]) - || input.peek3(Token![=])) - || input.peek2(Token![const])); - let mut generics: Generics = if has_generics { - input.parse()? - } else { - Generics::default() - }; - - let mut first_ty: Type = input.parse()?; - let self_ty: Type; - let trait_; - - let is_impl_for = input.peek(Token![for]); - if is_impl_for { - let for_token: Token![for] = input.parse()?; - let mut first_ty_ref = &first_ty; - while let Type::Group(ty) = first_ty_ref { - first_ty_ref = &ty.elem; - } - if let Type::Path(_) = first_ty_ref { - while let Type::Group(ty) = first_ty { - first_ty = *ty.elem; - } - if let Type::Path(syn::TypePath { qself: None, path }) = first_ty { - trait_ = Some((None, path, for_token)); - } else { - unreachable!(); - } - } else { - return Err(Error::new(for_token.span(), "for without target trait")); - } - self_ty = input.parse()?; - } else { - trait_ = None; - self_ty = first_ty; - } - - generics.where_clause = input.parse()?; - - if self_ty != parse_quote! { Self } { - if let Some(ident) = in_ident { - if !matches!(self_ty, Type::Path(syn::TypePath { - qself: None, - path: Path { - leading_colon: None, - ref segments, - } - }) if segments.len() == 1 && segments.first().unwrap().ident == *ident) - { - abort!( - self_ty.span(), - format!( - "expected `Self` or `{0}` or `{0}<...>` or `Trait for Self`, etc", - ident - ) - ); - } - } else { - abort!(self_ty.span(), "expected `Self` or `Trait for Self`"); - } - } - - let content; - let brace_token = braced!(content in input); - parse_attrs_inner(&content, &mut attrs)?; - - let mut items = Vec::new(); - while !content.is_empty() { - items.push(content.parse()?); - } - - Ok(ItemImpl { - attrs, - defaultness, - unsafety, - impl_token, - generics, - trait_, - self_ty: Box::new(self_ty), - brace_token, - items, - }) -} - -fn parse_attrs_inner(input: ParseStream, attrs: &mut Vec) -> Result<()> { - while input.peek(Token![#]) && input.peek2(Token![!]) { - let pound_token = input.parse()?; - let style = syn::AttrStyle::Inner(input.parse()?); - let content; - let bracket_token = bracketed!(content in input); - let path = content.call(Path::parse_mod_style)?; - let tokens = content.parse()?; - attrs.push(Attribute { - pound_token, - style, - bracket_token, - path, - tokens, - }); - } - Ok(()) -} - #[allow(non_camel_case_types)] mod kw { use syn::custom_keyword; custom_keyword!(layout); - custom_keyword!(col); - custom_keyword!(row); - custom_keyword!(cspan); - custom_keyword!(rspan); - custom_keyword!(widget); - custom_keyword!(handler); - custom_keyword!(generics); - custom_keyword!(single); - custom_keyword!(right); - custom_keyword!(left); - custom_keyword!(down); - custom_keyword!(up); - custom_keyword!(grid); - custom_keyword!(align); - custom_keyword!(halign); - custom_keyword!(valign); custom_keyword!(navigable); custom_keyword!(hover_highlight); custom_keyword!(cursor_icon); - custom_keyword!(handle); - custom_keyword!(send); - custom_keyword!(config); - custom_keyword!(children); - custom_keyword!(column); - custom_keyword!(draw); custom_keyword!(derive); } @@ -254,182 +112,3 @@ impl Parse for WidgetArgs { }) } } - -#[derive(Debug)] -pub enum StructStyle { - Unit(Semi), - Tuple(Paren, Semi), - Regular(Brace), -} - -#[derive(Debug)] -pub struct SingletonField { - pub attrs: Vec, - pub vis: syn::Visibility, - pub ident: Option, - pub colon_token: Option, - pub ty: Type, - pub assignment: Option<(Eq, Expr)>, -} - -#[derive(Debug)] -pub struct ImplSingleton { - pub attrs: Vec, - pub token: Token![struct], - pub generics: Generics, - pub style: StructStyle, - pub fields: Punctuated, - pub impls: Vec, -} - -impl Parse for ImplSingleton { - fn parse(input: ParseStream) -> Result { - let attrs = input.call(Attribute::parse_outer)?; - let token = input.parse::()?; - - let mut generics = input.parse::()?; - - let mut lookahead = input.lookahead1(); - if lookahead.peek(Token![where]) { - generics.where_clause = Some(input.parse()?); - lookahead = input.lookahead1(); - } - - let style; - let fields; - if generics.where_clause.is_none() && lookahead.peek(Paren) { - let content; - let paren_token = parenthesized!(content in input); - fields = content.parse_terminated(SingletonField::parse_unnamed)?; - - lookahead = input.lookahead1(); - if lookahead.peek(Token![where]) { - generics.where_clause = Some(input.parse()?); - lookahead = input.lookahead1(); - } - - if lookahead.peek(Semi) { - style = StructStyle::Tuple(paren_token, input.parse()?); - } else { - return Err(lookahead.error()); - } - } else if lookahead.peek(Brace) { - let content; - let brace_token = braced!(content in input); - style = StructStyle::Regular(brace_token); - fields = content.parse_terminated(SingletonField::parse_named)?; - } else if lookahead.peek(Semi) { - style = StructStyle::Unit(input.parse()?); - fields = Punctuated::new(); - } else { - return Err(lookahead.error()); - } - - let mut impls = Vec::new(); - while !input.is_empty() { - impls.push(parse_impl(None, input)?); - } - - Ok(ImplSingleton { - attrs, - token, - generics, - style, - fields, - impls, - }) - } -} - -impl SingletonField { - fn check_is_fixed(ty: &Type, input_span: Span) -> Result<()> { - let is_fixed = match ty { - Type::ImplTrait(_) | Type::Infer(_) => false, - ty => { - struct IsFixed(bool); - let mut checker = IsFixed(true); - - impl<'ast> syn::visit::Visit<'ast> for IsFixed { - fn visit_type(&mut self, node: &'ast Type) { - if matches!(node, Type::ImplTrait(_) | Type::Infer(_)) { - self.0 = false; - } - } - } - syn::visit::visit_type(&mut checker, ty); - - checker.0 - } - }; - - if is_fixed { - Ok(()) - } else { - Err(Error::new( - input_span, - "require either a fixed type or a value assignment", - )) - } - } - - fn parse_named(input: ParseStream) -> Result { - let attrs = input.call(Attribute::parse_outer)?; - let vis = input.parse()?; - - let ident = if input.peek(Token![_]) { - let _: Token![_] = input.parse()?; - None - } else { - Some(input.parse::()?) - }; - - let mut colon_token = None; - - // Note: Colon matches `::` but that results in confusing error messages - let ty = if input.peek(Colon) && !input.peek2(Colon) { - colon_token = Some(input.parse()?); - input.parse()? - } else { - parse_quote! { _ } - }; - - let mut assignment = None; - if let Ok(eq) = input.parse::() { - assignment = Some((eq, input.parse()?)); - } else { - Self::check_is_fixed(&ty, input.span())?; - } - - Ok(SingletonField { - attrs, - vis, - ident, - colon_token, - ty, - assignment, - }) - } - - fn parse_unnamed(input: ParseStream) -> Result { - let attrs = input.call(Attribute::parse_outer)?; - let vis = input.parse()?; - - let ty = input.parse()?; - - let mut assignment = None; - if let Ok(eq) = input.parse::() { - assignment = Some((eq, input.parse()?)); - } else { - Self::check_is_fixed(&ty, input.span())?; - } - - Ok(SingletonField { - attrs, - vis, - ident: None, - colon_token: None, - ty, - assignment, - }) - } -} diff --git a/crates/kas-macros/src/impl_singleton.rs b/crates/kas-macros/src/impl_singleton.rs deleted file mode 100644 index 377c8d642..000000000 --- a/crates/kas-macros/src/impl_singleton.rs +++ /dev/null @@ -1,228 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License in the LICENSE-APACHE file or at: -// https://www.apache.org/licenses/LICENSE-2.0 - -use crate::args::{ImplSingleton, StructStyle}; -use impl_tools_lib::{ - fields::{Field, Fields, FieldsNamed, FieldsUnnamed}, - Scope, ScopeItem, -}; -use proc_macro2::{Span, TokenStream}; -use quote::{quote, TokenStreamExt}; -use std::fmt::Write; -use syn::parse_quote; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::token::Comma; -use syn::{visit_mut, GenericParam, Ident, Member, Result, Type, TypeParam, TypePath, Visibility}; - -pub(crate) fn impl_singleton(mut args: ImplSingleton) -> Result { - // Used to make fresh identifiers for generic types - let mut name_buf = String::with_capacity(32); - let mut make_ident = move |args: std::fmt::Arguments, span| -> Ident { - name_buf.clear(); - name_buf.write_fmt(args).unwrap(); - Ident::new(&name_buf, span) - }; - - let mut fields = Punctuated::::new(); - let mut field_val_toks = quote! {}; - - for (index, pair) in args.fields.into_pairs().enumerate() { - let (field, opt_comma) = pair.into_tuple(); - - let mut ident = field.ident.clone(); - let ty = &field.ty; - let field_span = match field.assignment { - None => quote! { #ty }.span(), - Some((ref eq, ref expr)) => quote! { #ty #eq #expr }.span(), - }; - let mem = match args.style { - StructStyle::Regular(_) => { - let id = - ident.unwrap_or_else(|| make_ident(format_args!("_field{index}"), field_span)); - ident = Some(id.clone()); - Member::Named(id) - } - StructStyle::Tuple(_, _) => Member::Unnamed(syn::Index { - index: index as u32, - span: field_span, - }), - _ => unreachable!(), - }; - let ty_name = match ident { - None => format!("_Field{index}"), - Some(ref id) => { - let ident = id.to_string(); - let mut buf = "_Field".to_string(); - buf.reserve(ident.len()); - let mut next_upper = true; - for c in ident.chars() { - if c == '_' { - next_upper = true; - continue; - } - - if next_upper { - buf.extend(c.to_uppercase()); - next_upper = false; - } else { - buf.push(c); - } - } - buf - } - }; - - let ty: Type = match field.ty { - Type::ImplTrait(syn::TypeImplTrait { impl_token, bounds }) => { - let span = quote! { #impl_token #bounds }.span(); - let ty = Ident::new(&ty_name, span); - - args.generics.params.push(parse_quote! { #ty: #bounds }); - - Type::Path(TypePath { - qself: None, - path: ty.into(), - }) - } - Type::Infer(infer_token) => { - // This is a special case: add ::kas::Widget bound - let is_widget = field - .attrs - .iter() - .any(|attr| (attr.path == parse_quote! { widget })); - - let ty = Ident::new(&ty_name, infer_token.span()); - args.generics.params.push(if is_widget { - parse_quote! { #ty: ::kas::Widget } - } else { - parse_quote! { #ty } - }); - - Type::Path(TypePath { - qself: None, - path: ty.into(), - }) - } - mut ty => { - struct ReplaceInfers<'a, F: FnMut(std::fmt::Arguments, Span) -> Ident> { - index: usize, - params: Vec, - make_ident: &'a mut F, - ty_name: &'a str, - } - let mut replacer = ReplaceInfers { - index: 0, - params: vec![], - make_ident: &mut make_ident, - ty_name: &ty_name, - }; - - impl<'a, F: FnMut(std::fmt::Arguments, Span) -> Ident> visit_mut::VisitMut - for ReplaceInfers<'a, F> - { - fn visit_type_mut(&mut self, node: &mut Type) { - let (span, bounds) = match node { - Type::ImplTrait(syn::TypeImplTrait { impl_token, bounds }) => { - (impl_token.span, std::mem::take(bounds)) - } - Type::Infer(infer) => (infer.span(), Punctuated::new()), - _ => return, - }; - - let ident = - (self.make_ident)(format_args!("{}{}", self.ty_name, self.index), span); - self.index += 1; - - self.params.push(GenericParam::Type(TypeParam { - attrs: vec![], - ident: ident.clone(), - colon_token: Some(Default::default()), - bounds, - eq_token: None, - default: None, - })); - - *node = Type::Path(TypePath { - qself: None, - path: ident.into(), - }); - } - } - visit_mut::visit_type_mut(&mut replacer, &mut ty); - - args.generics.params.extend(replacer.params); - ty - } - }; - - if let Some((_, ref value)) = field.assignment { - field_val_toks.append_all(quote! { #mem: #value, }); - } else { - field_val_toks.append_all(quote! { #mem: Default::default(), }); - } - - fields.push_value(Field { - attrs: field.attrs, - vis: field.vis, - ident, - colon_token: field.colon_token.or_else(|| Some(Default::default())), - ty, - assign: None, - }); - if let Some(comma) = opt_comma { - fields.push_punct(comma); - } - } - - let (fields, semi) = match args.style { - StructStyle::Unit(semi) => (Fields::Unit, Some(semi)), - StructStyle::Regular(brace_token) => ( - Fields::Named(FieldsNamed { - brace_token, - fields, - }), - None, - ), - StructStyle::Tuple(paren_token, semi) => ( - Fields::Unnamed(FieldsUnnamed { - paren_token, - fields, - }), - Some(semi), - ), - }; - - let mut scope = Scope { - attrs: args.attrs, - vis: Visibility::Inherited, - ident: parse_quote! { _Singleton }, - generics: args.generics, - item: ScopeItem::Struct { - token: args.token, - fields, - }, - semi, - impls: args.impls, - generated: vec![], - }; - scope.apply_attrs(|path| { - crate::IMPL_SCOPE_RULES - .iter() - .cloned() - .find(|rule| rule.path().matches(path)) - }); - scope.expand_impl_self(); - - let toks = quote! { { - #scope - - _Singleton { - #field_val_toks - } - } }; - - Ok(toks) -} diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 31c95e4c5..82b879d3f 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -9,16 +9,15 @@ extern crate proc_macro; -use impl_tools_lib::autoimpl; -use impl_tools_lib::{AttrImplDefault, ImplDefault, Scope, ScopeAttr}; +use impl_tools_lib::{self as lib, autoimpl}; use proc_macro::TokenStream; use proc_macro_error::{emit_call_site_error, proc_macro_error}; -use syn::parse_macro_input; +use syn::spanned::Spanned; +use syn::{parse_macro_input, parse_quote_spanned}; mod args; mod class_traits; mod extends; -mod impl_singleton; mod make_layout; mod widget; mod widget_index; @@ -31,7 +30,7 @@ mod widget_index; #[proc_macro_error] pub fn impl_default(attr: TokenStream, item: TokenStream) -> TokenStream { let mut toks = item.clone(); - match syn::parse::(attr) { + match syn::parse::(attr) { Ok(attr) => toks.extend(TokenStream::from(attr.expand(item.into()))), Err(err) => { emit_call_site_error!(err); @@ -111,7 +110,14 @@ pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { toks } -const IMPL_SCOPE_RULES: [&'static dyn ScopeAttr; 2] = [&AttrImplDefault, &widget::AttrImplWidget]; +const IMPL_SCOPE_RULES: [&dyn lib::ScopeAttr; 2] = [&lib::AttrImplDefault, &widget::AttrImplWidget]; + +fn find_attr(path: &syn::Path) -> Option<&'static dyn lib::ScopeAttr> { + IMPL_SCOPE_RULES + .iter() + .cloned() + .find(|rule| rule.path().matches(path)) +} /// Scope supporting `impl Self` and advanced attribute macros /// @@ -141,13 +147,8 @@ const IMPL_SCOPE_RULES: [&'static dyn ScopeAttr; 2] = [&AttrImplDefault, &widget #[proc_macro_error] #[proc_macro] pub fn impl_scope(input: TokenStream) -> TokenStream { - let mut scope = parse_macro_input!(input as Scope); - scope.apply_attrs(|path| { - IMPL_SCOPE_RULES - .iter() - .cloned() - .find(|rule| rule.path().matches(path)) - }); + let mut scope = parse_macro_input!(input as lib::Scope); + scope.apply_attrs(find_attr); scope.expand().into() } @@ -383,7 +384,7 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { item } -/// Create a widget singleton +/// Construct a single-instance struct /// /// Rust doesn't currently support [`impl Trait { ... }` expressions](https://github.com/canndrew/rfcs/blob/impl-trait-expressions/text/0000-impl-trait-expressions.md) /// or implicit typing of struct fields. This macro is a **hack** allowing that. @@ -391,10 +392,10 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { /// Example: /// ``` /// use std::fmt; -/// use kas_macros::singleton; /// fn main() { -/// let says_hello_world = singleton! { -/// struct(impl fmt::Display = "world"); +/// let world = "world"; +/// let says_hello_world = impl_tools::singleton! { +/// struct(&'static str = world); /// impl fmt::Display for Self { /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { /// write!(f, "hello {}", self.0) @@ -428,10 +429,17 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_error] #[proc_macro] pub fn singleton(input: TokenStream) -> TokenStream { - let args = parse_macro_input!(input as args::ImplSingleton); - impl_singleton::impl_singleton(args) - .unwrap_or_else(|err| err.to_compile_error()) - .into() + let mut input = parse_macro_input!(input as lib::Singleton); + for field in input.fields.iter_mut() { + if let syn::Type::Infer(token) = &field.ty { + field.ty = parse_quote_spanned! {token.span()=> + impl ::kas::Widget + }; + } + } + let mut scope = input.into_scope(); + scope.apply_attrs(find_attr); + scope.expand().into() } /// Index of a child widget From 41c9432c5c77d3dd4a70b09de26c480af7939a77 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 6 Oct 2022 12:42:00 +0100 Subject: [PATCH 4/6] Remove kas_macros::args::Child --- crates/kas-macros/src/args.rs | 5 ----- crates/kas-macros/src/make_layout.rs | 7 +++---- crates/kas-macros/src/widget.rs | 7 +++---- crates/kas-macros/src/widget_index.rs | 7 +++---- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index 173db090e..89d435fe6 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -10,11 +10,6 @@ use syn::parse::{Error, Parse, ParseStream, Result}; use syn::spanned::Spanned; use syn::{token::Eq, Member, Token}; -#[derive(Debug)] -pub struct Child { - pub ident: Member, -} - #[allow(non_camel_case_types)] mod kw { use syn::custom_keyword; diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 7f84025bd..f44643cd1 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -3,7 +3,6 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use crate::args::Child; use proc_macro2::{Span, TokenStream as Toks}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::parse::{Error, Parse, ParseStream, Result}; @@ -60,7 +59,7 @@ impl Tree { self.0.generate(core) } - pub fn nav_next(&self, children: &[Child]) -> NavNextResult { + pub fn nav_next(&self, children: &[Member]) -> NavNextResult { match &self.0 { Layout::Slice(_, dir, _) => NavNextResult::Slice(dir.to_token_stream()), layout => { @@ -869,7 +868,7 @@ impl Layout { /// - `index`: the next widget's index fn nav_next( &self, - children: &[Child], + children: &[Member], output: &mut Vec, index: &mut usize, ) -> std::result::Result<(), &'static str> { @@ -887,7 +886,7 @@ impl Layout { } Layout::AlignSingle(m, _) | Layout::Single(m) => { for (i, child) in children.iter().enumerate() { - if m.member == child.ident { + if m.member == *child { output.push(i); return Ok(()); } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 58e67296b..a963d7570 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -3,7 +3,7 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use crate::args::{Child, WidgetArgs}; +use crate::args::WidgetArgs; use crate::make_layout::NavNextResult; use impl_tools_lib::fields::{Fields, FieldsNamed, FieldsUnnamed}; use impl_tools_lib::{Scope, ScopeAttr, ScopeItem, SimplePath}; @@ -147,8 +147,7 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { emit_error!(attr, "#[widget] must not be used on widget derive target"); } is_widget = true; - let ident = ident.clone(); - children.push(Child { ident }); + children.push(ident.clone()); } else { other_attrs.push(attr); } @@ -457,7 +456,7 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { let mut get_rules = quote! {}; let mut get_mut_rules = quote! {}; for (i, child) in children.iter().enumerate() { - let ident = &child.ident; + let ident = child; get_rules.append_all(quote! { #i => Some(&self.#ident), }); get_mut_rules.append_all(quote! { #i => Some(&mut self.#ident), }); } diff --git a/crates/kas-macros/src/widget_index.rs b/crates/kas-macros/src/widget_index.rs index f56a9408a..e858e1d7c 100644 --- a/crates/kas-macros/src/widget_index.rs +++ b/crates/kas-macros/src/widget_index.rs @@ -3,7 +3,6 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use crate::args::Child; use proc_macro2::Span; use proc_macro_error::emit_error; use syn::parse::{Parse, ParseStream}; @@ -53,7 +52,7 @@ impl Parse for WidgetInput { } struct Visitor<'a> { - children: &'a [Child], + children: &'a [Member], } impl<'a> VisitMut for Visitor<'a> { fn visit_macro_mut(&mut self, node: &mut syn::Macro) { @@ -73,7 +72,7 @@ impl<'a> VisitMut for Visitor<'a> { }; for (i, child) in self.children.iter().enumerate() { - if args.ident == child.ident { + if args.ident == *child { node.tokens = parse_quote! { #i }; return; } @@ -88,7 +87,7 @@ impl<'a> VisitMut for Visitor<'a> { } } -pub fn visit_impls(children: &[Child], impls: &mut [syn::ItemImpl]) { +pub fn visit_impls(children: &[Member], impls: &mut [syn::ItemImpl]) { let mut obj = Visitor { children }; for impl_ in impls { From 7bd66be143d3b595c84a8481c3fb709faa5b028a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 6 Oct 2022 12:45:36 +0100 Subject: [PATCH 5/6] Move WidgetArgs into kas_macros::widget module --- crates/kas-macros/src/args.rs | 109 -------------------------------- crates/kas-macros/src/lib.rs | 1 - crates/kas-macros/src/widget.rs | 108 +++++++++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 115 deletions(-) delete mode 100644 crates/kas-macros/src/args.rs diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs deleted file mode 100644 index 89d435fe6..000000000 --- a/crates/kas-macros/src/args.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License in the LICENSE-APACHE file or at: -// https://www.apache.org/licenses/LICENSE-2.0 - -use crate::make_layout; -use proc_macro2::{Span, TokenStream}; -use quote::quote_spanned; -use syn::parse::{Error, Parse, ParseStream, Result}; -use syn::spanned::Spanned; -use syn::{token::Eq, Member, Token}; - -#[allow(non_camel_case_types)] -mod kw { - use syn::custom_keyword; - - custom_keyword!(layout); - custom_keyword!(navigable); - custom_keyword!(hover_highlight); - custom_keyword!(cursor_icon); - custom_keyword!(derive); -} - -#[derive(Debug)] -pub struct BoolToken { - pub kw_span: Span, - pub eq: Eq, - pub lit: syn::LitBool, -} - -#[derive(Debug)] -pub struct ExprToken { - pub kw_span: Span, - pub eq: Eq, - pub expr: syn::Expr, -} - -#[derive(Debug, Default)] -pub struct WidgetArgs { - pub navigable: Option, - pub hover_highlight: Option, - pub cursor_icon: Option, - pub derive: Option, - pub layout: Option<(Span, make_layout::Tree)>, -} - -impl Parse for WidgetArgs { - fn parse(content: ParseStream) -> Result { - let mut navigable = None; - let mut hover_highlight = None; - let mut cursor_icon = None; - let mut kw_derive = None; - let mut derive = None; - let mut layout = None; - - while !content.is_empty() { - let lookahead = content.lookahead1(); - if lookahead.peek(kw::navigable) && navigable.is_none() { - let span = content.parse::()?.span(); - let _: Eq = content.parse()?; - let value = content.parse::()?; - navigable = Some(quote_spanned! {span=> - fn navigable(&self) -> bool { #value } - }); - } else if lookahead.peek(kw::hover_highlight) && hover_highlight.is_none() { - hover_highlight = Some(BoolToken { - kw_span: content.parse::()?.span(), - eq: content.parse()?, - lit: content.parse()?, - }); - } else if lookahead.peek(kw::cursor_icon) && cursor_icon.is_none() { - cursor_icon = Some(ExprToken { - kw_span: content.parse::()?.span(), - eq: content.parse()?, - expr: content.parse()?, - }); - } else if lookahead.peek(kw::derive) && derive.is_none() { - kw_derive = Some(content.parse::()?); - let _: Eq = content.parse()?; - let _: Token![self] = content.parse()?; - let _: Token![.] = content.parse()?; - derive = Some(content.parse()?); - } else if lookahead.peek(kw::layout) && layout.is_none() { - let kw = content.parse::()?; - let _: Eq = content.parse()?; - layout = Some((kw.span, content.parse()?)); - } else { - return Err(lookahead.error()); - } - - let _ = content.parse::()?; - } - - if let Some(_derive) = kw_derive { - if let Some((span, _)) = layout { - return Err(Error::new(span, "incompatible with widget derive")); - // note = derive.span() => "this derive" - } - } - - Ok(WidgetArgs { - navigable, - hover_highlight, - cursor_icon, - derive, - layout, - }) - } -} diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 82b879d3f..4e15d9ce0 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -15,7 +15,6 @@ use proc_macro_error::{emit_call_site_error, proc_macro_error}; use syn::spanned::Spanned; use syn::{parse_macro_input, parse_quote_spanned}; -mod args; mod class_traits; mod extends; mod make_layout; diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index a963d7570..eabc629fe 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -3,15 +3,113 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use crate::args::WidgetArgs; -use crate::make_layout::NavNextResult; +use crate::make_layout::{self, NavNextResult}; use impl_tools_lib::fields::{Fields, FieldsNamed, FieldsUnnamed}; use impl_tools_lib::{Scope, ScopeAttr, ScopeItem, SimplePath}; -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; use proc_macro_error::{emit_error, emit_warning}; -use quote::{quote, TokenStreamExt}; +use quote::{quote, quote_spanned, TokenStreamExt}; +use syn::parse::{Error, Parse, ParseStream, Result}; use syn::spanned::Spanned; -use syn::{parse2, parse_quote, Error, Ident, ImplItem, Index, ItemImpl, Member, Result, Type}; +use syn::{parse2, parse_quote, token::Eq, Ident, ImplItem, Index, ItemImpl, Member, Token, Type}; + +#[allow(non_camel_case_types)] +mod kw { + use syn::custom_keyword; + + custom_keyword!(layout); + custom_keyword!(navigable); + custom_keyword!(hover_highlight); + custom_keyword!(cursor_icon); + custom_keyword!(derive); +} + +#[derive(Debug)] +pub struct BoolToken { + pub kw_span: Span, + pub eq: Eq, + pub lit: syn::LitBool, +} + +#[derive(Debug)] +pub struct ExprToken { + pub kw_span: Span, + pub eq: Eq, + pub expr: syn::Expr, +} + +#[derive(Debug, Default)] +pub struct WidgetArgs { + pub navigable: Option, + pub hover_highlight: Option, + pub cursor_icon: Option, + pub derive: Option, + pub layout: Option<(Span, make_layout::Tree)>, +} + +impl Parse for WidgetArgs { + fn parse(content: ParseStream) -> Result { + let mut navigable = None; + let mut hover_highlight = None; + let mut cursor_icon = None; + let mut kw_derive = None; + let mut derive = None; + let mut layout = None; + + while !content.is_empty() { + let lookahead = content.lookahead1(); + if lookahead.peek(kw::navigable) && navigable.is_none() { + let span = content.parse::()?.span(); + let _: Eq = content.parse()?; + let value = content.parse::()?; + navigable = Some(quote_spanned! {span=> + fn navigable(&self) -> bool { #value } + }); + } else if lookahead.peek(kw::hover_highlight) && hover_highlight.is_none() { + hover_highlight = Some(BoolToken { + kw_span: content.parse::()?.span(), + eq: content.parse()?, + lit: content.parse()?, + }); + } else if lookahead.peek(kw::cursor_icon) && cursor_icon.is_none() { + cursor_icon = Some(ExprToken { + kw_span: content.parse::()?.span(), + eq: content.parse()?, + expr: content.parse()?, + }); + } else if lookahead.peek(kw::derive) && derive.is_none() { + kw_derive = Some(content.parse::()?); + let _: Eq = content.parse()?; + let _: Token![self] = content.parse()?; + let _: Token![.] = content.parse()?; + derive = Some(content.parse()?); + } else if lookahead.peek(kw::layout) && layout.is_none() { + let kw = content.parse::()?; + let _: Eq = content.parse()?; + layout = Some((kw.span, content.parse()?)); + } else { + return Err(lookahead.error()); + } + + let _ = content.parse::()?; + } + + if let Some(_derive) = kw_derive { + if let Some((span, _)) = layout { + return Err(Error::new(span, "incompatible with widget derive")); + // note = derive.span() => "this derive" + } + } + + Ok(WidgetArgs { + navigable, + hover_highlight, + cursor_icon, + derive, + layout, + }) + } +} fn member(index: usize, ident: Option) -> Member { match ident { From 711c1cec56e54bf9857c279a3a0820f75841dfc0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 7 Oct 2022 14:18:05 +0100 Subject: [PATCH 6/6] Fix kas_macros doc test --- crates/kas-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 4e15d9ce0..b01a87b7f 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -393,7 +393,7 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { /// use std::fmt; /// fn main() { /// let world = "world"; -/// let says_hello_world = impl_tools::singleton! { +/// let says_hello_world = kas_macros::singleton! { /// struct(&'static str = world); /// impl fmt::Display for Self { /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {