asteracea_proc-macro-definitions 0.0.2

Proc macro definitions for Asteracea. They are unlikely to work correctly unless used through the asteracea crate.
Documentation
use super::capture_definition::CaptureDefinition;
use crate::{
	asteracea_ident,
	parse_with_context::{ParseContext, ParseWithContext},
	workaround_module::Configuration,
};
use call2_for_syn::call2;
use proc_macro2::{Span, TokenStream};
use quote::quote_spanned;
use syn::{
	braced,
	parse::ParseStream,
	parse_quote,
	token::{Add, Move},
	visit_mut::{self, VisitMut},
	Expr, ExprPath, ForeignItemFn, Ident, ItemFn, LitStr, Result,
};

pub struct EventBindingDefinition {
	prefix: Add,
	name: LitStr,
	field_name: Ident,
}

impl EventBindingDefinition {
	pub fn parse_with_context(
		input: ParseStream<'_>,
		cx: &mut ParseContext,
	) -> Result<EventBindingDefinition> {
		cx.imply_bump = true;
		cx.imply_self_outlives_bump = true;

		let prefix: Add = input.parse()?;
		let name: LitStr = input.parse()?;
		let move_token: Option<Move> = input.parse().ok();
		let handler_body;
		let brace = braced!(handler_body in input);
		let handler_body: TokenStream = handler_body.parse()?;

		struct ReplaceSelf;
		impl VisitMut for ReplaceSelf {
			fn visit_expr_mut(&mut self, node: &mut Expr) {
				if let Expr::Path(ExprPath { path, .. }) = node {
					if path.leading_colon.is_none() && path.segments.len() == 1 {
						let segment = path.segments.first().unwrap();
						if segment.arguments.is_empty() {
							let ident = &segment.ident;
							if format!("{}", ident).starts_with("asteracea__") {
								//TODO: Handle this more gracefully and also check other custom identifiers.
								panic!("User-provided identifier starting with asteracea__ found in event handler")
							}
							if format!("{}", ident) == "self" {
								let replacement = Ident::new("asteracea__self", ident.span());
								*node = parse_quote!(#replacement);
								return;
							}
						}
					}
				}
				visit_mut::visit_expr_mut(self, node)
			}

			// Ignore function definitions, since they can redeclare `self` and an outer `self` isn't valid there.
			fn visit_foreign_item_fn_mut(&mut self, _: &mut ForeignItemFn) {}
			fn visit_item_fn_mut(&mut self, _: &mut ItemFn) {}
		}
		let handler = quote_spanned! (brace.span=> { #handler_body });
		let mut handler = parse_quote!(#handler);
		ReplaceSelf.visit_expr_block_mut(&mut handler);

		let component_name = cx
			.component_name
			.as_ref()
			.expect("Component name not set in ParseContext");
		let handler = quote_spanned! {brace.span =>
			#move_token |#[allow(non_snake_case)] asteracea__self| {
				#[allow(non_snake_case)]
				let asteracea__self = asteracea__self
					.downcast_ref::<#component_name>()
					.expect(
						concat!(
							"Failed to downcast reference to component ",
							stringify!(#component_name)
						)
					);
				#handler
			}
		};

		let field_name = Ident::new(
			&format!(
				"asteracea__event_handler_{}_{}",
				cx.event_binding_count,
				&name.value()
			),
			name.span(),
		);
		cx.event_binding_count += 1;

		cx.allow_non_snake_case_on_structure_workaround = true;
		call2(
			quote_spanned! {prefix.span=>
				#[allow(non_snake_case)] // This currently has no effect, hence `allow_non_snake_case_on_structure_workaround`.
				|#field_name: Box<dyn Fn(&dyn ::core::any::Any)> = { Box::new(#handler) }|;
			},
			|input| {
				enum EventBindingConfiguration {}
				impl Configuration for EventBindingConfiguration {
					const NAME: &'static str = "component! event binding expression";
					const CAN_CAPTURE: bool = true;
				}
				match CaptureDefinition::<EventBindingConfiguration>::parse_with_context(input, cx)
					.expect("Error parsing internal event binding capture")
				{
					None => (),
					Some(_) => unreachable!(),
				}
			},
		);

		Ok(EventBindingDefinition {
			prefix,
			name,
			field_name,
		})
	}

	pub fn part_tokens(&self) -> TokenStream {
		let EventBindingDefinition {
			prefix,
			name,
			field_name,
		} = self;
		let asteracea = asteracea_ident(prefix.span);
		let self_ident = Ident::new("self", Span::call_site());

		quote_spanned! {name.span()=>
			#asteracea::lignin_schema::lignin::EventBinding {
				name: #name,
				context: self,
				handler: &*#self_ident.#field_name,
			}
		}
	}
}