//! Expansion of a template into output tokens, plus `derive_adhoc_expand!()` //! //! Contains the implementations of `fn expand()` //! for the various template types in [`super::syntax`]. //! //! Also contains the top-level "do the work" macro function - //! the implementation of `derive_adhoc_expand!()`. use super::framework::*; /// Input to `derive_adhoc_expand!` #[derive(Debug)] pub struct DeriveAdhocExpandInput { pub driver_brace: token::Brace, pub driver: syn::DeriveInput, pub template_brace: token::Brace, pub template_crate: syn::Path, pub template_name: Option, pub options: DaOptions, pub template: Template, } /// Node in tree structure found in driver `#[adhoc(some(thing))]` #[derive(Debug)] pub enum FoundMetaNode<'l> { Unit(Span), Lit { path_span: Span, lit: &'l syn::Lit }, } /// Information about a nearby meta node we found /// /// "Nearby" means that the node we found is a prefix (in tree descent) /// of the one we were looking for, or vice versa. #[derive(Debug)] pub struct FoundNearbyMetaNode { pub kind: FoundNearbyMetaNodeKind, /// Span of the identifier in the actual `#[adhoc]` driver attribute pub path_span: Span, } /// How the nearby node relates to the one we were looking for #[derive(Debug)] pub enum FoundNearbyMetaNodeKind { /// We were looking to go deeper, but found a unit in `#[adhoc]` Unit, /// We were looking to go deeper, but found a `name = value` in `#[adhoc]` Lit, /// We were looking for a leaf, but we found nested list in `#[adhoc]` List, } /// What would `${fname}` expand to? As type from [`syn`]. /// /// Implements [`quote::IdentFragment`] and [`ToTokens`]. #[derive(Debug)] pub enum Fname<'r> { Name(&'r syn::Ident), Index(syn::Index), } pub use FoundMetaNode as FMN; pub use FoundNearbyMetaNodeKind as FNMNK; impl Parse for DeriveAdhocExpandInput { fn parse(input: ParseStream) -> syn::Result { // This strange structure is to add a note to most of the errors that // come out of syn parsing. The braced! etc. macros insist that the // calling scope throws syn::Error. // See the match at the bottom for what the return values mean. match (|| { let mut options = DaOptions::default(); // If `got` is an error, returns Some of what the outer closure // should return. Otherwise, return None. let unadvised_err_of_unit = |got: syn::Result<()>| got.err().map(Err).map(Ok); let driver; let driver_brace = braced!(driver in input); let driver = driver.parse()?; if input.peek(syn::token::Bracket) { let tokens; let _ = bracketed!(tokens in input); let r = options .parse_update(&tokens, OpContext::DriverApplicationPassed); if let Some(r) = unadvised_err_of_unit(r) { return r; } } let driver_passed; let _ = braced!(driver_passed in input); let _: TokenStream = driver_passed.parse()?; let template; let template_brace = braced!(template in input); let template = Template::parse(&template); let template_crate; let template_name; { let template_passed; let _ = braced!(template_passed in input); let input = template_passed; template_crate = input.parse()?; let _: Token![;] = input.parse()?; let tokens; let _ = bracketed!(tokens in input); let r = options.parse_update(&tokens, OpContext::Template); if let Some(r) = unadvised_err_of_unit(r) { return r; } template_name = if input.peek(Token![;]) { None } else { Some(input.parse()?) }; let _: Token![;] = input.parse()?; let _: TokenStream = input.parse()?; } let _: TokenStream = input.parse()?; let template = match template { Ok(template) => template, Err(err_return_raw) => return Ok(Err(err_return_raw)), }; Ok(Ok(DeriveAdhocExpandInput { driver_brace, driver, template_brace, template, template_crate, template_name, options, })) })() { Ok(Ok(dae_input)) => Ok(dae_input), Ok(Err(err_to_return_directly)) => Err(err_to_return_directly), Err(err_needing_advice) => { Err(advise_incompatibility(err_needing_advice)) } } } } impl Expand for SubstIf where Template: ExpandInfallible, O: ExpansionOutput, { fn expand(&self, ctx: &Context, out: &mut O) -> syn::Result<()> { for (condition, consequence) in &self.tests { //dbg!(&condition); if condition.eval_bool(ctx)? { //dbg!(&consequence); consequence.expand(ctx, out); return Ok(()); } } if let Some(consequence) = &self.otherwise { //dbg!(&consequence); consequence.expand(ctx, out); } Ok(()) } } impl SubstIf where Template: ExpandInfallible, O: ExpansionOutput, { fn expand_select1(&self, ctx: &Context, out: &mut O) -> syn::Result<()> { let mut found: Result)>, Vec> = Ok(None); for (condition, consequence) in &self.tests { if !condition.eval_bool(ctx)? { continue; } let cspan = condition.span(); let error_loc = |span| (span, "true condition"); match &mut found { Ok(None) => found = Ok(Some((cspan, consequence))), Ok(Some((span1, _))) => { found = Err(vec![ ctx.error_loc(), error_loc(*span1), error_loc(cspan), ]) } Err(several) => several.push(error_loc(cspan)), } } let found = found .map_err(|several| several.error("multiple conditions matched"))? .map(|(_cspan, consequence)| consequence) .or(self.otherwise.as_deref()) .ok_or_else(|| { [ctx.error_loc(), (self.kw_span, "select1 expansion")] .error("no conditions matched, and no else clause") })?; found.expand(ctx, out); Ok(()) } } impl SubstVType { fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, kw_span: Span, self_def: SubstDetails, ) -> syn::Result<()> { let expand_spec_or_sd = |out: &mut _, spec: &Option>, sd: SubstDetails| { if let Some(spec) = spec { spec.expand(ctx, out); Ok(()) } else { sd.expand(ctx, out, kw_span) } }; if !ctx.is_enum() { return expand_spec_or_sd(out, &self.self_, self_def); } // It's an enum. We need to write the main type name, // and the variant. Naively we might expect to just do // TTYPE::VNAME // but that doesn't work, because if TTYPE has generics, that's // TNAME::::VNAME // and this triggers bizarre (buggy) behaviour in rustc - // see rust-lang/rust/issues/108224. // So we need to emit // TNAME::VNAME:: // // The most convenient way to do that seems to be to re-parse // this bit of the expansion as a syn::Path. That lets // us fish out the generics, for writing out later. let mut self_ty = TokenAccumulator::new(); expand_spec_or_sd(&mut self_ty, &self.self_, self_def)?; let self_ty = self_ty.tokens()?; let mut self_ty: syn::Path = syn::parse2(self_ty).map_err(|mut e| { e.combine(kw_span.error( "error re-parsing self type path for this expansion", )); e })?; let mut generics = mem::take( &mut self_ty .segments .last_mut() .ok_or_else(|| { kw_span.error( "self type path for this expansion is empty path!", ) })? .arguments, ); out.append(self_ty); out.append(Token![::](kw_span)); expand_spec_or_sd(out, &self.vname, SD::vname(Default::default()))?; let gen_content = match &mut generics { syn::PathArguments::AngleBracketed(content) => Some(content), syn::PathArguments::None => None, syn::PathArguments::Parenthesized(..) => { return Err([ (generics.span(), "generics"), (kw_span, "template keyword"), ] .error("self type has parenthesised generics, not supported")) } }; if let Some(gen_content) = gen_content { // Normalise `` to `::`. gen_content .colon2_token .get_or_insert_with(|| Token![::](kw_span)); out.append(&generics); } Ok(()) } } impl SubstVPat { // $vpat for struct $tname { $( $fname: $fpatname, ) } // $vpat for enum $tname::$vname { $( $fname: $fpatname, ) } fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, kw_span: Span, ) -> syn::Result<()> { let self_def = SD::tname(Default::default()); SubstVType::expand(&self.vtype, ctx, out, kw_span, self_def)?; let in_braces = braced_group(kw_span, |mut out| { WithinField::for_each(ctx, |ctx, field| { SD::fname::(()) .expand(ctx, &mut out, kw_span)?; out.append_tokens(&(), Token![:](kw_span))?; // Do the expansion with the paste machinery, since // that has a ready-made notion of what fprefix= might // allow, and how to use it. let mut paste = paste::Items::new(kw_span); if let Some(fprefix) = &self.fprefix { fprefix.expand(ctx, &mut paste); } else { paste.append_fixed_string("f_"); } paste.append_identfrag_toks(&field.fname(kw_span))?; paste.assemble(out, None)?; out.append(Token![,](kw_span)); Ok::<_, syn::Error>(()) }) })?; out.append(in_braces); Ok(()) } } impl ExpandInfallible for Template where TemplateElement: Expand, O: ExpansionOutput, { fn expand(&self, ctx_in: &Context, out: &mut O) { let mut ctx_buf; let mut definitions_here = vec![]; let mut defconds_here = vec![]; let mut ctx = ctx_in; for element in &self.elements { macro_rules! handle_definition { { $variant:ident, $store:expr } => { if let TE::Subst(Subst { sd: SD::$variant(def, _), .. }) = element { // Doing this with a macro makes it nice and obvious // to the borrow checker. $store.push(def); ctx_buf = ctx_in.clone(); ctx_buf.definitions.earlier = Some(&ctx_in.definitions); ctx_buf.definitions.here = &definitions_here; ctx_buf.definitions.conds = &defconds_here; ctx = &ctx_buf; continue; } } } handle_definition!(define, definitions_here); handle_definition!(defcond, defconds_here); let () = element .expand(ctx, out) .unwrap_or_else(|err| out.record_error(err)); } } } impl Expand for TemplateElement { fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, ) -> syn::Result<()> { match self { TE::Ident(tt) => out.append(tt.clone()), TE::Literal(tt, ..) => out.append(tt.clone()), TE::LitStr(tt) => out.append(tt.clone()), TE::Punct(tt, _) => out.append(tt.clone()), TE::Group { delim_span, delimiter, template, not_in_paste: _, } => { use proc_macro2::Group; let mut content = TokenAccumulator::new(); template.expand(ctx, &mut content); let mut group = Group::new(*delimiter, content.tokens()?); group.set_span(*delim_span); out.append(TT::Group(group)); } TE::Subst(exp) => { exp.expand(ctx, out)?; } TE::Repeat(repeated_template) => { repeated_template.expand(ctx, out); } } Ok(()) } } impl Expand for Subst where O: ExpansionOutput, TemplateElement: Expand, { fn expand(&self, ctx: &Context, out: &mut O) -> syn::Result<()> { self.sd.expand(ctx, out, self.kw_span) } } impl SubstDetails where O: ExpansionOutput, TemplateElement: Expand, { /// Expand this template element, by adding it to `O` /// /// This is done using `O`'s [`ExpansionOutput`] methods. fn expand( &self, ctx: &Context, out: &mut O, kw_span: Span, ) -> syn::Result<()> { // eprintln!("@@@@@@@@@@@@@@@@@@@@ EXPAND {:?}", self); let do_meta = |sm: &SubstMeta<_>, out, meta| sm.expand(ctx, out, meta); // Methods for handling generics. Most take `composable: bool`, // which lets us control the trailing comma. This is desirable // because we should include it for expansions like $tgens that the // user can append things to, but ideally *not* for expansions like // $ttype that the user can't. let do_tgnames = |out: &mut TokenAccumulator, composable| { for pair in ctx.top.generics.params.pairs() { use syn::GenericParam as GP; match pair.value() { GP::Type(t) => out.append(&t.ident), GP::Const(c) => out.append(&c.ident), GP::Lifetime(l) => out.append(&l.lifetime), } out.append_maybe_punct_composable(&pair.punct(), composable); } }; let do_tgens_nodefs = |out: &mut TokenAccumulator| { for pair in ctx.top.generics.params.pairs() { use syn::GenericParam as GP; let out_attrs = |out: &mut TokenAccumulator, attrs: &[_]| { attrs.iter().for_each(|attr| out.append(attr)); }; match pair.value() { GP::Type(t) => { out_attrs(out, &t.attrs); out.append(&t.ident); out.append(&t.colon_token); out.append(&t.bounds); } GP::Const(c) => { out_attrs(out, &c.attrs); out.append(&c.const_token); out.append(&c.ident); out.append(&c.colon_token); out.append(&c.ty); } GP::Lifetime(l) => out.append(&l), } out.with_tokens(|out| { pair.punct().to_tokens_punct_composable(out); }); } }; let do_tgens = |out: &mut TokenAccumulator, composable: bool| { out.append_maybe_punct_composable( &ctx.top.generics.params, composable, ); }; // There are three contexts where the top-level type // name might occur with generics, and two syntaxes: // referring to the type $ttype Type:: // impl'ing for the type $ttype Type:: // defining a new type $tdeftype Type // Handles $ttype and $tdeftype, and, indirectly, $vtype let do_ttype = |out: &mut O, colons: Option<()>, do_some_gens| { let _: &dyn Fn(&mut _, bool) = do_some_gens; // specify type let gens = &ctx.top.generics; let colons = gens .lt_token .and_then(|_| colons.map(|()| Token![::](kw_span))); out.append_idpath( kw_span, |_| {}, &ctx.top.ident, |out| { out.append(colons); out.append(gens.lt_token); do_some_gens(out, false); out.append(gens.gt_token); }, ) .unwrap_or_else(|e| e.unreachable()) }; let do_maybe_delimited_group = |out, np, delim, content| { let _: &mut O = out; let _: &Template = content; out.append_tokens_with(np, |out| { if let Some(delim) = delim { out.append(delimit_token_group( delim, kw_span, |inside: &mut TokenAccumulator| { Ok(content.expand(ctx, inside)) }, )?); } else { content.expand(ctx, out); } Ok(()) }) }; match self { SD::tname(_) => out.append_identfrag_toks(&ctx.top.ident)?, SD::ttype(_) => do_ttype(out, Some(()), &do_tgnames), SD::tdeftype(_) => do_ttype(out, None, &do_tgens), SD::vname(_) => { out.append_identfrag_toks(&ctx.syn_variant(&kw_span)?.ident)? } SD::fname(_) => { let fname = ctx.field(&kw_span)?.fname(kw_span); out.append_identfrag_toks(&fname)?; } SD::ftype(_) => { let f = ctx.field(&kw_span)?; out.append_syn_type(kw_span, &f.field.ty); } SD::fpatname(_) => { let f = ctx.field(&kw_span)?; let fpatname = Ident::new(&format!("f_{}", f.fname(kw_span)), kw_span); out.append_identfrag_toks(&fpatname)?; } SD::xmeta(sm) => do_meta(sm, out, sm.pmetas(ctx)?)?, SD::Vis(vis, np) => { out.append_tokens(np, vis.syn_vis(ctx, kw_span)?)? } SD::tdefkwd(_) => { fn w(out: &mut O, t: impl ToTokens) where O: ExpansionOutput, { out.append_identfrag_toks(&TokenPastesAsIdent(t)) .unwrap_or_else(|e| e.unreachable()); } use syn::Data::*; match &ctx.top.data { Struct(d) => w(out, &d.struct_token), Enum(d) => w(out, &d.enum_token), Union(d) => w(out, &d.union_token), }; } SD::tattrs(ra, np, ..) => out.append_tokens_with(np, |out| { ra.expand(ctx, out, &ctx.top.attrs) })?, SD::vattrs(ra, np, ..) => out.append_tokens_with(np, |out| { let variant = ctx.variant(&kw_span)?.variant; let attrs = variant.as_ref().map(|v| &*v.attrs); ra.expand(ctx, out, attrs.unwrap_or_default()) })?, SD::fattrs(ra, np, ..) => out.append_tokens_with(np, |out| { ra.expand(ctx, out, &ctx.field(&kw_span)?.field.attrs) })?, SD::tgens(np, ..) => out.append_tokens_with(np, |out| { do_tgens_nodefs(out); Ok(()) })?, SD::tdefgens(np, ..) => out.append_tokens_with(np, |out| { do_tgens(out, true); Ok(()) })?, SD::tgnames(np, ..) => out.append_tokens_with(np, |out| { do_tgnames(out, true); Ok(()) })?, SD::twheres(np, ..) => out.append_tokens_with(np, |out| { if let Some(clause) = &ctx.top.generics.where_clause { out.with_tokens(|out| { clause.predicates.to_tokens_punct_composable(out); }); } Ok(()) })?, SD::vpat(v, np, ..) => out.append_tokens_with(np, |out| { // This comment prevents rustfmt making this unlike the others v.expand(ctx, out, kw_span) })?, SD::vtype(v, np, ..) => out.append_tokens_with(np, |out| { v.expand(ctx, out, kw_span, SD::ttype(Default::default())) })?, SD::tdefvariants(content, np, ..) => { let delim = if ctx.is_enum() { Some(Delimiter::Brace) } else { None }; do_maybe_delimited_group(out, np, delim, content)?; } SD::fdefine(spec_f, np, ..) => { out.append_tokens_with(np, |out| { let field = ctx.field(&kw_span)?.field; if let Some(driver_f) = &field.ident { if let Some(spec_f) = spec_f { spec_f.expand(ctx, out); } else { out.append(driver_f); } } out.append(&field.colon_token); Ok(()) })? } SD::vdefbody(vname, content, np, ..) => { use syn::Fields as SF; let variant = ctx.variant(&kw_span)?; let enum_variant: Option<&syn::Variant> = variant.variant; if enum_variant.is_some() { vname.expand(ctx, out); } let delim = match variant.fields { SF::Unit => None, SF::Unnamed(..) => Some(Delimiter::Parenthesis), SF::Named(..) => Some(Delimiter::Brace), }; do_maybe_delimited_group(out, np, delim, content)?; match variant.fields { SF::Unit => Some(()), SF::Unnamed(..) => Some(()), SF::Named(..) => None, } .map(|()| { if enum_variant.is_some() { out.append_tokens(np, Token![,](kw_span)) } else { out.append_tokens(np, Token![;](kw_span)) } }) .transpose()?; } SD::Crate(np, ..) => out.append_tokens(np, &ctx.template_crate)?, SD::paste(content, ..) => { paste::expand(ctx, kw_span, content, out)?; } SD::ChangeCase(content, case, ..) => { let mut items = paste::Items::new(kw_span); content.expand(ctx, &mut items); items.assemble(out, Some(*case))?; } SD::define(..) | SD::defcond(..) => out.write_error( &kw_span, // I think this is impossible. It could only occur if // someone parsed a Subst or SubstDetails that wasn't // in a Template. It is Template.expand() that handles this. // We could possibly use proof tokens to see if this happens // and exclude it, but that would be super invasive. "${define } and ${defcond } only allowed in a full template", ), SD::UserDefined(name) => name.lookup_expand(ctx, out)?, SD::when(..) => out.write_error( &kw_span, "${when } only allowed in toplevel of $( )", ), SD::If(conds, ..) => conds.expand(ctx, out)?, SD::select1(conds, ..) => conds.expand_select1(ctx, out)?, SD::For(repeat, _) => repeat.expand(ctx, out), SD::dbg_all_keywords(_) => dbg_allkw::dump(ctx), // ## maint/check-keywords-documented BoolOnly ## SD::is_struct(bo) | SD::is_enum(bo) | SD::is_union(bo) | SD::v_is_unit(bo) | SD::v_is_tuple(bo) | SD::v_is_named(bo) | SD::approx_equal(bo, _) | SD::False(bo) | SD::True(bo) | SD::not(_, bo) | SD::any(_, bo) | SD::all(_, bo) => out.append_bool_only(bo), }; Ok(()) } } impl DefinitionName { fn lookup_expand( &self, ctx: &Context<'_>, out: &mut O, ) -> syn::Result<()> { let (def, ctx) = ctx .find_definition(self)? .ok_or_else(|| self.error("user-defined expansion not fund"))?; match &def.body { DefinitionBody::Paste(content) => { paste::expand(&ctx, def.body_span, content, out)?; } DefinitionBody::Normal(content) => { let not_in_paste = O::not_in_paste(self).map_err(|mut unpasteable| { unpasteable.combine(def.body_span.error( "user-defined expansion is not pasteable because it isn't, itself, ${paste }" )); unpasteable })?; out.append_tokens_with(¬_in_paste, |out| { content.expand(&ctx, out); Ok(()) })?; } } Ok(()) } } impl SubstMeta where O: SubstParseContext, { pub fn pmetas<'c>( &self, ctx: &'c Context<'c>, ) -> syn::Result<&'c PreprocessedMetas> { Ok(match self.level { SubstMetaLevel::T => &ctx.tmetas, SubstMetaLevel::V => &ctx.variant(self)?.pmetas, SubstMetaLevel::F => &ctx.field(self)?.pfield.pmetas, }) } } impl SubstMeta where O: ExpansionOutput, { fn expand( &self, ctx: &Context, out: &mut O, pmetas: &PreprocessedMetas, ) -> syn::Result<()> { let mut found = None::; let mut hint = None::; let self_loc = || (self.span(), "expansion"); let error_loc = || [ctx.error_loc(), self_loc()]; self.path.search( pmetas, &mut |av: FoundMetaNode| { if let Some(first) = &found { return Err([(first.path_span(), "first occurrence"), (av.path_span(), "second occurrence"), self_loc()].error( "tried to expand just attribute value, but it was specified multiple times" )); } found = Some(av); Ok(()) }, &mut |nearby| { hint.get_or_insert(nearby); Ok(()) }, )?; let found = found.ok_or_else(|| { if let Some(hint) = hint { let hint_msg = match hint.kind { FNMNK::Unit => "expected a list with sub-attributes, found a unit", FNMNK::Lit => "expected a list with sub-attributes, found a simple value", FNMNK::List => "expected a leaf node, found a list with sub-attributes", }; let mut err = hint.path_span.error(hint_msg); err.combine(error_loc().error( "attribute value expanded, but no suitable value in data structure definition" )); err } else { error_loc().error( "attribute value expanded, but no value in data structure definition" ) } })?; found.expand(self.span(), &self.as_, out)?; Ok(()) } } fn metavalue_spans(tspan: Span, vspan: Span) -> [ErrorLoc; 2] { [(vspan, "attribute value"), (tspan, "template")] } /// Obtain the `LiStr` from a meta node value (ie, a `Lit`) /// /// This is the thing we actually use. /// Non-string-literal values are not allowed. fn metavalue_litstr<'l>( lit: &'l syn::Lit, tspan: Span, msg: fmt::Arguments<'_>, ) -> syn::Result<&'l syn::LitStr> { match lit { syn::Lit::Str(s) => Ok(s), // having checked derive_builder, it doesn't handle // Lit::Verbatim so I guess we don't need to either. _ => Err(metavalue_spans(tspan, lit.span()).error(msg)), } } /// Convert a literal found in a meta item into `T` /// /// `into_what` is used only for error reporting pub fn metavalue_lit_as( lit: &syn::Lit, tspan: Span, into_what: &dyn Display, ) -> syn::Result where T: Parse + ToTokens, { let s = metavalue_litstr( lit, tspan, format_args!( "expected string literal, for conversion to {}", into_what, ), )?; let t: TokenStream = s.parse().map_err(|e| { // Empirically, parsing a LitStr in actual proc macro context, with // proc_macro2, into tokens, can generate a lexical error with a // "fallback" Span. Then, attempting to render the results, // including the eventual compiler_error! invocation, back to // a compiler proc_ma cor::TokenStream can panic with // "compiler/fallback mismatch". // // https://github.com/dtolnay/syn/issues/1504 // // Attempt to detect this situation. match (|| { let _: String = (&e).into_iter().next()?.span().source_text()?; Some(()) })() { Some(()) => e, None => lit.span().error(e.to_string()), } })?; let thing: T = syn::parse2(t)?; Ok(thing) } impl SubstMetaPath { pub fn search<'a, A, F, G, E>( &self, pmetas: A, f: &mut F, g: &mut G, ) -> Result<(), E> where F: FnMut(FoundMetaNode<'a>) -> Result<(), E>, G: FnMut(FoundNearbyMetaNode) -> Result<(), E>, A: IntoIterator, { for pmeta in pmetas { self.search_1(pmeta, &mut *f, &mut *g)?; } Ok(()) } fn search_1<'a, E, F, G>( &self, pmeta: &'a PreprocessedMeta, f: &mut F, g: &mut G, ) -> Result<(), E> where F: FnMut(FoundMetaNode<'a>) -> Result<(), E>, G: FnMut(FoundNearbyMetaNode) -> Result<(), E>, { use PreprocessedMetaValue as PMV; if pmeta.path != self.path { return Ok(()); } let path_span = pmeta.path.span(); let mut nearby = |kind| g(FoundNearbyMetaNode { kind, path_span }); match (&self.deeper, &pmeta.value) { (None, PMV::Unit) => f(FMN::Unit(path_span))?, (None, PMV::List(_)) => nearby(FNMNK::List)?, (None, PMV::Value { value, .. }) => f(FMN::Lit { path_span, lit: value, })?, (Some(_), PMV::Value { .. }) => nearby(FNMNK::Lit)?, (Some(_), PMV::Unit) => nearby(FNMNK::Unit)?, (Some(d), PMV::List(l)) => { for m in l.content.iter() { d.search_1(m, &mut *f, &mut *g)?; } } } Ok(()) } } impl FoundMetaNode<'_> { fn path_span(&self) -> Span { match self { FMN::Unit(span) => *span, FMN::Lit { path_span, .. } => *path_span, } } } impl<'l> FoundMetaNode<'l> { fn expand( &self, tspan: Span, as_: &Option>, out: &mut O, ) -> syn::Result<()> where O: ExpansionOutput, { let spans = |vspan| metavalue_spans(tspan, vspan); let lit = match self { FMN::Unit(vspan) => return Err(spans(*vspan).error( "tried to expand attribute which is just a unit, not a literal" )), FMN::Lit { lit, .. } => lit, }; use SubstMetaAs as SMA; let default_buf; let as_ = match as_ { Some(as_) => as_, None => { default_buf = O::default_subst_meta_as(); &default_buf } }; match as_ { SMA::tokens(_, np) => { let tokens: TokenStream = metavalue_lit_as(lit, tspan, &"tokens")?; out.append_tokens(np, tokens)?; } as_ @ SMA::ty(..) => { out.append_syn_type(tspan, &metavalue_lit_as(lit, tspan, as_)?) } SMA::str(..) => { let s = metavalue_litstr( lit, tspan, format_args!("expected string literal, for meta value",), )?; out.append_syn_litstr(s); } } Ok(()) } } impl RawAttr { fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, attrs: &[syn::Attribute], ) -> syn::Result<()> { for attr in attrs { match self { RawAttr::Default => { if ["adhoc", "derive_adhoc"] .iter() .all(|exclude| !attr.path().is_ident(exclude)) { out.append(attr); } } RawAttr::Include { entries } => { let ent = entries.iter().find(|ent| ent.matches(attr)); if let Some(ent) = ent { ent.expand(ctx, out, attr)?; } } RawAttr::Exclude { exclusions } => { if !exclusions.iter().any(|excl| excl == attr.path()) { out.append(attr); } } } } Ok(()) } } impl RawAttrEntry { fn matches(&self, attr: &syn::Attribute) -> bool { &self.path == attr.path() } fn expand( &self, _ctx: &Context, out: &mut TokenAccumulator, attr: &syn::Attribute, ) -> syn::Result<()> { out.append(attr); Ok(()) } } impl ExpandInfallible for RepeatedTemplate where Template: ExpandInfallible, O: ExpansionOutput, { fn expand(&self, ctx: &Context, out: &mut O) { // for_with_within expects a fallible closure, but we want to do // infallible work in our infallible context, so we use `Void` // as the error type and wrap each call in `Ok`. #[allow(clippy::unit_arg)] // clippy wants us to worsify the style match self.over { RO::Variants => ctx.for_with_within(|ctx, _: &WithinVariant| { Ok::<_, Void>(self.expand_inner(ctx, out)) }), RO::Fields => ctx.for_with_within(|ctx, _: &WithinField| { Ok::<_, Void>(self.expand_inner(ctx, out)) }), } .void_unwrap() } } impl RepeatedTemplate { /// private, does the condition fn expand_inner(&self, ctx: &Context, out: &mut O) where Template: ExpandInfallible, O: ExpansionOutput, { for when in &self.whens { match when.eval_bool(ctx) { Ok(true) => continue, Ok(false) => return, Err(e) => { out.record_error(e); return; } } } self.template.expand(ctx, out) } } impl<'w> WithinField<'w> { /// What would `${fname}` expand to? pub fn fname(&self, tspan: Span) -> Fname { if let Some(fname) = &self.field.ident { // todo is this the right span to emit? Fname::Name(fname) } else { Fname::Index(syn::Index { index: self.index, span: tspan, }) } } } impl IdentFrag for Fname<'_> { type BadIdent = IdentFragInfallible; fn frag_to_tokens( &self, out: &mut TokenStream, ) -> Result<(), IdentFragInfallible> { Ok(self.to_tokens(out)) } fn fragment(&self) -> String { self.to_string() } } impl Display for Fname<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Fname::Name(v) => quote::IdentFragment::fmt(v, f), Fname::Index(v) => quote::IdentFragment::fmt(v, f), } } } impl ToTokens for Fname<'_> { fn to_tokens(&self, out: &mut TokenStream) { match self { Fname::Name(v) => v.to_tokens(out), Fname::Index(v) => v.to_tokens(out), } } } /// `derive_adhoc_expand!` -- implements the actual template engine /// /// In my design, the input contains, firstly, literally the definition /// that #[derive(Adhoc)] was applied to (see NOTES.txt). /// Using the literal input, rather than some pre-parsed version, is /// slower, but means that we aren't inventing a nontrivial data format which /// potentially crosses crate boundaries with semver implications. pub fn derive_adhoc_expand_func_macro( input: TokenStream, ) -> syn::Result { // eprintln!("derive_adhoc_expand! {}", &input); let input: DeriveAdhocExpandInput = syn::parse2(input)?; // eprintln!("derive_adhoc_expand! crate = {:?}", &input.template_crate); let DaOptions { dbg, driver_kind, expect_target, // } = input.options; if let Some(exp) = driver_kind { macro_rules! got_kind { { $($kind:ident)* } => { match &input.driver.data { $( syn::Data::$kind(..) => ExpectedDriverKind::$kind, )* } } } let got_kind = got_kind!(Struct Enum Union); if got_kind != exp.value { return Err([ (exp.span, "expected kind"), (input.driver.span(), "actual kind"), ] .error(format_args!( "template defined for {}, but applied to {}", exp.value, got_kind, ))); } } Context::call( &input.driver, &input.template_crate, input.template_name.as_ref(), |ctx| { let mut output = TokenAccumulator::new(); input.template.expand(&ctx, &mut output); let output = output.tokens()?; // dbg!(&&output); if dbg { let description = ctx.expansion_description(); let dump = format!( concat!( "---------- {} (start) ----------\n", "{}\n", "---------- {} (end) ----------\n", ), &description, &output, &description, ); eprint!("{}", dump); } let mut output = output; if let Some(target) = expect_target { check::check_expected_target_syntax(&ctx, &mut output, target); } Ok(output) }, ) }