From bef7c3a38c8a4d7dbf3b70c953883a481559e69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 24 Nov 2024 17:24:07 +0100 Subject: [PATCH 1/6] derive: fix missing import --- rinja_derive/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rinja_derive/src/config.rs b/rinja_derive/src/config.rs index 1d4485f2..9dc63ba3 100644 --- a/rinja_derive/src/config.rs +++ b/rinja_derive/src/config.rs @@ -307,7 +307,7 @@ impl RawConfig<'_> { } } -#[cfg_attr(feature = "config", derive(Deserialize))] +#[cfg_attr(feature = "config", derive(serde::Deserialize))] struct General<'a> { #[cfg_attr(feature = "config", serde(borrow))] dirs: Option>, From 9527d14d7fc50b3c3403e7b21731148ada986c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 24 Nov 2024 17:25:58 +0100 Subject: [PATCH 2/6] =?UTF-8?q?derive:=20capture=20`source=20=3D=20?= =?UTF-8?q?=E2=80=A6`=20value=20into=20an=20`Arc`=20for=20cheaper=20clonin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rinja_derive/src/input.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rinja_derive/src/input.rs b/rinja_derive/src/input.rs index ee2ee628..2fce3f81 100644 --- a/rinja_derive/src/input.rs +++ b/rinja_derive/src/input.rs @@ -309,7 +309,7 @@ impl TemplateArgs { Ok(Self { source: match args.source { Some(PartialTemplateArgsSource::Path(s)) => { - (Source::Path(s.value()), Some(s.span())) + (Source::Path(s.value().into()), Some(s.span())) } Some(PartialTemplateArgsSource::Source(s)) => { (Source::Source(s.value().into()), Some(s.span())) @@ -547,9 +547,9 @@ fn extension(path: &Path) -> Option<&str> { } } -#[derive(Debug, Hash, PartialEq)] +#[derive(Debug, Clone, Hash, PartialEq)] pub(crate) enum Source { - Path(String), + Path(Arc), Source(Arc), } From 0d39b84fc05c970ce5fbdd0a7cda76b6968585f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 24 Nov 2024 17:33:58 +0100 Subject: [PATCH 3/6] derive: do not clone `#[doc]` attributes --- rinja_derive/src/input.rs | 71 +++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/rinja_derive/src/input.rs b/rinja_derive/src/input.rs index 2fce3f81..d3c41a2c 100644 --- a/rinja_derive/src/input.rs +++ b/rinja_derive/src/input.rs @@ -290,16 +290,7 @@ pub(crate) struct TemplateArgs { impl TemplateArgs { pub(crate) fn new(ast: &syn::DeriveInput) -> Result { - // FIXME: implement once is stable - if let syn::Data::Union(data) = &ast.data { - return Err(CompileError::new_with_span( - "rinja templates are not supported for `union` types, only `struct` and `enum`", - None, - Some(data.union_token.span), - )); - } - - let args = PartialTemplateArgs::new(&ast.attrs)?; + let args = PartialTemplateArgs::new(ast, &ast.attrs)?; let Some(template) = args.template else { return Err(CompileError::no_file_info( "no attribute `template` found", @@ -315,9 +306,7 @@ impl TemplateArgs { (Source::Source(s.value().into()), Some(s.span())) } #[cfg(feature = "code-in-doc")] - Some(PartialTemplateArgsSource::InDoc(s)) => { - source_from_docs(s.span(), &args.meta_docs, ast)? - } + Some(PartialTemplateArgsSource::InDoc(span, source)) => (source, Some(span)), None => { return Err(CompileError::no_file_info( #[cfg(not(feature = "code-in-doc"))] @@ -368,7 +357,7 @@ impl TemplateArgs { #[cfg(feature = "code-in-doc")] fn source_from_docs( span: Span, - docs: &[Attribute], + docs: &[&Attribute], ast: &syn::DeriveInput, ) -> Result<(Source, Option), CompileError> { let (source_span, source) = collect_comment_blocks(span, docs, ast)?; @@ -380,7 +369,7 @@ fn source_from_docs( #[cfg(feature = "code-in-doc")] fn collect_comment_blocks( span: Span, - docs: &[Attribute], + docs: &[&Attribute], ast: &syn::DeriveInput, ) -> Result<(Option, String), CompileError> { let mut source_span: Option = None; @@ -652,7 +641,6 @@ pub(crate) fn get_template_source( #[derive(Default)] pub(crate) struct PartialTemplateArgs { pub(crate) template: Option, - pub(crate) meta_docs: Vec, pub(crate) source: Option, pub(crate) block: Option, pub(crate) print: Option, @@ -663,34 +651,54 @@ pub(crate) struct PartialTemplateArgs { pub(crate) whitespace: Option, } +#[derive(Clone)] pub(crate) enum PartialTemplateArgsSource { Path(LitStr), Source(LitStr), #[cfg(feature = "code-in-doc")] - InDoc(#[allow(dead_code)] LitBool), + InDoc(Span, Source), } // implement PartialTemplateArgs::new() const _: () = { impl PartialTemplateArgs { - pub(crate) fn new(attrs: &[Attribute]) -> Result { - new(attrs) + pub(crate) fn new( + ast: &syn::DeriveInput, + attrs: &[Attribute], + ) -> Result { + new(ast, attrs) } } #[inline] - fn new(attrs: &[Attribute]) -> Result { + fn new( + ast: &syn::DeriveInput, + attrs: &[Attribute], + ) -> Result { + // FIXME: implement once is stable + if let syn::Data::Union(data) = &ast.data { + return Err(CompileError::new_with_span( + "rinja templates are not supported for `union` types, only `struct` and `enum`", + None, + Some(data.union_token.span), + )); + } + + #[cfg(feature = "code-in-doc")] + let mut meta_docs = vec![]; + let mut this = PartialTemplateArgs::default(); for attr in attrs { let Some(ident) = attr.path().get_ident() else { continue; }; - if ident == "doc" { - this.meta_docs.push(attr.clone()); - continue; - } else if ident == "template" { + if ident == "template" { this.template = Some(ident.clone()); } else { + #[cfg(feature = "code-in-doc")] + if ident == "doc" { + meta_docs.push(attr); + } continue; } @@ -742,7 +750,10 @@ const _: () = { } #[cfg(feature = "code-in-doc")] { - this.source = Some(PartialTemplateArgsSource::InDoc(value)); + this.source = Some(PartialTemplateArgsSource::InDoc( + value.span(), + Source::Path("".into()), + )); } } else if ident == "block" { set_strlit_pair(ident, value, &mut this.block)?; @@ -766,6 +777,16 @@ const _: () = { } } } + + #[cfg(feature = "code-in-doc")] + if let Some(PartialTemplateArgsSource::InDoc(lit_span, _)) = this.source { + let (source, doc_span) = source_from_docs(lit_span, &meta_docs, ast)?; + this.source = Some(PartialTemplateArgsSource::InDoc( + doc_span.unwrap_or(lit_span), + source, + )); + } + Ok(this) } From 9488f218d43b326c29bbbe295e373427013ca064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 24 Nov 2024 17:50:31 +0100 Subject: [PATCH 4/6] derive: only one, re-usable `template_to_string()` function --- rinja_derive/src/generator.rs | 72 +++++++++++++++------------------ rinja_derive/src/integration.rs | 28 ++++++++----- rinja_derive/src/lib.rs | 45 ++++++++++++--------- rinja_derive/src/tests.rs | 8 +++- 4 files changed, 84 insertions(+), 69 deletions(-) diff --git a/rinja_derive/src/generator.rs b/rinja_derive/src/generator.rs index 2fa7d039..40152275 100644 --- a/rinja_derive/src/generator.rs +++ b/rinja_derive/src/generator.rs @@ -22,22 +22,12 @@ use crate::integration::{Buffer, impl_everything, write_header}; use crate::{BUILT_IN_FILTERS, CRATE, CompileError, FileInfo, MsgValidEscapers}; pub(crate) fn template_to_string( - input: &TemplateInput<'_>, - contexts: &HashMap<&Arc, Context<'_>, FxBuildHasher>, - heritage: Option<&Heritage<'_, '_>>, -) -> Result { - let mut buf = Buffer::new(); - template_into_buffer(input, contexts, heritage, &mut buf, false)?; - Ok(buf.into_string()) -} - -pub(crate) fn template_into_buffer( - input: &TemplateInput<'_>, - contexts: &HashMap<&Arc, Context<'_>, FxBuildHasher>, - heritage: Option<&Heritage<'_, '_>>, buf: &mut Buffer, - only_template: bool, -) -> Result<(), CompileError> { + input: &TemplateInput<'_>, + contexts: &HashMap<&Arc, Context<'_>, FxBuildHasher>, + heritage: Option<&Heritage<'_, '_>>, + target: Option<&str>, +) -> Result { let ctx = &contexts[&input.path]; let generator = Generator::new( input, @@ -47,7 +37,7 @@ pub(crate) fn template_into_buffer( input.block.is_some(), 0, ); - let mut result = generator.build(ctx, buf, only_template); + let mut result = generator.build(ctx, buf, target); if let Err(err) = &mut result { if err.span.is_none() { err.span = input.source_span; @@ -110,24 +100,19 @@ impl<'a, 'h> Generator<'a, 'h> { mut self, ctx: &Context<'a>, buf: &mut Buffer, - only_template: bool, - ) -> Result<(), CompileError> { - if !only_template { + target: Option<&str>, + ) -> Result { + if target.is_none() { buf.write(format_args!( - "\ - const _: () = {{\ - extern crate {CRATE} as rinja;\ - " + "const _: () = {{ extern crate {CRATE} as rinja;" )); } - - self.impl_template(ctx, buf)?; - impl_everything(self.input.ast, buf, only_template); - - if !only_template { + let size_hint = self.impl_template(ctx, buf, target.unwrap_or("rinja::Template"))?; + if target.is_none() { + impl_everything(self.input.ast, buf); buf.write("};"); } - Ok(()) + Ok(size_hint) } fn push_locals(&mut self, callback: F) -> Result @@ -178,8 +163,9 @@ impl<'a, 'h> Generator<'a, 'h> { &mut self, ctx: &Context<'a>, buf: &mut Buffer, + target: &str, ) -> Result { - write_header(self.input.ast, buf, "rinja::Template", None); + write_header(self.input.ast, buf, target, None); buf.write( "fn render_into(&self, writer: &mut RinjaW) -> rinja::Result<()>\ where \ @@ -189,7 +175,6 @@ impl<'a, 'h> Generator<'a, 'h> { use rinja::helpers::core::fmt::Write as _;", ); - buf.set_discard(self.buf_writable.discard); // Make sure the compiler understands that the generated code depends on the template files. let mut paths = self .contexts @@ -212,14 +197,7 @@ impl<'a, 'h> Generator<'a, 'h> { } } - let size_hint = if let Some(heritage) = self.heritage { - self.handle(heritage.root, heritage.root.nodes, buf, AstLevel::Top) - } else { - self.handle(ctx, ctx.nodes, buf, AstLevel::Top) - }?; - buf.set_discard(false); - - self.flush_ws(Ws(None, None)); + let size_hint = self.impl_template_inner(ctx, buf)?; buf.write(format_args!( "\ @@ -234,6 +212,22 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(size_hint) } + fn impl_template_inner( + &mut self, + ctx: &Context<'a>, + buf: &mut Buffer, + ) -> Result { + buf.set_discard(self.buf_writable.discard); + let size_hint = if let Some(heritage) = self.heritage { + self.handle(heritage.root, heritage.root.nodes, buf, AstLevel::Top) + } else { + self.handle(ctx, ctx.nodes, buf, AstLevel::Top) + }?; + self.flush_ws(Ws(None, None)); + buf.set_discard(false); + Ok(size_hint) + } + // Helper methods for handling node types fn handle( diff --git a/rinja_derive/src/integration.rs b/rinja_derive/src/integration.rs index d28b8cf7..8c933427 100644 --- a/rinja_derive/src/integration.rs +++ b/rinja_derive/src/integration.rs @@ -4,20 +4,18 @@ use quote::quote; use syn::DeriveInput; /// Implement every integration for the given item -pub(crate) fn impl_everything(ast: &DeriveInput, buf: &mut Buffer, only_template: bool) { +pub(crate) fn impl_everything(ast: &DeriveInput, buf: &mut Buffer) { impl_display(ast, buf); impl_fast_writable(ast, buf); - if !only_template { - #[cfg(feature = "with-actix-web")] - impl_actix_web_responder(ast, buf); - #[cfg(feature = "with-axum")] - impl_axum_into_response(ast, buf); - #[cfg(feature = "with-rocket")] - impl_rocket_responder(ast, buf); - #[cfg(feature = "with-warp")] - impl_warp_reply(ast, buf); - } + #[cfg(feature = "with-actix-web")] + impl_actix_web_responder(ast, buf); + #[cfg(feature = "with-axum")] + impl_axum_into_response(ast, buf); + #[cfg(feature = "with-rocket")] + impl_rocket_responder(ast, buf); + #[cfg(feature = "with-warp")] + impl_warp_reply(ast, buf); } /// Writes header for the `impl` for `TraitFromPathName` or `Template` for the given item @@ -258,6 +256,14 @@ impl Buffer { self.buf.clear(); self.last_was_write_str = false; } + + pub(crate) fn get_mark(&mut self) -> usize { + self.buf.len() + } + + pub(crate) fn marked_text(&self, mark: usize) -> &str { + &self.buf[..mark] + } } pub(crate) trait BufferFmt { diff --git a/rinja_derive/src/lib.rs b/rinja_derive/src/lib.rs index 48de87c8..84352dde 100644 --- a/rinja_derive/src/lib.rs +++ b/rinja_derive/src/lib.rs @@ -22,6 +22,7 @@ use config::{Config, read_config_file}; use generator::template_to_string; use heritage::{Context, Heritage}; use input::{Print, TemplateArgs, TemplateInput}; +use integration::Buffer; use parser::{Parsed, WithSpan, strip_common}; #[cfg(not(feature = "__standalone"))] use proc_macro::TokenStream as TokenStream12; @@ -126,16 +127,18 @@ pub fn derive_template(input: TokenStream12) -> TokenStream12 { return compile_error(msgs, Span::call_site()).into(); } }; - match build_template(&ast) { - Ok(source) => source.parse().unwrap(), - Err(CompileError { msg, span }) => { - let mut ts = compile_error(std::iter::once(msg), span.unwrap_or(ast.ident.span())); - if let Ok(source) = build_skeleton(&ast) { - let source: TokenStream = source.parse().unwrap(); - ts.extend(source); - } - ts.into() + + let mut buf = Buffer::new(); + if let Err(CompileError { msg, span }) = build_template(&mut buf, &ast) { + let mut ts = compile_error(std::iter::once(msg), span.unwrap_or(ast.ident.span())); + buf.clear(); + if build_skeleton(&mut buf, &ast).is_ok() { + let source: TokenStream = buf.into_string().parse().unwrap(); + ts.extend(source); } + ts.into() + } else { + buf.into_string().parse().unwrap() } } @@ -150,14 +153,14 @@ fn compile_error(msgs: impl Iterator, span: Span) -> TokenStream } } -fn build_skeleton(ast: &syn::DeriveInput) -> Result { +fn build_skeleton(buf: &mut Buffer, ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::fallback(); let config = Config::new("", None, None, None)?; let input = TemplateInput::new(ast, config, &template_args)?; let mut contexts = HashMap::default(); let parsed = parser::Parsed::default(); contexts.insert(&input.path, Context::empty(&parsed)); - template_to_string(&input, &contexts, None) + template_to_string(buf, &input, &contexts, None, None) } /// Takes a `syn::DeriveInput` and generates source code for it @@ -167,9 +170,12 @@ fn build_skeleton(ast: &syn::DeriveInput) -> Result { /// parsed, and the parse tree is fed to the code generator. Will print /// the parse tree and/or generated source according to the `print` key's /// value as passed to the `template()` attribute. -pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { +pub(crate) fn build_template( + buf: &mut Buffer, + ast: &syn::DeriveInput, +) -> Result { let template_args = TemplateArgs::new(ast)?; - let mut result = build_template_inner(ast, &template_args); + let mut result = build_template_item(buf, ast, &template_args, None); if let Err(err) = &mut result { if err.span.is_none() { err.span = template_args.source.1.or(template_args.template_span); @@ -178,10 +184,12 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Result { + target: Option<&str>, +) -> Result { let config_path = template_args.config_path(); let s = read_config_file(config_path, template_args.config_span)?; let config = Config::new( @@ -222,11 +230,12 @@ fn build_template_inner( eprintln!("{:?}", templates[&input.path].nodes()); } - let code = template_to_string(&input, &contexts, heritage.as_ref())?; + let mark = buf.get_mark(); + let size_hint = template_to_string(buf, &input, &contexts, heritage.as_ref(), target)?; if input.print == Print::Code || input.print == Print::All { - eprintln!("{code}"); + eprintln!("{}", buf.marked_text(mark)); } - Ok(code) + Ok(size_hint) } #[derive(Debug, Clone)] diff --git a/rinja_derive/src/tests.rs b/rinja_derive/src/tests.rs index b83a477b..280cb4f1 100644 --- a/rinja_derive/src/tests.rs +++ b/rinja_derive/src/tests.rs @@ -7,7 +7,13 @@ use console::style; use prettyplease::unparse; use similar::{Algorithm, ChangeTag, TextDiffConfig}; -use crate::build_template; +use crate::integration::Buffer; + +fn build_template(ast: &syn::DeriveInput) -> Result { + let mut buf = Buffer::new(); + crate::build_template(&mut buf, ast)?; + Ok(buf.into_string()) +} // This function makes it much easier to compare expected code by adding the wrapping around // the code we want to check. From ebbd9ce4c88824c6614a89753ac10c5c2b7e75e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 24 Nov 2024 17:59:51 +0100 Subject: [PATCH 5/6] derive: make `PartialTemplateArgs::new()` return `None` if there is no `#[template]` --- rinja_derive/src/input.rs | 46 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/rinja_derive/src/input.rs b/rinja_derive/src/input.rs index d3c41a2c..da7468a3 100644 --- a/rinja_derive/src/input.rs +++ b/rinja_derive/src/input.rs @@ -290,11 +290,18 @@ pub(crate) struct TemplateArgs { impl TemplateArgs { pub(crate) fn new(ast: &syn::DeriveInput) -> Result { - let args = PartialTemplateArgs::new(ast, &ast.attrs)?; - let Some(template) = args.template else { - return Err(CompileError::no_file_info( + Self::from_partial(ast, PartialTemplateArgs::new(ast, &ast.attrs)?) + } + + pub(crate) fn from_partial( + ast: &syn::DeriveInput, + args: Option, + ) -> Result { + let Some(args) = args else { + return Err(CompileError::new_with_span( "no attribute `template` found", None, + Some(ast.ident.span()), )); }; Ok(Self { @@ -313,7 +320,7 @@ impl TemplateArgs { "specify one template argument `path` or `source`", #[cfg(feature = "code-in-doc")] "specify one template argument `path`, `source` or `in_doc`", - Some(template.span()), + Some(args.template.span()), )); } }, @@ -325,7 +332,7 @@ impl TemplateArgs { syntax: args.syntax.map(|value| value.value()), config: args.config.as_ref().map(|value| value.value()), whitespace: args.whitespace, - template_span: Some(template.span()), + template_span: Some(args.template.span()), config_span: args.config.as_ref().map(|value| value.span()), }) } @@ -638,9 +645,8 @@ pub(crate) fn get_template_source( ) } -#[derive(Default)] pub(crate) struct PartialTemplateArgs { - pub(crate) template: Option, + pub(crate) template: Ident, pub(crate) source: Option, pub(crate) block: Option, pub(crate) print: Option, @@ -665,7 +671,7 @@ const _: () = { pub(crate) fn new( ast: &syn::DeriveInput, attrs: &[Attribute], - ) -> Result { + ) -> Result, CompileError> { new(ast, attrs) } } @@ -674,7 +680,7 @@ const _: () = { fn new( ast: &syn::DeriveInput, attrs: &[Attribute], - ) -> Result { + ) -> Result, CompileError> { // FIXME: implement once is stable if let syn::Data::Union(data) = &ast.data { return Err(CompileError::new_with_span( @@ -687,13 +693,26 @@ const _: () = { #[cfg(feature = "code-in-doc")] let mut meta_docs = vec![]; - let mut this = PartialTemplateArgs::default(); + let mut this = PartialTemplateArgs { + template: Ident::new("template", Span::call_site()), + source: None, + block: None, + print: None, + escape: None, + ext: None, + syntax: None, + config: None, + whitespace: None, + }; + let mut has_data = false; + for attr in attrs { let Some(ident) = attr.path().get_ident() else { continue; }; if ident == "template" { - this.template = Some(ident.clone()); + this.template = ident.clone(); + has_data = true; } else { #[cfg(feature = "code-in-doc")] if ident == "doc" { @@ -777,6 +796,9 @@ const _: () = { } } } + if !has_data { + return Ok(None); + } #[cfg(feature = "code-in-doc")] if let Some(PartialTemplateArgsSource::InDoc(lit_span, _)) = this.source { @@ -787,7 +809,7 @@ const _: () = { )); } - Ok(this) + Ok(Some(this)) } fn set_strlit_pair( From 87ee5fb94850b67d789b87a09d475b821355795b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 24 Nov 2024 18:02:19 +0100 Subject: [PATCH 6/6] Templates always implement `FastWritable` --- rinja/src/lib.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/rinja/src/lib.rs b/rinja/src/lib.rs index 4d8039ff..afe80f78 100644 --- a/rinja/src/lib.rs +++ b/rinja/src/lib.rs @@ -95,7 +95,7 @@ pub use crate::helpers::PrimitiveType; /// `.render()`. /// /// [dynamic methods calls]: -pub trait Template: fmt::Display { +pub trait Template: fmt::Display + filters::FastWritable { /// Helper method which allocates a new `String` and renders into it fn render(&self) -> Result { let mut buf = String::new(); @@ -152,17 +152,17 @@ pub trait Template: fmt::Display { impl Template for &T { #[inline] fn render_into(&self, writer: &mut W) -> Result<()> { - T::render_into(self, writer) + ::render_into(self, writer) } #[inline] fn render(&self) -> Result { - T::render(self) + ::render(self) } #[inline] fn write_into(&self, writer: &mut W) -> io::Result<()> { - T::write_into(self, writer) + ::write_into(self, writer) } const SIZE_HINT: usize = T::SIZE_HINT; @@ -254,6 +254,7 @@ mod tests { #[test] fn dyn_template() { struct Test; + impl Template for Test { fn render_into(&self, writer: &mut W) -> Result<()> { Ok(writer.write_str("test")?) @@ -271,6 +272,13 @@ mod tests { } } + impl filters::FastWritable for Test { + #[inline] + fn write_into(&self, f: &mut W) -> crate::Result<()> { + self.render_into(f) + } + } + fn render(t: &dyn DynTemplate) -> String { t.dyn_render().unwrap() }