Skip to content

Commit 063ae8b

Browse files
committed
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.
1 parent 40fc8ba commit 063ae8b

File tree

1 file changed

+80
-1
lines changed

1 file changed

+80
-1
lines changed

src/libproc_macro/lib.rs

+80-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ mod diagnostic;
5656
pub use diagnostic::{Diagnostic, Level};
5757

5858
use std::{ascii, fmt, iter};
59+
use std::ops::{Bound, RangeBounds};
5960
use std::path::PathBuf;
6061
use rustc_data_structures::sync::Lrc;
6162
use std::str::FromStr;
@@ -64,7 +65,7 @@ use syntax::errors::DiagnosticBuilder;
6465
use syntax::parse::{self, token};
6566
use syntax::symbol::Symbol;
6667
use syntax::tokenstream::{self, DelimSpan};
67-
use syntax_pos::{Pos, FileName};
68+
use syntax_pos::{BytePos, Pos, FileName};
6869

6970
/// The main type provided by this crate, representing an abstract stream of
7071
/// tokens, or, more specifically, a sequence of token trees.
@@ -348,6 +349,84 @@ impl Span {
348349
}
349350
}
350351

352+
/// Number of bytes of source code covered by this span.
353+
///
354+
/// In other words, the number of bytes from the first byte associated with
355+
/// this span to the byte after the last byte associated with this span.
356+
///
357+
/// # Example
358+
///
359+
/// In the following line of source code, the span associated with the
360+
/// parenthesis token would have a `len` of 6 bytes.
361+
///
362+
/// ```text
363+
/// pub fn len(self) -> usize {
364+
/// ^^^^^^
365+
/// ```
366+
#[unstable(feature = "proc_macro_span", issue = "38356")]
367+
pub fn len(self) -> usize {
368+
self.0.hi().to_usize() - self.0.lo().to_usize()
369+
}
370+
371+
/// Produces a span for some range of the bytes represented by this span.
372+
///
373+
/// # Examples
374+
///
375+
/// In the following line of source code the span of the parenthesis token
376+
/// is shown. We could call `span.subspan(..1)` on this span to receive a
377+
/// span referring to just the first byte, the opening parenthesis.
378+
///
379+
/// ```text
380+
/// pub fn subspan<R: RangeBounds<usize>>(self, range: R) -> Span {
381+
/// ^^^^^^^^^^^^^^^^
382+
/// ```
383+
///
384+
/// In this line the indicated span would be obtained by
385+
/// `span.subspan(9..12)` on the span of the full string literal token.
386+
///
387+
/// ```text
388+
/// println!("{a} {b} {c}", a=1, b=2);
389+
/// ^^^
390+
/// ```
391+
///
392+
/// # Panics
393+
///
394+
/// Panics if the start of the resulting span would be greater than its end,
395+
/// or if the upper bound of the range is out of bounds considering the
396+
/// length of this span.
397+
///
398+
/// In the case of a half-open range like `lo..hi` this means we require `lo
399+
/// <= hi` and `hi <= span.len()`.
400+
#[unstable(feature = "proc_macro_span", issue = "38356")]
401+
pub fn subspan<R: RangeBounds<usize>>(self, range: R) -> Span {
402+
let len = self.len();
403+
let hi = match range.end_bound() {
404+
Bound::Included(&hi) => {
405+
assert!(hi < len);
406+
hi + 1
407+
}
408+
Bound::Excluded(&hi) => {
409+
assert!(hi <= len);
410+
hi
411+
}
412+
Bound::Unbounded => len,
413+
};
414+
let lo = match range.start_bound() {
415+
Bound::Included(&lo) => {
416+
assert!(lo <= hi);
417+
lo
418+
}
419+
Bound::Excluded(&lo) => {
420+
assert!(lo < hi);
421+
lo + 1
422+
}
423+
Bound::Unbounded => 0,
424+
};
425+
let lo = BytePos::from_usize(self.0.lo().to_usize() + lo);
426+
let hi = BytePos::from_usize(self.0.lo().to_usize() + hi);
427+
Span(self.0.with_lo(lo).with_hi(hi))
428+
}
429+
351430
/// Create a new span encompassing `self` and `other`.
352431
///
353432
/// Returns `None` if `self` and `other` are from different files.

0 commit comments

Comments
 (0)