Merge pull request #257 from Kijewski/pr-smaller-span

parser: shrink the size of `WithSpan` to one register
This commit is contained in:
René Kijewski 2024-11-24 22:47:10 +01:00 committed by GitHub
commit 87f611e871
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 217 additions and 129 deletions

View File

@ -10,8 +10,8 @@ use parser::node::{
Whitespace, Ws, Whitespace, Ws,
}; };
use parser::{ use parser::{
CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, StrLit, StrPrefix, Target, CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, Span, StrLit, StrPrefix,
WithSpan, Target, WithSpan,
}; };
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
@ -263,7 +263,7 @@ impl<'a, 'h> Generator<'a, 'h> {
} }
Node::BlockDef(ref b) => { Node::BlockDef(ref b) => {
size_hint += size_hint +=
self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1), b)?; self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1), b.span())?;
} }
Node::Include(ref i) => { Node::Include(ref i) => {
size_hint += self.handle_include(ctx, buf, i)?; size_hint += self.handle_include(ctx, buf, i)?;
@ -276,9 +276,10 @@ impl<'a, 'h> Generator<'a, 'h> {
} }
Node::Macro(ref m) => { Node::Macro(ref m) => {
if level != AstLevel::Top { if level != AstLevel::Top {
return Err( return Err(ctx.generate_error(
ctx.generate_error("macro blocks only allowed at the top level", m) "macro blocks only allowed at the top level",
); m.span(),
));
} }
self.flush_ws(m.ws1); self.flush_ws(m.ws1);
self.prepare_ws(m.ws2); self.prepare_ws(m.ws2);
@ -290,17 +291,19 @@ impl<'a, 'h> Generator<'a, 'h> {
} }
Node::Import(ref i) => { Node::Import(ref i) => {
if level != AstLevel::Top { if level != AstLevel::Top {
return Err( return Err(ctx.generate_error(
ctx.generate_error("import blocks only allowed at the top level", i) "import blocks only allowed at the top level",
); i.span(),
));
} }
self.handle_ws(i.ws); self.handle_ws(i.ws);
} }
Node::Extends(ref e) => { Node::Extends(ref e) => {
if level != AstLevel::Top { if level != AstLevel::Top {
return Err( return Err(ctx.generate_error(
ctx.generate_error("extend blocks only allowed at the top level", e) "extend blocks only allowed at the top level",
); e.span(),
));
} }
// No whitespace handling: child template top-level is not used, // No whitespace handling: child template top-level is not used,
// except for the blocks defined in it. // except for the blocks defined in it.
@ -746,26 +749,26 @@ impl<'a, 'h> Generator<'a, 'h> {
ref args, ref args,
} = **call; } = **call;
if name == "super" { if name == "super" {
return self.write_block(ctx, buf, None, ws, call); return self.write_block(ctx, buf, None, ws, call.span());
} }
let (def, own_ctx) = if let Some(s) = scope { let (def, own_ctx) = if let Some(s) = scope {
let path = ctx.imports.get(s).ok_or_else(|| { let path = ctx.imports.get(s).ok_or_else(|| {
ctx.generate_error(format_args!("no import found for scope {s:?}"), call) ctx.generate_error(format_args!("no import found for scope {s:?}"), call.span())
})?; })?;
let mctx = self.contexts.get(path).ok_or_else(|| { let mctx = self.contexts.get(path).ok_or_else(|| {
ctx.generate_error(format_args!("context for {path:?} not found"), call) ctx.generate_error(format_args!("context for {path:?} not found"), call.span())
})?; })?;
let def = mctx.macros.get(name).ok_or_else(|| { let def = mctx.macros.get(name).ok_or_else(|| {
ctx.generate_error( ctx.generate_error(
format_args!("macro {name:?} not found in scope {s:?}"), format_args!("macro {name:?} not found in scope {s:?}"),
call, call.span(),
) )
})?; })?;
(def, mctx) (def, mctx)
} else { } else {
let def = ctx.macros.get(name).ok_or_else(|| { let def = ctx.macros.get(name).ok_or_else(|| {
ctx.generate_error(format_args!("macro {name:?} not found"), call) ctx.generate_error(format_args!("macro {name:?} not found"), call.span())
})?; })?;
(def, ctx) (def, ctx)
}; };
@ -790,7 +793,7 @@ impl<'a, 'h> Generator<'a, 'h> {
if !def.args.iter().any(|(arg, _)| arg == arg_name) { if !def.args.iter().any(|(arg, _)| arg == arg_name) {
return Err(ctx.generate_error( return Err(ctx.generate_error(
format_args!("no argument named `{arg_name}` in macro {name:?}"), format_args!("no argument named `{arg_name}` in macro {name:?}"),
call, call.span(),
)); ));
} }
named_arguments.insert(arg_name, (index, arg)); named_arguments.insert(arg_name, (index, arg));
@ -824,7 +827,7 @@ impl<'a, 'h> Generator<'a, 'h> {
"cannot have unnamed argument (`{arg}`) after named argument \ "cannot have unnamed argument (`{arg}`) after named argument \
in call to macro {name:?}" in call to macro {name:?}"
), ),
call, call.span(),
)); ));
} }
arg_expr arg_expr
@ -833,14 +836,14 @@ impl<'a, 'h> Generator<'a, 'h> {
let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() }; let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() };
return Err(ctx.generate_error( return Err(ctx.generate_error(
format_args!("`{name}` is passed more than once"), format_args!("`{name}` is passed more than once"),
call, call.span(),
)); ));
} }
_ => { _ => {
if let Some(default_value) = default_value { if let Some(default_value) = default_value {
default_value default_value
} else { } else {
return Err(ctx.generate_error(format_args!("missing `{arg}` argument"), call)); return Err(ctx.generate_error(format_args!("missing `{arg}` argument"), call.span()));
} }
} }
} }
@ -932,7 +935,7 @@ impl<'a, 'h> Generator<'a, 'h> {
&mut filter_buf, &mut filter_buf,
filter.filters.name, filter.filters.name,
&filter.filters.arguments, &filter.filters.arguments,
filter, filter.span(),
)?; )?;
let filter_buf = match display_wrap { let filter_buf = match display_wrap {
DisplayWrap::Wrapped => filter_buf.into_string(), DisplayWrap::Wrapped => filter_buf.into_string(),
@ -961,7 +964,9 @@ impl<'a, 'h> Generator<'a, 'h> {
) -> Result<usize, CompileError> { ) -> Result<usize, CompileError> {
self.flush_ws(i.ws); self.flush_ws(i.ws);
self.write_buf_writable(ctx, buf)?; self.write_buf_writable(ctx, buf)?;
let file_info = ctx.path.map(|path| FileInfo::of(i, path, ctx.parsed)); let file_info = ctx
.path
.map(|path| FileInfo::of(i.span(), path, ctx.parsed));
let path = self let path = self
.input .input
.config .config
@ -1006,11 +1011,11 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(size_hint) Ok(size_hint)
} }
fn is_shadowing_variable<T>( fn is_shadowing_variable(
&self, &self,
ctx: &Context<'_>, ctx: &Context<'_>,
var: &Target<'a>, var: &Target<'a>,
l: &WithSpan<'_, T>, l: Span<'_>,
) -> Result<bool, CompileError> { ) -> Result<bool, CompileError> {
match var { match var {
Target::Name(name) => { Target::Name(name) => {
@ -1078,7 +1083,7 @@ impl<'a, 'h> Generator<'a, 'h> {
let mut expr_buf = Buffer::new(); let mut expr_buf = Buffer::new();
self.visit_expr(ctx, &mut expr_buf, val)?; self.visit_expr(ctx, &mut expr_buf, val)?;
let shadowed = self.is_shadowing_variable(ctx, &l.var, l)?; let shadowed = self.is_shadowing_variable(ctx, &l.var, l.span())?;
if shadowed { if shadowed {
// Need to flush the buffer if the variable is being shadowed, // Need to flush the buffer if the variable is being shadowed,
// to ensure the old variable is used. // to ensure the old variable is used.
@ -1104,13 +1109,13 @@ impl<'a, 'h> Generator<'a, 'h> {
// If `name` is `Some`, this is a call to a block definition, and we have to find // If `name` is `Some`, this is a call to a block definition, and we have to find
// the first block for that name from the ancestry chain. If name is `None`, this // the first block for that name from the ancestry chain. If name is `None`, this
// is from a `super()` call, and we can get the name from `self.super_block`. // is from a `super()` call, and we can get the name from `self.super_block`.
fn write_block<T>( fn write_block(
&mut self, &mut self,
ctx: &Context<'a>, ctx: &Context<'a>,
buf: &mut Buffer, buf: &mut Buffer,
name: Option<&'a str>, name: Option<&'a str>,
outer: Ws, outer: Ws,
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<usize, CompileError> { ) -> Result<usize, CompileError> {
if self.is_in_filter_block > 0 { if self.is_in_filter_block > 0 {
return Err(ctx.generate_error("cannot have a block inside a filter block", node)); return Err(ctx.generate_error("cannot have a block inside a filter block", node));
@ -1384,7 +1389,7 @@ impl<'a, 'h> Generator<'a, 'h> {
Expr::Filter(Filter { Expr::Filter(Filter {
name, name,
ref arguments, ref arguments,
}) => self.visit_filter(ctx, buf, name, arguments, expr)?, }) => self.visit_filter(ctx, buf, name, arguments, expr.span())?,
Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?, Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?,
Expr::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?, Expr::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?,
Expr::Range(op, ref left, ref right) => { Expr::Range(op, ref left, ref right) => {
@ -1507,13 +1512,13 @@ impl<'a, 'h> Generator<'a, 'h> {
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }
fn visit_filter<T>( fn visit_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
name: &str, name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
let filter = match name { let filter = match name {
"deref" => Self::_visit_deref_filter, "deref" => Self::_visit_deref_filter,
@ -1534,13 +1539,13 @@ impl<'a, 'h> Generator<'a, 'h> {
filter(self, ctx, buf, name, args, node) filter(self, ctx, buf, name, args, node)
} }
fn _visit_custom_filter<T>( fn _visit_custom_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
name: &str, name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>, _node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
buf.write(format_args!("filters::{name}(")); buf.write(format_args!("filters::{name}("));
self._visit_args(ctx, buf, args)?; self._visit_args(ctx, buf, args)?;
@ -1548,13 +1553,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_builtin_filter<T>( fn _visit_builtin_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
name: &str, name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>, _node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
buf.write(format_args!("rinja::filters::{name}(")); buf.write(format_args!("rinja::filters::{name}("));
self._visit_args(ctx, buf, args)?; self._visit_args(ctx, buf, args)?;
@ -1562,13 +1567,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_urlencode<T>( fn _visit_urlencode(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
name: &str, name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if cfg!(not(feature = "urlencode")) { if cfg!(not(feature = "urlencode")) {
return Err(ctx.generate_error( return Err(ctx.generate_error(
@ -1586,13 +1591,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_humansize<T>( fn _visit_humansize(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
name: &str, name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>, _node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
// All filters return numbers, and any default formatted number is HTML safe. // All filters return numbers, and any default formatted number is HTML safe.
buf.write(format_args!( buf.write(format_args!(
@ -1604,28 +1609,24 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_pluralize_filter<T>( fn _visit_pluralize_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
const SINGULAR: &WithSpan<'static, Expr<'static>> = &WithSpan::new( const SINGULAR: &WithSpan<'static, Expr<'static>> =
Expr::StrLit(StrLit { &WithSpan::new_without_span(Expr::StrLit(StrLit {
prefix: None, prefix: None,
content: "", content: "",
}), }));
"", const PLURAL: &WithSpan<'static, Expr<'static>> =
); &WithSpan::new_without_span(Expr::StrLit(StrLit {
const PLURAL: &WithSpan<'static, Expr<'static>> = &WithSpan::new(
Expr::StrLit(StrLit {
prefix: None, prefix: None,
content: "s", content: "s",
}), }));
"",
);
let (count, sg, pl) = match args { let (count, sg, pl) = match args {
[count] => (count, SINGULAR, PLURAL), [count] => (count, SINGULAR, PLURAL),
@ -1652,13 +1653,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Wrapped) Ok(DisplayWrap::Wrapped)
} }
fn _visit_linebreaks_filter<T>( fn _visit_linebreaks_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
name: &str, name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if args.len() != 1 { if args.len() != 1 {
return Err(ctx.generate_error( return Err(ctx.generate_error(
@ -1676,13 +1677,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_ref_filter<T>( fn _visit_ref_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
let arg = match args { let arg = match args {
[arg] => arg, [arg] => arg,
@ -1693,13 +1694,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_deref_filter<T>( fn _visit_deref_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
let arg = match args { let arg = match args {
[arg] => arg, [arg] => arg,
@ -1710,13 +1711,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_json_filter<T>( fn _visit_json_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if cfg!(not(feature = "serde_json")) { if cfg!(not(feature = "serde_json")) {
return Err(ctx.generate_error( return Err(ctx.generate_error(
@ -1737,13 +1738,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
fn _visit_safe_filter<T>( fn _visit_safe_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if args.len() != 1 { if args.len() != 1 {
return Err(ctx.generate_error("unexpected argument(s) in `safe` filter", node)); return Err(ctx.generate_error("unexpected argument(s) in `safe` filter", node));
@ -1754,13 +1755,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Wrapped) Ok(DisplayWrap::Wrapped)
} }
fn _visit_escape_filter<T>( fn _visit_escape_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if args.len() > 2 { if args.len() > 2 {
return Err(ctx.generate_error("only two arguments allowed to escape filter", node)); return Err(ctx.generate_error("only two arguments allowed to escape filter", node));
@ -1777,7 +1778,7 @@ impl<'a, 'h> Generator<'a, 'h> {
format_args!( format_args!(
"invalid escaper `b{content:?}`. Expected a string, found a {kind}" "invalid escaper `b{content:?}`. Expected a string, found a {kind}"
), ),
&args[1], args[1].span(),
)); ));
} }
Some(content) Some(content)
@ -1815,13 +1816,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Ok(DisplayWrap::Wrapped) Ok(DisplayWrap::Wrapped)
} }
fn _visit_format_filter<T>( fn _visit_format_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if !args.is_empty() { if !args.is_empty() {
if let Expr::StrLit(ref fmt) = *args[0] { if let Expr::StrLit(ref fmt) = *args[0] {
@ -1838,13 +1839,13 @@ impl<'a, 'h> Generator<'a, 'h> {
Err(ctx.generate_error(r#"use filter format like `"a={} b={}"|format(a, b)`"#, node)) Err(ctx.generate_error(r#"use filter format like `"a={} b={}"|format(a, b)`"#, node))
} }
fn _visit_fmt_filter<T>( fn _visit_fmt_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
node: &WithSpan<'_, T>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if let [_, arg2] = args { if let [_, arg2] = args {
if let Expr::StrLit(ref fmt) = **arg2 { if let Expr::StrLit(ref fmt) = **arg2 {
@ -1860,13 +1861,13 @@ impl<'a, 'h> Generator<'a, 'h> {
} }
// Force type coercion on first argument to `join` filter (see #39). // Force type coercion on first argument to `join` filter (see #39).
fn _visit_join_filter<T>( fn _visit_join_filter(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
_name: &str, _name: &str,
args: &[WithSpan<'_, Expr<'_>>], args: &[WithSpan<'_, Expr<'_>>],
_node: &WithSpan<'_, T>, _node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
buf.write("rinja::filters::join((&"); buf.write("rinja::filters::join((&");
for (i, arg) in args.iter().enumerate() { for (i, arg) in args.iter().enumerate() {
@ -1986,7 +1987,7 @@ impl<'a, 'h> Generator<'a, 'h> {
buf.write("_loop_item.last"); buf.write("_loop_item.last");
return Ok(DisplayWrap::Unwrapped); return Ok(DisplayWrap::Unwrapped);
} else { } else {
return Err(ctx.generate_error("unknown loop variable", obj)); return Err(ctx.generate_error("unknown loop variable", obj.span()));
} }
} }
} }
@ -2022,9 +2023,10 @@ impl<'a, 'h> Generator<'a, 'h> {
"cycle" => match args { "cycle" => match args {
[arg] => { [arg] => {
if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) { if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) {
return Err( return Err(ctx.generate_error(
ctx.generate_error("loop.cycle(…) cannot use an empty array", arg) "loop.cycle(…) cannot use an empty array",
); arg.span(),
));
} }
buf.write( buf.write(
"\ "\
@ -2044,14 +2046,15 @@ impl<'a, 'h> Generator<'a, 'h> {
); );
} }
_ => { _ => {
return Err( return Err(ctx.generate_error(
ctx.generate_error("loop.cycle(…) cannot use an empty array", left) "loop.cycle(…) cannot use an empty array",
); left.span(),
));
} }
}, },
s => { s => {
return Err( return Err(
ctx.generate_error(format_args!("unknown loop method: {s:?}"), left) ctx.generate_error(format_args!("unknown loop method: {s:?}"), left.span())
); );
} }
}, },
@ -2421,7 +2424,7 @@ fn macro_call_ensure_arg_count(
if expected_args != 1 { "s" } else { "" }, if expected_args != 1 { "s" } else { "" },
call.args.len(), call.args.len(),
), ),
call, call.span(),
)) ))
} }

View File

@ -4,7 +4,7 @@ use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use parser::node::{BlockDef, Macro}; use parser::node::{BlockDef, Macro};
use parser::{Node, Parsed, WithSpan}; use parser::{Node, Parsed, Span};
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use crate::config::Config; use crate::config::Config;
@ -80,29 +80,29 @@ impl Context<'_> {
for n in nodes { for n in nodes {
match n { match n {
Node::Extends(e) => { Node::Extends(e) => {
ensure_top(top, e, path, parsed, "extends")?; ensure_top(top, e.span(), path, parsed, "extends")?;
if extends.is_some() { if extends.is_some() {
return Err(CompileError::new( return Err(CompileError::new(
"multiple extend blocks found", "multiple extend blocks found",
Some(FileInfo::of(e, path, parsed)), Some(FileInfo::of(e.span(), path, parsed)),
)); ));
} }
extends = Some(config.find_template( extends = Some(config.find_template(
e.path, e.path,
Some(path), Some(path),
Some(FileInfo::of(e, path, parsed)), Some(FileInfo::of(e.span(), path, parsed)),
)?); )?);
} }
Node::Macro(m) => { Node::Macro(m) => {
ensure_top(top, m, path, parsed, "macro")?; ensure_top(top, m.span(), path, parsed, "macro")?;
macros.insert(m.name, &**m); macros.insert(m.name, &**m);
} }
Node::Import(import) => { Node::Import(import) => {
ensure_top(top, import, path, parsed, "import")?; ensure_top(top, import.span(), path, parsed, "import")?;
let path = config.find_template( let path = config.find_template(
import.path, import.path,
Some(path), Some(path),
Some(FileInfo::of(import, path, parsed)), Some(FileInfo::of(import.span(), path, parsed)),
)?; )?;
imports.insert(import.scope, path); imports.insert(import.scope, path);
} }
@ -141,11 +141,7 @@ impl Context<'_> {
}) })
} }
pub(crate) fn generate_error<T>( pub(crate) fn generate_error(&self, msg: impl fmt::Display, node: Span<'_>) -> CompileError {
&self,
msg: impl fmt::Display,
node: &WithSpan<'_, T>,
) -> CompileError {
CompileError::new( CompileError::new(
msg, msg,
self.path.map(|path| FileInfo::of(node, path, self.parsed)), self.path.map(|path| FileInfo::of(node, path, self.parsed)),
@ -153,9 +149,9 @@ impl Context<'_> {
} }
} }
fn ensure_top<T>( fn ensure_top(
top: bool, top: bool,
node: &WithSpan<'_, T>, node: Span<'_>,
path: &Path, path: &Path,
parsed: &Parsed, parsed: &Parsed,
kind: &str, kind: &str,

View File

@ -185,9 +185,14 @@ impl TemplateInput<'_> {
// Add a dummy entry to `map` in order to prevent adding `path` // Add a dummy entry to `map` in order to prevent adding `path`
// multiple times to `check`. // multiple times to `check`.
let new_path = e.key(); let new_path = e.key();
let source = parsed.source();
let source = get_template_source( let source = get_template_source(
new_path, new_path,
Some((&path, parsed.source(), n.span())), Some((
&path,
source,
n.span().as_suffix_of(source).unwrap_or_default(),
)),
)?; )?;
check.push((new_path.clone(), source, Some(new_path.clone()))); check.push((new_path.clone(), source, Some(new_path.clone())));
e.insert(Arc::default()); e.insert(Arc::default());
@ -200,7 +205,7 @@ impl TemplateInput<'_> {
let extends = self.config.find_template( let extends = self.config.find_template(
extends.path, extends.path,
Some(&path), Some(&path),
Some(FileInfo::of(extends, &path, &parsed)), Some(FileInfo::of(extends.span(), &path, &parsed)),
)?; )?;
let dependency_path = (path.clone(), extends.clone()); let dependency_path = (path.clone(), extends.clone());
if path == extends { if path == extends {
@ -220,7 +225,7 @@ impl TemplateInput<'_> {
let import = self.config.find_template( let import = self.config.find_template(
import.path, import.path,
Some(&path), Some(&path),
Some(FileInfo::of(import, &path, &parsed)), Some(FileInfo::of(import.span(), &path, &parsed)),
)?; )?;
add_to_check(import)?; add_to_check(import)?;
} }
@ -231,7 +236,7 @@ impl TemplateInput<'_> {
let include = self.config.find_template( let include = self.config.find_template(
include.path, include.path,
Some(&path), Some(&path),
Some(FileInfo::of(include, &path, &parsed)), Some(FileInfo::of(include.span(), &path, &parsed)),
)?; )?;
add_to_check(include)?; add_to_check(include)?;
} }

View File

@ -23,7 +23,7 @@ use generator::template_to_string;
use heritage::{Context, Heritage}; use heritage::{Context, Heritage};
use input::{Print, TemplateArgs, TemplateInput}; use input::{Print, TemplateArgs, TemplateInput};
use integration::Buffer; use integration::Buffer;
use parser::{Parsed, WithSpan, strip_common}; use parser::{Parsed, strip_common};
#[cfg(not(feature = "__standalone"))] #[cfg(not(feature = "__standalone"))]
use proc_macro::TokenStream as TokenStream12; use proc_macro::TokenStream as TokenStream12;
#[cfg(feature = "__standalone")] #[cfg(feature = "__standalone")]
@ -294,11 +294,12 @@ impl<'a> FileInfo<'a> {
} }
} }
fn of<T>(node: &WithSpan<'a, T>, path: &'a Path, parsed: &'a Parsed) -> Self { fn of(node: parser::Span<'a>, path: &'a Path, parsed: &'a Parsed) -> Self {
let source = parsed.source();
Self { Self {
path, path,
source: Some(parsed.source()), source: Some(source),
node_source: Some(node.span()), node_source: node.as_suffix_of(source),
} }
} }
} }

View File

@ -10,9 +10,9 @@ use winnow::combinator::{
use winnow::error::{ErrorKind, ParserError as _}; use winnow::error::{ErrorKind, ParserError as _};
use crate::{ use crate::{
CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, StrLit, WithSpan, CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, Span, StrLit,
char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0, skip_ws1, WithSpan, char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0,
str_lit, ws, skip_ws1, str_lit, ws,
}; };
macro_rules! expr_prec_layer { macro_rules! expr_prec_layer {
@ -217,14 +217,14 @@ impl<'a> Expr<'a> {
allow_underscore: bool, allow_underscore: bool,
) -> ParseResult<'a, WithSpan<'a, Self>> { ) -> ParseResult<'a, WithSpan<'a, Self>> {
let (_, level) = level.nest(i)?; let (_, level) = level.nest(i)?;
let start = i; let start = Span::from(i);
let range_right = let range_right =
move |i| (ws(alt(("..=", ".."))), opt(move |i| Self::or(i, level))).parse_next(i); move |i| (ws(alt(("..=", ".."))), opt(move |i| Self::or(i, level))).parse_next(i);
let (i, expr) = alt(( let (i, expr) = alt((
range_right.map(|(op, right)| { range_right.map(move |(op, right)| {
WithSpan::new(Self::Range(op, None, right.map(Box::new)), start) WithSpan::new(Self::Range(op, None, right.map(Box::new)), start)
}), }),
(move |i| Self::or(i, level), opt(range_right)).map(|(left, right)| match right { (move |i| Self::or(i, level), opt(range_right)).map(move |(left, right)| match right {
Some((op, right)) => WithSpan::new( Some((op, right)) => WithSpan::new(
Self::Range(op, Some(Box::new(left)), right.map(Box::new)), Self::Range(op, Some(Box::new(left)), right.map(Box::new)),
start, start,

View File

@ -114,11 +114,11 @@ impl<'a> Ast<'a> {
Ok(("", nodes)) => Ok(Self { nodes }), Ok(("", nodes)) => Ok(Self { nodes }),
Ok(_) | Err(winnow::error::ErrMode::Incomplete(_)) => unreachable!(), Ok(_) | Err(winnow::error::ErrMode::Incomplete(_)) => unreachable!(),
Err( Err(
winnow::error::ErrMode::Backtrack(ErrorContext { input, message, .. }) winnow::error::ErrMode::Backtrack(ErrorContext { span, message, .. })
| winnow::error::ErrMode::Cut(ErrorContext { input, message, .. }), | winnow::error::ErrMode::Cut(ErrorContext { span, message, .. }),
) => Err(ParseError { ) => Err(ParseError {
message, message,
offset: src.len() - input.len(), offset: span.offset_from(src).unwrap_or_default(),
file_path, file_path,
}), }),
} }
@ -134,19 +134,76 @@ impl<'a> Ast<'a> {
/// in the code generation. /// in the code generation.
pub struct WithSpan<'a, T> { pub struct WithSpan<'a, T> {
inner: T, inner: T,
span: &'a str, span: Span<'a>,
}
/// An location in `&'a str`
#[derive(Debug, Clone, Copy)]
pub struct Span<'a>(&'a [u8; 0]);
impl Default for Span<'static> {
#[inline]
fn default() -> Self {
Self::empty()
}
}
impl<'a> Span<'a> {
#[inline]
pub const fn empty() -> Self {
Self(&[])
}
pub fn offset_from(self, start: &'a str) -> Option<usize> {
let start_range = start.as_bytes().as_ptr_range();
let this_ptr = self.0.as_slice().as_ptr();
match start_range.contains(&this_ptr) {
// SAFETY: we just checked that `this_ptr` is inside `start_range`
true => Some(unsafe { this_ptr.offset_from(start_range.start) as usize }),
false => None,
}
}
pub fn as_suffix_of(self, start: &'a str) -> Option<&'a str> {
let offset = self.offset_from(start)?;
match start.is_char_boundary(offset) {
true => Some(&start[offset..]),
false => None,
}
}
}
impl<'a> From<&'a str> for Span<'a> {
#[inline]
fn from(value: &'a str) -> Self {
Self(value[..0].as_bytes().try_into().unwrap())
}
} }
impl<'a, T> WithSpan<'a, T> { impl<'a, T> WithSpan<'a, T> {
pub const fn new(inner: T, span: &'a str) -> Self { #[inline]
Self { inner, span } pub fn new(inner: T, span: impl Into<Span<'a>>) -> Self {
Self {
inner,
span: span.into(),
}
} }
pub fn span(&self) -> &'a str { #[inline]
pub const fn new_without_span(inner: T) -> Self {
Self {
inner,
span: Span::empty(),
}
}
#[inline]
pub fn span(&self) -> Span<'a> {
self.span self.span
} }
pub fn deconstruct(self) -> (T, &'a str) { #[inline]
pub fn deconstruct(self) -> (T, Span<'a>) {
let Self { inner, span } = self; let Self { inner, span } = self;
(inner, span) (inner, span)
} }
@ -229,18 +286,18 @@ pub(crate) type ParseResult<'a, T = &'a str> = Result<(&'a str, T), ParseErr<'a>
/// `rinja`'s users experience less good (since this generic is only needed for `nom`). /// `rinja`'s users experience less good (since this generic is only needed for `nom`).
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ErrorContext<'a> { pub(crate) struct ErrorContext<'a> {
pub(crate) input: &'a str, pub(crate) span: Span<'a>,
pub(crate) message: Option<Cow<'static, str>>, pub(crate) message: Option<Cow<'static, str>>,
} }
impl<'a> ErrorContext<'a> { impl<'a> ErrorContext<'a> {
fn unclosed(kind: &str, tag: &str, i: &'a str) -> Self { fn unclosed(kind: &str, tag: &str, span: impl Into<Span<'a>>) -> Self {
Self::new(format!("unclosed {kind}, missing {tag:?}"), i) Self::new(format!("unclosed {kind}, missing {tag:?}"), span)
} }
fn new(message: impl Into<Cow<'static, str>>, input: &'a str) -> Self { fn new(message: impl Into<Cow<'static, str>>, span: impl Into<Span<'a>>) -> Self {
Self { Self {
input, span: span.into(),
message: Some(message.into()), message: Some(message.into()),
} }
} }
@ -249,7 +306,7 @@ impl<'a> ErrorContext<'a> {
impl<'a> winnow::error::ParserError<&'a str> for ErrorContext<'a> { impl<'a> winnow::error::ParserError<&'a str> for ErrorContext<'a> {
fn from_error_kind(input: &'a str, _code: ErrorKind) -> Self { fn from_error_kind(input: &'a str, _code: ErrorKind) -> Self {
Self { Self {
input, span: input.into(),
message: None, message: None,
} }
} }
@ -262,7 +319,7 @@ impl<'a> winnow::error::ParserError<&'a str> for ErrorContext<'a> {
impl<'a, E: std::fmt::Display> FromExternalError<&'a str, E> for ErrorContext<'a> { impl<'a, E: std::fmt::Display> FromExternalError<&'a str, E> for ErrorContext<'a> {
fn from_external_error(input: &'a str, _kind: ErrorKind, e: E) -> Self { fn from_external_error(input: &'a str, _kind: ErrorKind, e: E) -> Self {
Self { Self {
input, span: input.into(),
message: Some(Cow::Owned(e.to_string())), message: Some(Cow::Owned(e.to_string())),
} }
} }
@ -1213,3 +1270,11 @@ mod test {
assert!(str_lit.parse_next(r#"d"hello""#).is_err()); assert!(str_lit.parse_next(r#"d"hello""#).is_err());
} }
} }
#[test]
fn assert_span_size() {
assert_eq!(
std::mem::size_of::<Span<'static>>(),
std::mem::size_of::<*const ()>()
);
}

View File

@ -9,8 +9,8 @@ use winnow::token::{any, tag};
use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3}; use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3};
use crate::{ use crate::{
ErrorContext, Expr, Filter, ParseResult, State, Target, WithSpan, filter, identifier, keyword, ErrorContext, Expr, Filter, ParseResult, Span, State, Target, WithSpan, filter, identifier,
skip_till, skip_ws0, str_lit_without_prefix, ws, keyword, skip_till, skip_ws0, str_lit_without_prefix, ws,
}; };
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -43,7 +43,9 @@ impl<'a> Node<'a> {
&err &err
{ {
if err.message.is_none() { if err.message.is_none() {
opt(|i| unexpected_tag(i, s)).parse_next(err.input)?; if let Some(span) = err.span.as_suffix_of(i) {
opt(|i| unexpected_tag(i, s)).parse_next(span)?;
}
} }
} }
return Err(err); return Err(err);
@ -184,7 +186,7 @@ impl<'a> Node<'a> {
} }
#[must_use] #[must_use]
pub fn span(&self) -> &str { pub fn span(&self) -> Span<'a> {
match self { match self {
Self::Lit(span) => span.span, Self::Lit(span) => span.span,
Self::Comment(span) => span.span, Self::Comment(span) => span.span,
@ -218,7 +220,9 @@ fn cut_node<'a, O>(
&result &result
{ {
if err.message.is_none() { if err.message.is_none() {
opt(|i| unexpected_raw_tag(kind, i)).parse_next(err.input)?; if let Some(span) = err.span.as_suffix_of(i) {
opt(|i| unexpected_raw_tag(kind, i)).parse_next(span)?;
}
} }
} }
result result

View File

@ -1,9 +1,14 @@
use crate::node::{Lit, Whitespace, Ws}; use crate::node::{Lit, Whitespace, Ws};
use crate::{Ast, Expr, Filter, InnerSyntax, Node, Num, StrLit, Syntax, SyntaxBuilder, WithSpan}; use crate::{
Ast, Expr, Filter, InnerSyntax, Node, Num, Span, StrLit, Syntax, SyntaxBuilder, WithSpan,
};
impl<T> WithSpan<'static, T> { impl<T> WithSpan<'static, T> {
fn no_span(inner: T) -> Self { fn no_span(inner: T) -> Self {
Self { inner, span: "" } Self {
inner,
span: Span::default(),
}
} }
} }
@ -1122,3 +1127,12 @@ fn extends_with_whitespace_control() {
} }
} }
} }
#[test]
fn fuzzed_span_is_not_substring_of_source() {
let _: Result<Ast<'_>, crate::ParseError> = Ast::from_str(
include_str!("../tests/fuzzed_span_is_not_substring_of_source.bin"),
None,
&Syntax::default(),
);
}