diff --git a/rinja_derive/src/generator.rs b/rinja_derive/src/generator.rs index f1189bfb0..cafc009ac 100644 --- a/rinja_derive/src/generator.rs +++ b/rinja_derive/src/generator.rs @@ -10,8 +10,8 @@ use parser::node::{ Whitespace, Ws, }; use parser::{ - CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, StrLit, StrPrefix, Target, - WithSpan, + CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, Span, StrLit, StrPrefix, + Target, WithSpan, }; use rustc_hash::FxBuildHasher; @@ -263,7 +263,7 @@ impl<'a, 'h> Generator<'a, 'h> { } Node::BlockDef(ref b) => { 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) => { size_hint += self.handle_include(ctx, buf, i)?; @@ -276,9 +276,10 @@ impl<'a, 'h> Generator<'a, 'h> { } Node::Macro(ref m) => { if level != AstLevel::Top { - return Err( - ctx.generate_error("macro blocks only allowed at the top level", m) - ); + return Err(ctx.generate_error( + "macro blocks only allowed at the top level", + m.span(), + )); } self.flush_ws(m.ws1); self.prepare_ws(m.ws2); @@ -290,17 +291,19 @@ impl<'a, 'h> Generator<'a, 'h> { } Node::Import(ref i) => { if level != AstLevel::Top { - return Err( - ctx.generate_error("import blocks only allowed at the top level", i) - ); + return Err(ctx.generate_error( + "import blocks only allowed at the top level", + i.span(), + )); } self.handle_ws(i.ws); } Node::Extends(ref e) => { if level != AstLevel::Top { - return Err( - ctx.generate_error("extend blocks only allowed at the top level", e) - ); + return Err(ctx.generate_error( + "extend blocks only allowed at the top level", + e.span(), + )); } // No whitespace handling: child template top-level is not used, // except for the blocks defined in it. @@ -746,26 +749,26 @@ impl<'a, 'h> Generator<'a, 'h> { ref args, } = **call; 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 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(|| { - 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(|| { ctx.generate_error( format_args!("macro {name:?} not found in scope {s:?}"), - call, + call.span(), ) })?; (def, mctx) } 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) }; @@ -790,7 +793,7 @@ impl<'a, 'h> Generator<'a, 'h> { if !def.args.iter().any(|(arg, _)| arg == arg_name) { return Err(ctx.generate_error( format_args!("no argument named `{arg_name}` in macro {name:?}"), - call, + call.span(), )); } 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 \ in call to macro {name:?}" ), - call, + call.span(), )); } arg_expr @@ -833,14 +836,14 @@ impl<'a, 'h> Generator<'a, 'h> { let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() }; return Err(ctx.generate_error( format_args!("`{name}` is passed more than once"), - call, + call.span(), )); } _ => { if let Some(default_value) = default_value { default_value } 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, filter.filters.name, &filter.filters.arguments, - filter, + filter.span(), )?; let filter_buf = match display_wrap { DisplayWrap::Wrapped => filter_buf.into_string(), @@ -961,7 +964,9 @@ impl<'a, 'h> Generator<'a, 'h> { ) -> Result { self.flush_ws(i.ws); 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 .input .config @@ -1006,11 +1011,11 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(size_hint) } - fn is_shadowing_variable( + fn is_shadowing_variable( &self, ctx: &Context<'_>, var: &Target<'a>, - l: &WithSpan<'_, T>, + l: Span<'_>, ) -> Result { match var { Target::Name(name) => { @@ -1078,7 +1083,7 @@ impl<'a, 'h> Generator<'a, 'h> { let mut expr_buf = Buffer::new(); 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 { // Need to flush the buffer if the variable is being shadowed, // 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 // 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`. - fn write_block( + fn write_block( &mut self, ctx: &Context<'a>, buf: &mut Buffer, name: Option<&'a str>, outer: Ws, - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if self.is_in_filter_block > 0 { 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 { name, 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::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?, Expr::Range(op, ref left, ref right) => { @@ -1507,13 +1512,13 @@ impl<'a, 'h> Generator<'a, 'h> { DisplayWrap::Unwrapped } - fn visit_filter( + fn visit_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { let filter = match name { "deref" => Self::_visit_deref_filter, @@ -1534,13 +1539,13 @@ impl<'a, 'h> Generator<'a, 'h> { filter(self, ctx, buf, name, args, node) } - fn _visit_custom_filter( + fn _visit_custom_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], - _node: &WithSpan<'_, T>, + _node: Span<'_>, ) -> Result { buf.write(format_args!("filters::{name}(")); self._visit_args(ctx, buf, args)?; @@ -1548,13 +1553,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_builtin_filter( + fn _visit_builtin_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], - _node: &WithSpan<'_, T>, + _node: Span<'_>, ) -> Result { buf.write(format_args!("rinja::filters::{name}(")); self._visit_args(ctx, buf, args)?; @@ -1562,13 +1567,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_urlencode( + fn _visit_urlencode( &mut self, ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if cfg!(not(feature = "urlencode")) { return Err(ctx.generate_error( @@ -1586,13 +1591,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_humansize( + fn _visit_humansize( &mut self, ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], - _node: &WithSpan<'_, T>, + _node: Span<'_>, ) -> Result { // All filters return numbers, and any default formatted number is HTML safe. buf.write(format_args!( @@ -1604,28 +1609,24 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_pluralize_filter( + fn _visit_pluralize_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { - const SINGULAR: &WithSpan<'static, Expr<'static>> = &WithSpan::new( - Expr::StrLit(StrLit { + const SINGULAR: &WithSpan<'static, Expr<'static>> = + &WithSpan::new_without_span(Expr::StrLit(StrLit { prefix: None, content: "", - }), - "", - ); - const PLURAL: &WithSpan<'static, Expr<'static>> = &WithSpan::new( - Expr::StrLit(StrLit { + })); + const PLURAL: &WithSpan<'static, Expr<'static>> = + &WithSpan::new_without_span(Expr::StrLit(StrLit { prefix: None, content: "s", - }), - "", - ); + })); let (count, sg, pl) = match args { [count] => (count, SINGULAR, PLURAL), @@ -1652,13 +1653,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Wrapped) } - fn _visit_linebreaks_filter( + fn _visit_linebreaks_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if args.len() != 1 { return Err(ctx.generate_error( @@ -1676,13 +1677,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_ref_filter( + fn _visit_ref_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { let arg = match args { [arg] => arg, @@ -1693,13 +1694,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_deref_filter( + fn _visit_deref_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { let arg = match args { [arg] => arg, @@ -1710,13 +1711,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_json_filter( + fn _visit_json_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if cfg!(not(feature = "serde_json")) { return Err(ctx.generate_error( @@ -1737,13 +1738,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Unwrapped) } - fn _visit_safe_filter( + fn _visit_safe_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if args.len() != 1 { 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) } - fn _visit_escape_filter( + fn _visit_escape_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if args.len() > 2 { 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!( "invalid escaper `b{content:?}`. Expected a string, found a {kind}" ), - &args[1], + args[1].span(), )); } Some(content) @@ -1815,13 +1816,13 @@ impl<'a, 'h> Generator<'a, 'h> { Ok(DisplayWrap::Wrapped) } - fn _visit_format_filter( + fn _visit_format_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if !args.is_empty() { 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)) } - fn _visit_fmt_filter( + fn _visit_fmt_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - node: &WithSpan<'_, T>, + node: Span<'_>, ) -> Result { if let [_, arg2] = args { 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). - fn _visit_join_filter( + fn _visit_join_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, _name: &str, args: &[WithSpan<'_, Expr<'_>>], - _node: &WithSpan<'_, T>, + _node: Span<'_>, ) -> Result { buf.write("rinja::filters::join((&"); for (i, arg) in args.iter().enumerate() { @@ -1986,7 +1987,7 @@ impl<'a, 'h> Generator<'a, 'h> { buf.write("_loop_item.last"); return Ok(DisplayWrap::Unwrapped); } 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 { [arg] => { if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) { - return Err( - ctx.generate_error("loop.cycle(…) cannot use an empty array", arg) - ); + return Err(ctx.generate_error( + "loop.cycle(…) cannot use an empty array", + arg.span(), + )); } buf.write( "\ @@ -2044,14 +2046,15 @@ impl<'a, 'h> Generator<'a, 'h> { ); } _ => { - return Err( - ctx.generate_error("loop.cycle(…) cannot use an empty array", left) - ); + return Err(ctx.generate_error( + "loop.cycle(…) cannot use an empty array", + left.span(), + )); } }, s => { 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 { "" }, call.args.len(), ), - call, + call.span(), )) } diff --git a/rinja_derive/src/heritage.rs b/rinja_derive/src/heritage.rs index 12e3c229c..0345b1b65 100644 --- a/rinja_derive/src/heritage.rs +++ b/rinja_derive/src/heritage.rs @@ -4,7 +4,7 @@ use std::path::Path; use std::sync::Arc; use parser::node::{BlockDef, Macro}; -use parser::{Node, Parsed, WithSpan}; +use parser::{Node, Parsed, Span}; use rustc_hash::FxBuildHasher; use crate::config::Config; @@ -80,29 +80,29 @@ impl Context<'_> { for n in nodes { match n { Node::Extends(e) => { - ensure_top(top, e, path, parsed, "extends")?; + ensure_top(top, e.span(), path, parsed, "extends")?; if extends.is_some() { return Err(CompileError::new( "multiple extend blocks found", - Some(FileInfo::of(e, path, parsed)), + Some(FileInfo::of(e.span(), path, parsed)), )); } extends = Some(config.find_template( e.path, Some(path), - Some(FileInfo::of(e, path, parsed)), + Some(FileInfo::of(e.span(), path, parsed)), )?); } Node::Macro(m) => { - ensure_top(top, m, path, parsed, "macro")?; + ensure_top(top, m.span(), path, parsed, "macro")?; macros.insert(m.name, &**m); } Node::Import(import) => { - ensure_top(top, import, path, parsed, "import")?; + ensure_top(top, import.span(), path, parsed, "import")?; let path = config.find_template( import.path, Some(path), - Some(FileInfo::of(import, path, parsed)), + Some(FileInfo::of(import.span(), path, parsed)), )?; imports.insert(import.scope, path); } @@ -141,11 +141,7 @@ impl Context<'_> { }) } - pub(crate) fn generate_error( - &self, - msg: impl fmt::Display, - node: &WithSpan<'_, T>, - ) -> CompileError { + pub(crate) fn generate_error(&self, msg: impl fmt::Display, node: Span<'_>) -> CompileError { CompileError::new( msg, self.path.map(|path| FileInfo::of(node, path, self.parsed)), @@ -153,9 +149,9 @@ impl Context<'_> { } } -fn ensure_top( +fn ensure_top( top: bool, - node: &WithSpan<'_, T>, + node: Span<'_>, path: &Path, parsed: &Parsed, kind: &str, diff --git a/rinja_derive/src/input.rs b/rinja_derive/src/input.rs index da7468a3b..b9f343ea8 100644 --- a/rinja_derive/src/input.rs +++ b/rinja_derive/src/input.rs @@ -185,9 +185,14 @@ impl TemplateInput<'_> { // Add a dummy entry to `map` in order to prevent adding `path` // multiple times to `check`. let new_path = e.key(); + let source = parsed.source(); let source = get_template_source( 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()))); e.insert(Arc::default()); @@ -200,7 +205,7 @@ impl TemplateInput<'_> { let extends = self.config.find_template( extends.path, Some(&path), - Some(FileInfo::of(extends, &path, &parsed)), + Some(FileInfo::of(extends.span(), &path, &parsed)), )?; let dependency_path = (path.clone(), extends.clone()); if path == extends { @@ -220,7 +225,7 @@ impl TemplateInput<'_> { let import = self.config.find_template( import.path, Some(&path), - Some(FileInfo::of(import, &path, &parsed)), + Some(FileInfo::of(import.span(), &path, &parsed)), )?; add_to_check(import)?; } @@ -231,7 +236,7 @@ impl TemplateInput<'_> { let include = self.config.find_template( include.path, Some(&path), - Some(FileInfo::of(include, &path, &parsed)), + Some(FileInfo::of(include.span(), &path, &parsed)), )?; add_to_check(include)?; } diff --git a/rinja_derive/src/lib.rs b/rinja_derive/src/lib.rs index 84352ddee..78f376ac5 100644 --- a/rinja_derive/src/lib.rs +++ b/rinja_derive/src/lib.rs @@ -23,7 +23,7 @@ use generator::template_to_string; use heritage::{Context, Heritage}; use input::{Print, TemplateArgs, TemplateInput}; use integration::Buffer; -use parser::{Parsed, WithSpan, strip_common}; +use parser::{Parsed, strip_common}; #[cfg(not(feature = "__standalone"))] use proc_macro::TokenStream as TokenStream12; #[cfg(feature = "__standalone")] @@ -294,11 +294,12 @@ impl<'a> FileInfo<'a> { } } - fn of(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 { path, - source: Some(parsed.source()), - node_source: Some(node.span()), + source: Some(source), + node_source: node.as_suffix_of(source), } } } diff --git a/rinja_parser/src/expr.rs b/rinja_parser/src/expr.rs index f77241d5c..e6c595fc1 100644 --- a/rinja_parser/src/expr.rs +++ b/rinja_parser/src/expr.rs @@ -10,9 +10,9 @@ use winnow::combinator::{ use winnow::error::{ErrorKind, ParserError as _}; use crate::{ - CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, StrLit, WithSpan, - char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0, skip_ws1, - str_lit, ws, + CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, Span, StrLit, + WithSpan, char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0, + skip_ws1, str_lit, ws, }; macro_rules! expr_prec_layer { @@ -217,14 +217,14 @@ impl<'a> Expr<'a> { allow_underscore: bool, ) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; - let start = i; + let start = Span::from(i); let range_right = move |i| (ws(alt(("..=", ".."))), opt(move |i| Self::or(i, level))).parse_next(i); 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) }), - (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( Self::Range(op, Some(Box::new(left)), right.map(Box::new)), start, diff --git a/rinja_parser/src/lib.rs b/rinja_parser/src/lib.rs index b39d7fabd..6f27b4935 100644 --- a/rinja_parser/src/lib.rs +++ b/rinja_parser/src/lib.rs @@ -114,11 +114,11 @@ impl<'a> Ast<'a> { Ok(("", nodes)) => Ok(Self { nodes }), Ok(_) | Err(winnow::error::ErrMode::Incomplete(_)) => unreachable!(), Err( - winnow::error::ErrMode::Backtrack(ErrorContext { input, message, .. }) - | winnow::error::ErrMode::Cut(ErrorContext { input, message, .. }), + winnow::error::ErrMode::Backtrack(ErrorContext { span, message, .. }) + | winnow::error::ErrMode::Cut(ErrorContext { span, message, .. }), ) => Err(ParseError { message, - offset: src.len() - input.len(), + offset: span.offset_from(src).unwrap_or_default(), file_path, }), } @@ -134,19 +134,76 @@ impl<'a> Ast<'a> { /// in the code generation. pub struct WithSpan<'a, 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 { + 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> { - pub const fn new(inner: T, span: &'a str) -> Self { - Self { inner, span } + #[inline] + pub fn new(inner: T, span: impl Into>) -> Self { + Self { + inner, + span: span.into(), + } + } + + #[inline] + pub const fn new_without_span(inner: T) -> Self { + Self { + inner, + span: Span::empty(), + } } - pub fn span(&self) -> &'a str { + #[inline] + pub fn span(&self) -> Span<'a> { self.span } - pub fn deconstruct(self) -> (T, &'a str) { + #[inline] + pub fn deconstruct(self) -> (T, Span<'a>) { let Self { inner, span } = self; (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`). #[derive(Debug)] pub(crate) struct ErrorContext<'a> { - pub(crate) input: &'a str, + pub(crate) span: Span<'a>, pub(crate) message: Option>, } impl<'a> ErrorContext<'a> { - fn unclosed(kind: &str, tag: &str, i: &'a str) -> Self { - Self::new(format!("unclosed {kind}, missing {tag:?}"), i) + fn unclosed(kind: &str, tag: &str, span: impl Into>) -> Self { + Self::new(format!("unclosed {kind}, missing {tag:?}"), span) } - fn new(message: impl Into>, input: &'a str) -> Self { + fn new(message: impl Into>, span: impl Into>) -> Self { Self { - input, + span: span.into(), message: Some(message.into()), } } @@ -249,7 +306,7 @@ impl<'a> ErrorContext<'a> { impl<'a> winnow::error::ParserError<&'a str> for ErrorContext<'a> { fn from_error_kind(input: &'a str, _code: ErrorKind) -> Self { Self { - input, + span: input.into(), 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> { fn from_external_error(input: &'a str, _kind: ErrorKind, e: E) -> Self { Self { - input, + span: input.into(), message: Some(Cow::Owned(e.to_string())), } } @@ -1213,3 +1270,11 @@ mod test { assert!(str_lit.parse_next(r#"d"hello""#).is_err()); } } + +#[test] +fn assert_span_size() { + assert_eq!( + std::mem::size_of::>(), + std::mem::size_of::<*const ()>() + ); +} diff --git a/rinja_parser/src/node.rs b/rinja_parser/src/node.rs index d797e38df..f7f56e933 100644 --- a/rinja_parser/src/node.rs +++ b/rinja_parser/src/node.rs @@ -9,8 +9,8 @@ use winnow::token::{any, tag}; use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3}; use crate::{ - ErrorContext, Expr, Filter, ParseResult, State, Target, WithSpan, filter, identifier, keyword, - skip_till, skip_ws0, str_lit_without_prefix, ws, + ErrorContext, Expr, Filter, ParseResult, Span, State, Target, WithSpan, filter, identifier, + keyword, skip_till, skip_ws0, str_lit_without_prefix, ws, }; #[derive(Debug, PartialEq)] @@ -43,7 +43,9 @@ impl<'a> Node<'a> { &err { 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); @@ -184,7 +186,7 @@ impl<'a> Node<'a> { } #[must_use] - pub fn span(&self) -> &str { + pub fn span(&self) -> Span<'a> { match self { Self::Lit(span) => span.span, Self::Comment(span) => span.span, @@ -218,7 +220,9 @@ fn cut_node<'a, O>( &result { 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 diff --git a/rinja_parser/src/tests.rs b/rinja_parser/src/tests.rs index dd437517e..b3017e198 100644 --- a/rinja_parser/src/tests.rs +++ b/rinja_parser/src/tests.rs @@ -1,9 +1,14 @@ 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 WithSpan<'static, T> { 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, crate::ParseError> = Ast::from_str( + include_str!("../tests/fuzzed_span_is_not_substring_of_source.bin"), + None, + &Syntax::default(), + ); +} diff --git a/rinja_parser/tests/fuzzed_span_is_not_substring_of_source.bin b/rinja_parser/tests/fuzzed_span_is_not_substring_of_source.bin new file mode 100644 index 000000000..5a20066a0 Binary files /dev/null and b/rinja_parser/tests/fuzzed_span_is_not_substring_of_source.bin differ