From 807e2451cfae67e36275878df286d58965a49a4e Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 17 Nov 2025 22:47:50 -0800 Subject: [PATCH 1/5] Make span_close() and span_open() more accurate after set_span() call. When `Group::set_span()` is called the spans for `Group::span_open()` and `Group::span_close() ` methods just become the whole span instead of the spans the delimiters. This ensure this have at least a more useable value after a call to `Group::set_span()`. --- library/proc_macro/src/lib.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 4efdfcad924b5..fbcd5f622a659 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -880,7 +880,20 @@ impl Group { /// tokens at the level of the `Group`. #[stable(feature = "proc_macro_lib2", since = "1.29.0")] pub fn set_span(&mut self, span: Span) { - self.0.span = bridge::DelimSpan::from_single(span.0); + // This is an invisible delimiter so they have no width, so just use start and end. + if matches!(self.0.delimiter, Delimiter::None) { + self.0.span = + bridge::DelimSpan { open: span.0.start(), close: span.0.end(), entire: span.0 }; + return; + } + + let rng = span.0.byte_range(); + let beg = (rng.end - rng.start).saturating_sub(1); + self.0.span = bridge::DelimSpan { + open: span.0.subspan(Bound::Included(0), Bound::Excluded(1)).unwrap_or(span.0.start()), + close: span.0.subspan(Bound::Included(beg), Bound::Unbounded).unwrap_or(span.0.end()), + entire: span.0, + }; } } From 82082f181241ca553d4de04a59880f96e2f9111c Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 17 Nov 2025 23:41:32 -0800 Subject: [PATCH 2/5] Forgot to add `use std::ops::Bound` on this fork. --- library/proc_macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index fbcd5f622a659..30f85856523f4 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -46,7 +46,7 @@ mod to_tokens; use core::ops::BitOr; use std::ffi::CStr; -use std::ops::{Range, RangeBounds}; +use std::ops::{Bound, Range, RangeBounds}; use std::path::PathBuf; use std::str::FromStr; use std::{error, fmt}; From 2c4d48781ee2196e51589d74cc0249aefbac02da Mon Sep 17 00:00:00 2001 From: Keith-Cancel Date: Tue, 18 Nov 2025 21:00:54 -0800 Subject: [PATCH 3/5] Subspan helper. --- compiler/rustc_span/src/lib.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 2e03ccb1aa1a3..72631f1050ee6 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -77,7 +77,7 @@ use std::cmp::{self, Ordering}; use std::fmt::Display; use std::hash::Hash; use std::io::{self, Read}; -use std::ops::{Add, Range, Sub}; +use std::ops::{Add, Bound, Range, RangeBounds, Sub}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; @@ -1176,6 +1176,37 @@ impl Span { pub fn normalize_to_macro_rules(self) -> Span { self.map_ctxt(|ctxt| ctxt.normalize_to_macro_rules()) } + + /// This function is similar to `Span::from_inner`, but it + /// will return `None` if the relative Range span exceeds + /// the bounds of span. + pub fn subspan>(self, subspan: R) -> Option + where + u32: TryFrom, + { + let lo = self.lo().0; + let hi = self.hi().0; + + let start = match subspan.start_bound() { + Bound::Included(s) => u32::try_from(*s).ok()?, + Bound::Excluded(s) => u32::try_from(*s).ok()?.checked_add(1)?, + Bound::Unbounded => 0, + }; + + let end = match subspan.end_bound() { + Bound::Included(e) => u32::try_from(*e).ok()?.checked_add(1)?, + Bound::Excluded(e) => u32::try_from(*e).ok()?, + Bound::Unbounded => hi - lo, + }; + + let new_lo = lo.checked_add(start)?; + let new_hi = lo.checked_add(end)?; + if new_lo > hi || new_hi > hi { + return None; + } + + Some(self.with_lo(BytePos(new_lo)).with_hi(BytePos(new_hi))) + } } impl Default for Span { From 9543c18543f37ec004ce07ae3254a11d4c1805b0 Mon Sep 17 00:00:00 2001 From: Keith-Cancel Date: Tue, 18 Nov 2025 21:02:40 -0800 Subject: [PATCH 4/5] Smarter from_single. --- compiler/rustc_ast/src/tokenstream.rs | 47 ++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 4111182c3b7dc..3e78c9bb12d59 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -978,7 +978,52 @@ pub struct DelimSpan { impl DelimSpan { pub fn from_single(sp: Span) -> Self { - DelimSpan { open: sp, close: sp } + let default_open = sp.shrink_to_lo(); + let default_close = sp.shrink_to_hi(); + + let Some(sm) = rustc_span::source_map::get_source_map() else { + // No source map available. + return Self { open: default_open, close: default_close }; + }; + + let (open, close) = sm + .span_to_source(sp, |src, start, end| { + let Some(src) = src.get(start..end) else { + return Ok((default_close, default_close)); + }; + + // Only check the first and last characters. + // If there is white space or other characters + // other than `( ... )`, `[ ... ]`, and `{ ... }`. + // I assume that is intentionally included in this + // span so we don't want to shrink the span by + // searching for the delimiters, and setting + // the open and close spans to some more interior + // position. + let mut chars = src.chars(); + // just using '_' as a place holder. + let first = chars.next().unwrap_or('_'); + let last = chars.last().unwrap_or('_'); + + // Thought maybe scan through if first is '(', '[', or '{' + // and see if the last matches up e.g. make sure it's not some + // extra mismatched delimiter. + + let len = (sp.hi() - sp.lo()).0 as usize; + let open = sp.subspan(0..first.len_utf8()).unwrap_or(default_open); + let close = sp.subspan((len - last.len_utf8())..).unwrap_or(default_close); + + Ok(match (first, last) { + ('(', ')') | ('{', '}') | ('[', ']') => (open, close), + ('(', _) | ('{', _) | ('[', _) => (open, default_close), + (_, ')') | (_, '}') | (_, ']') => (default_open, close), + (_, _) => (default_close, default_open), + }) + }) + .ok() + .unwrap_or((default_open, default_close)); + + DelimSpan { open, close } } pub fn from_pair(open: Span, close: Span) -> Self { From 670ff93391d48a7efc235a3e0ba8b4a14a899d2f Mon Sep 17 00:00:00 2001 From: Keith-Cancel Date: Tue, 18 Nov 2025 21:25:40 -0800 Subject: [PATCH 5/5] Handle underflow, probably an empty span. --- compiler/rustc_ast/src/tokenstream.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 3e78c9bb12d59..055b32de45cbe 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -1009,9 +1009,11 @@ impl DelimSpan { // and see if the last matches up e.g. make sure it's not some // extra mismatched delimiter. - let len = (sp.hi() - sp.lo()).0 as usize; let open = sp.subspan(0..first.len_utf8()).unwrap_or(default_open); - let close = sp.subspan((len - last.len_utf8())..).unwrap_or(default_close); + + let len = (sp.hi() - sp.lo()).0 as usize; + let pos = len.checked_sub(last.len_utf8()).unwrap_or(0); + let close = sp.subspan(pos..).unwrap_or(default_close); Ok(match (first, last) { ('(', ')') | ('{', '}') | ('[', ']') => (open, close),