From f93c4cf7c2de6d16fd1592bbf70ca28b7df3df23 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Sun, 26 Nov 2023 16:16:14 +1000 Subject: [PATCH] work on error reporting for uncaptured and uninterpolated key values --- macros/src/build.rs | 6 +++--- macros/src/capture.rs | 13 ++++++++++--- macros/src/emit.rs | 18 +++++++++++------- macros/src/fmt.rs | 9 ++++++++- macros/src/props.rs | 31 ++++++++++++++++++++++--------- macros/src/template.rs | 25 +++++++++++++++---------- src/macro_hooks.rs | 11 +++++++++++ 7 files changed, 80 insertions(+), 33 deletions(-) diff --git a/macros/src/build.rs b/macros/src/build.rs index 31fbb63..e7a201c 100644 --- a/macros/src/build.rs +++ b/macros/src/build.rs @@ -31,14 +31,14 @@ impl Parse for TemplateArgs { } pub fn expand_template_tokens(opts: ExpandTemplateTokens) -> Result { - let (_, template, props) = template::parse2::(opts.input)?; + let (_, template, props) = template::parse2::(opts.input, false)?; // Ensure that a standalone template only specifies identifiers for key_value in props.iter() { - if key_value.has_expr { + if !key_value.interpolated { return Err(syn::Error::new( key_value.span(), - "key-values in raw templates cannot capture values", + "key-values in raw templates must be in the template itself", )); } } diff --git a/macros/src/capture.rs b/macros/src/capture.rs index 1ec419c..1dfa8d5 100644 --- a/macros/src/capture.rs +++ b/macros/src/capture.rs @@ -34,6 +34,7 @@ pub fn key_value_with_hook( attrs: &[Attribute], fv: &FieldValue, interpolated: bool, + captured: bool, ) -> TokenStream { let fn_name = match &*fv.key_name() { emit_core::well_known::LVL_KEY => quote_spanned!(fv.span()=> __private_capture_as_level), @@ -60,10 +61,16 @@ pub fn key_value_with_hook( quote!(.__private_uninterpolated()) }; + let captured_expr = if captured { + quote!(.__private_captured()) + } else { + quote!(.__private_uncaptured()) + }; + let key_tokens = key::key_with_hook(&[], &key_expr); let value_tokens = quote_spanned!(fv.span()=> { use emit::__private::{__PrivateCaptureHook as _, __PrivateOptionalCaptureHook as _, __PrivateOptionalMapHook as _, __PrivateInterpolatedHook as _}; - (#expr).__private_optional_capture_some().__private_optional_map_some(|v| v.#fn_name())#interpolated_expr + (#expr).__private_optional_capture_some().__private_optional_map_some(|v| v.#fn_name()) #interpolated_expr #captured_expr }); quote_spanned!(fv.span()=> @@ -90,10 +97,10 @@ pub fn rename_hook_tokens( args: opts.args, expr: opts.expr, predicate: |ident: &str| { - ident.starts_with("__private_capture") || ident.starts_with("__private_interpolated") + ident.starts_with("__private_capture") || ident.starts_with("__private_captured") }, to: move |args: &Args, ident: &Ident, _: &Punctuated| { - if ident.to_string().starts_with("__private_interpolated") { + if ident.to_string().starts_with("__private_captured") { return None; } diff --git a/macros/src/emit.rs b/macros/src/emit.rs index c753c1f..21a49cb 100644 --- a/macros/src/emit.rs +++ b/macros/src/emit.rs @@ -50,21 +50,25 @@ impl Parse for Args { } pub fn expand_tokens(opts: ExpandTokens) -> Result { - let (args, template, mut props) = template::parse2::(opts.input)?; + let (args, template, mut props) = template::parse2::(opts.input, true)?; // Add the level as a property let level_ident = Ident::new(emit_core::well_known::LVL_KEY, Span::call_site()); let level_value = opts.level; - props.push(&syn::parse2::( - quote!(#level_ident: emit::Level::#level_value), - )?)?; + props.push( + &syn::parse2::(quote!(#level_ident: emit::Level::#level_value))?, + false, + true, + )?; // Add the location as a property let loc_ident = Ident::new(emit_core::well_known::LOCATION_KEY, Span::call_site()); - props.push(&syn::parse2::( - quote!(#loc_ident: emit::__private::loc!()), - )?)?; + props.push( + &syn::parse2::(quote!(#loc_ident: emit::__private::loc!()))?, + false, + true, + )?; let props_match_input_tokens = props.match_input_tokens(); let props_match_binding_tokens = props.match_binding_tokens(); diff --git a/macros/src/fmt.rs b/macros/src/fmt.rs index bac8b08..a3ca663 100644 --- a/macros/src/fmt.rs +++ b/macros/src/fmt.rs @@ -13,6 +13,7 @@ pub fn template_hole_with_hook( attrs: &[Attribute], hole: &ExprLit, interpolated: bool, + captured: bool, ) -> TokenStream { let interpolated_expr = if interpolated { quote!(.__private_interpolated()) @@ -20,11 +21,17 @@ pub fn template_hole_with_hook( quote!(.__private_uninterpolated()) }; + let captured_expr = if captured { + quote!(.__private_captured()) + } else { + quote!(.__private_uncaptured()) + }; + quote_spanned!(hole.span()=> #(#attrs)* { use emit::__private::{__PrivateFmtHook as _, __PrivateInterpolatedHook as _}; - emit::template::Part::hole(#hole).__private_fmt_as_default()#interpolated_expr + emit::template::Part::hole(#hole).__private_fmt_as_default()#interpolated_expr #captured_expr } ) } diff --git a/macros/src/props.rs b/macros/src/props.rs index 51ff627..5877211 100644 --- a/macros/src/props.rs +++ b/macros/src/props.rs @@ -9,7 +9,6 @@ use crate::{ }; pub struct Props { - interpolated: bool, match_value_tokens: Vec, match_binding_tokens: Vec, key_values: BTreeMap, @@ -20,10 +19,10 @@ impl Parse for Props { fn parse(input: syn::parse::ParseStream) -> syn::Result { let fv = input.parse_terminated(FieldValue::parse, Token![,])?; - let mut props = Props::new(false); + let mut props = Props::new(); for fv in fv { - props.push(&fv)?; + props.push(&fv, false, true)?; } Ok(props) @@ -34,10 +33,11 @@ pub struct KeyValue { match_bound_tokens: TokenStream, direct_bound_tokens: TokenStream, span: Span, + pub interpolated: bool, + pub captured: bool, pub label: String, pub cfg_attr: Option, pub attrs: Vec, - pub has_expr: bool, } impl KeyValue { @@ -47,9 +47,8 @@ impl KeyValue { } impl Props { - pub fn new(interpolated: bool) -> Self { + pub fn new() -> Self { Props { - interpolated, match_value_tokens: Vec::new(), match_binding_tokens: Vec::new(), key_values: BTreeMap::new(), @@ -94,7 +93,12 @@ impl Props { self.key_values.values() } - pub fn push(&mut self, fv: &FieldValue) -> Result<(), syn::Error> { + pub fn push( + &mut self, + fv: &FieldValue, + interpolated: bool, + captured: bool, + ) -> Result<(), syn::Error> { let mut attrs = vec![]; let mut cfg_attr = None; @@ -116,7 +120,8 @@ impl Props { let match_bound_ident = self.next_match_binding_ident(fv.span()); let key_value_tokens = { - let key_value_tokens = capture::key_value_with_hook(&attrs, &fv, self.interpolated); + let key_value_tokens = + capture::key_value_with_hook(&attrs, &fv, interpolated, captured); match cfg_attr { Some(ref cfg_attr) => quote_spanned!(fv.span()=> @@ -147,6 +152,13 @@ impl Props { let label = fv.key_name(); + if fv.colon_token.is_some() && !captured { + return Err(syn::Error::new( + fv.span(), + "uncaptured key values must be plain identifiers", + )); + } + // Make sure keys aren't duplicated let previous = self.key_values.insert( label.clone(), @@ -155,9 +167,10 @@ impl Props { direct_bound_tokens: quote_spanned!(fv.span()=> #key_value_tokens), span: fv.span(), label, - has_expr: fv.colon_token.is_some(), cfg_attr, attrs, + captured, + interpolated, }, ); diff --git a/macros/src/template.rs b/macros/src/template.rs index f35cdef..25ad410 100644 --- a/macros/src/template.rs +++ b/macros/src/template.rs @@ -6,7 +6,10 @@ use syn::{parse::Parse, punctuated::Punctuated, spanned::Spanned, Expr, ExprPath use crate::{fmt, props::Props, util::FieldValueKey}; -pub fn parse2(input: TokenStream) -> Result<(A, Template, Props), syn::Error> { +pub fn parse2( + input: TokenStream, + captured: bool, +) -> Result<(A, Template, Props), syn::Error> { let template = fv_template::Template::parse2(input).map_err(|e| syn::Error::new(e.span(), e))?; @@ -25,7 +28,7 @@ pub fn parse2(input: TokenStream) -> Result<(A, Template, Props), syn: .map(|fv| Ok((fv.key_name(), fv))) .collect::>()?; - let mut props = Props::new(true); + let mut props = Props::new(); // Push the field-values that appear in the template for fv in template.literal_field_values() { @@ -35,7 +38,7 @@ pub fn parse2(input: TokenStream) -> Result<(A, Template, Props), syn: // then it will be used as the source for the value and attributes // In this case, it's expected that the field-value in the template is // just a single identifier - let fv = match extra_field_values.remove(&k) { + match extra_field_values.remove(&k) { Some(extra_fv) => { if let Expr::Path(ExprPath { ref path, .. }) = fv.expr { // Make sure the field-value in the template is just a plain identifier @@ -55,18 +58,18 @@ pub fn parse2(input: TokenStream) -> Result<(A, Template, Props), syn: )); } - extra_fv + props.push(extra_fv, true, captured)?; } - None => fv, - }; - - props.push(fv)?; + None => { + props.push(fv, true, captured)?; + } + } } // Push any remaining extra field-values // This won't include any field values that also appear in the template for (_, fv) in extra_field_values { - props.push(fv)?; + props.push(fv, false, captured)?; } // A runtime representation of the template @@ -108,7 +111,9 @@ impl<'a> fv_template::LiteralVisitor for TemplateVisitor<'a> { let field = self.props.get(&label).expect("missing prop"); - let hole_tokens = fmt::template_hole_with_hook(&field.attrs, &hole, true); + debug_assert!(field.interpolated); + + let hole_tokens = fmt::template_hole_with_hook(&field.attrs, &hole, true, field.captured); match field.cfg_attr { Some(ref cfg_attr) => self.parts.push(quote!(#cfg_attr { #hole_tokens })), diff --git a/src/macro_hooks.rs b/src/macro_hooks.rs index c1114d0..c31447f 100644 --- a/src/macro_hooks.rs +++ b/src/macro_hooks.rs @@ -263,6 +263,9 @@ impl __PrivateOptionalMapHook for Option { pub trait __PrivateInterpolatedHook { fn __private_interpolated(self) -> Self; fn __private_uninterpolated(self) -> Self; + + fn __private_captured(self) -> Self; + fn __private_uncaptured(self) -> Self; } impl __PrivateInterpolatedHook for T { @@ -273,6 +276,14 @@ impl __PrivateInterpolatedHook for T { fn __private_uninterpolated(self) -> Self { self } + + fn __private_captured(self) -> Self { + self + } + + fn __private_uncaptured(self) -> Self { + self + } } /**