Merge pull request #266 from Kijewski/pr-enum-optimizations

Second round of optimizations extracted from PR to implement `enum` variants
This commit is contained in:
Guillaume Gomez 2024-11-24 20:45:09 +01:00 committed by GitHub
commit 829ebb0ab3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 176 additions and 110 deletions

View File

@ -95,7 +95,7 @@ pub use crate::helpers::PrimitiveType;
/// `.render()`.
///
/// [dynamic methods calls]: <https://doc.rust-lang.org/stable/std/keyword.dyn.html>
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<String> {
let mut buf = String::new();
@ -152,17 +152,17 @@ pub trait Template: fmt::Display {
impl<T: Template + ?Sized> Template for &T {
#[inline]
fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
T::render_into(self, writer)
<T as Template>::render_into(self, writer)
}
#[inline]
fn render(&self) -> Result<String> {
T::render(self)
<T as Template>::render(self)
}
#[inline]
fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
T::write_into(self, writer)
<T as Template>::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<W: fmt::Write + ?Sized>(&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<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
self.render_into(f)
}
}
fn render(t: &dyn DynTemplate) -> String {
t.dyn_render().unwrap()
}

View File

@ -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<Vec<&'a str>>,

View File

@ -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<Path>, Context<'_>, FxBuildHasher>,
heritage: Option<&Heritage<'_, '_>>,
) -> Result<String, CompileError> {
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<Path>, Context<'_>, FxBuildHasher>,
heritage: Option<&Heritage<'_, '_>>,
buf: &mut Buffer,
only_template: bool,
) -> Result<(), CompileError> {
input: &TemplateInput<'_>,
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
heritage: Option<&Heritage<'_, '_>>,
target: Option<&str>,
) -> Result<usize, CompileError> {
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<usize, CompileError> {
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<T, F>(&mut self, callback: F) -> Result<T, CompileError>
@ -178,8 +163,9 @@ impl<'a, 'h> Generator<'a, 'h> {
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
target: &str,
) -> Result<usize, CompileError> {
write_header(self.input.ast, buf, "rinja::Template", None);
write_header(self.input.ast, buf, target, None);
buf.write(
"fn render_into<RinjaW>(&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<usize, CompileError> {
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(

View File

@ -290,41 +290,37 @@ pub(crate) struct TemplateArgs {
impl TemplateArgs {
pub(crate) fn new(ast: &syn::DeriveInput) -> Result<Self, CompileError> {
// FIXME: implement once <https://github.com/rust-lang/rfcs/pull/3715> 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),
));
}
Self::from_partial(ast, PartialTemplateArgs::new(ast, &ast.attrs)?)
}
let args = PartialTemplateArgs::new(&ast.attrs)?;
let Some(template) = args.template else {
return Err(CompileError::no_file_info(
pub(crate) fn from_partial(
ast: &syn::DeriveInput,
args: Option<PartialTemplateArgs>,
) -> Result<Self, CompileError> {
let Some(args) = args else {
return Err(CompileError::new_with_span(
"no attribute `template` found",
None,
Some(ast.ident.span()),
));
};
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()))
}
#[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"))]
"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()),
));
}
},
@ -336,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()),
})
}
@ -368,7 +364,7 @@ impl TemplateArgs {
#[cfg(feature = "code-in-doc")]
fn source_from_docs(
span: Span,
docs: &[Attribute],
docs: &[&Attribute],
ast: &syn::DeriveInput,
) -> Result<(Source, Option<Span>), CompileError> {
let (source_span, source) = collect_comment_blocks(span, docs, ast)?;
@ -380,7 +376,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<Span>, String), CompileError> {
let mut source_span: Option<Span> = None;
@ -547,9 +543,9 @@ fn extension(path: &Path) -> Option<&str> {
}
}
#[derive(Debug, Hash, PartialEq)]
#[derive(Debug, Clone, Hash, PartialEq)]
pub(crate) enum Source {
Path(String),
Path(Arc<str>),
Source(Arc<str>),
}
@ -649,10 +645,8 @@ pub(crate) fn get_template_source(
)
}
#[derive(Default)]
pub(crate) struct PartialTemplateArgs {
pub(crate) template: Option<Ident>,
pub(crate) meta_docs: Vec<Attribute>,
pub(crate) template: Ident,
pub(crate) source: Option<PartialTemplateArgsSource>,
pub(crate) block: Option<LitStr>,
pub(crate) print: Option<Print>,
@ -663,34 +657,67 @@ pub(crate) struct PartialTemplateArgs {
pub(crate) whitespace: Option<Whitespace>,
}
#[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<Self, CompileError> {
new(attrs)
pub(crate) fn new(
ast: &syn::DeriveInput,
attrs: &[Attribute],
) -> Result<Option<Self>, CompileError> {
new(ast, attrs)
}
}
#[inline]
fn new(attrs: &[Attribute]) -> Result<PartialTemplateArgs, CompileError> {
let mut this = PartialTemplateArgs::default();
fn new(
ast: &syn::DeriveInput,
attrs: &[Attribute],
) -> Result<Option<PartialTemplateArgs>, CompileError> {
// FIXME: implement once <https://github.com/rust-lang/rfcs/pull/3715> 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 {
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 == "doc" {
this.meta_docs.push(attr.clone());
continue;
} else if ident == "template" {
this.template = Some(ident.clone());
if ident == "template" {
this.template = ident.clone();
has_data = true;
} else {
#[cfg(feature = "code-in-doc")]
if ident == "doc" {
meta_docs.push(attr);
}
continue;
}
@ -742,7 +769,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,7 +796,20 @@ const _: () = {
}
}
}
Ok(this)
if !has_data {
return Ok(None);
}
#[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(Some(this))
}
fn set_strlit_pair(

View File

@ -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 {

View File

@ -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<Item = String>, span: Span) -> TokenStream
}
}
fn build_skeleton(ast: &syn::DeriveInput) -> Result<String, CompileError> {
fn build_skeleton(buf: &mut Buffer, ast: &syn::DeriveInput) -> Result<usize, CompileError> {
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<String, CompileError> {
/// 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<String, CompileError> {
pub(crate) fn build_template(
buf: &mut Buffer,
ast: &syn::DeriveInput,
) -> Result<usize, CompileError> {
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<String, CompileEr
result
}
fn build_template_inner(
fn build_template_item(
buf: &mut Buffer,
ast: &syn::DeriveInput,
template_args: &TemplateArgs,
) -> Result<String, CompileError> {
target: Option<&str>,
) -> Result<usize, CompileError> {
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)]

View File

@ -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<String, crate::CompileError> {
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.