From 063ae8bef2dacb0f01ffbac34503f52f21ced13f Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Mon, 3 Sep 2018 11:52:39 -0700 Subject: [PATCH] proc_macro::Span::len and subspan Before this addition, every delimited group like (...) [...] {...} has only a single Span that covers the full source location from opening delimiter to closing delimiter. This makes it impossible for a procedural macro to trigger an error pointing to just the opening or closing delimiter. The Rust compiler does not seem to have the same limitation: mod m { type T = } error: expected type, found `}` --> src/main.rs:3:1 | 3 | } | ^ On that same input, a procedural macro would be forced to trigger the error on the last token inside the block, on the entire block, or on the next token after the block, none of which is really what you want for an error like above. This commit adds span.len() and span.subspan(range) through which we can slice a span to point to some range of its original bytes. Relevant to Syn as we implement real error messages for when parsing fails in a procedural macro. --- src/libproc_macro/lib.rs | 81 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/libproc_macro/lib.rs b/src/libproc_macro/lib.rs index d4737052875b6..d21873db06faa 100644 --- a/src/libproc_macro/lib.rs +++ b/src/libproc_macro/lib.rs @@ -56,6 +56,7 @@ mod diagnostic; pub use diagnostic::{Diagnostic, Level}; use std::{ascii, fmt, iter}; +use std::ops::{Bound, RangeBounds}; use std::path::PathBuf; use rustc_data_structures::sync::Lrc; use std::str::FromStr; @@ -64,7 +65,7 @@ use syntax::errors::DiagnosticBuilder; use syntax::parse::{self, token}; use syntax::symbol::Symbol; use syntax::tokenstream::{self, DelimSpan}; -use syntax_pos::{Pos, FileName}; +use syntax_pos::{BytePos, Pos, FileName}; /// The main type provided by this crate, representing an abstract stream of /// tokens, or, more specifically, a sequence of token trees. @@ -348,6 +349,84 @@ impl Span { } } + /// Number of bytes of source code covered by this span. + /// + /// In other words, the number of bytes from the first byte associated with + /// this span to the byte after the last byte associated with this span. + /// + /// # Example + /// + /// In the following line of source code, the span associated with the + /// parenthesis token would have a `len` of 6 bytes. + /// + /// ```text + /// pub fn len(self) -> usize { + /// ^^^^^^ + /// ``` + #[unstable(feature = "proc_macro_span", issue = "38356")] + pub fn len(self) -> usize { + self.0.hi().to_usize() - self.0.lo().to_usize() + } + + /// Produces a span for some range of the bytes represented by this span. + /// + /// # Examples + /// + /// In the following line of source code the span of the parenthesis token + /// is shown. We could call `span.subspan(..1)` on this span to receive a + /// span referring to just the first byte, the opening parenthesis. + /// + /// ```text + /// pub fn subspan>(self, range: R) -> Span { + /// ^^^^^^^^^^^^^^^^ + /// ``` + /// + /// In this line the indicated span would be obtained by + /// `span.subspan(9..12)` on the span of the full string literal token. + /// + /// ```text + /// println!("{a} {b} {c}", a=1, b=2); + /// ^^^ + /// ``` + /// + /// # Panics + /// + /// Panics if the start of the resulting span would be greater than its end, + /// or if the upper bound of the range is out of bounds considering the + /// length of this span. + /// + /// In the case of a half-open range like `lo..hi` this means we require `lo + /// <= hi` and `hi <= span.len()`. + #[unstable(feature = "proc_macro_span", issue = "38356")] + pub fn subspan>(self, range: R) -> Span { + let len = self.len(); + let hi = match range.end_bound() { + Bound::Included(&hi) => { + assert!(hi < len); + hi + 1 + } + Bound::Excluded(&hi) => { + assert!(hi <= len); + hi + } + Bound::Unbounded => len, + }; + let lo = match range.start_bound() { + Bound::Included(&lo) => { + assert!(lo <= hi); + lo + } + Bound::Excluded(&lo) => { + assert!(lo < hi); + lo + 1 + } + Bound::Unbounded => 0, + }; + let lo = BytePos::from_usize(self.0.lo().to_usize() + lo); + let hi = BytePos::from_usize(self.0.lo().to_usize() + hi); + Span(self.0.with_lo(lo).with_hi(hi)) + } + /// Create a new span encompassing `self` and `other`. /// /// Returns `None` if `self` and `other` are from different files.