Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
02e3f1a
Make Path an enum with a variant for just a single Ident (the common …
joshtriplett Oct 3, 2026
205f029
Track path sizes in input-stats
joshtriplett Oct 3, 2026
dd03341
Avoid storing a span for Path values where we can reconstruct it from…
joshtriplett Oct 4, 2026
cd21804
rustc_ast_lowering: Cache computed path spans
joshtriplett Oct 4, 2026
cb76f45
Inline `segments_span`
joshtriplett Oct 4, 2026
c0b3c9e
rustc_ast_lowering: Cache spans in restriction lowering
joshtriplett Oct 4, 2026
ab3bb3f
rustc_ast_lowering: Defer calling `.span()` for an error path until a…
joshtriplett Oct 4, 2026
230042e
rustc_resolve: Don't recompute `use_tree.span()` in a loop
joshtriplett Oct 4, 2026
b3e1209
`UseTree::span()`: Add a fast path for `UseTreeKind::Simple(None)`
joshtriplett Oct 4, 2026
30a9c82
`report_path_args`: Defer error span computation until actually erroring
joshtriplett Oct 4, 2026
f16bcbc
`check_stability_and_deprecation`: Defer error span computation until…
joshtriplett Oct 4, 2026
bec0098
`MetaItem::from_tokens`: Avoid redundant span calculations
joshtriplett Oct 4, 2026
ec9c4a6
Inline `Segment::from`
joshtriplett Oct 5, 2026
5a90626
Add fast paths to `Segment::from_path`
joshtriplett Oct 5, 2026
b66ab4e
Remove redundant clones of attribute paths
joshtriplett Oct 5, 2026
0e50314
Remove redundant collections of attribute paths
joshtriplett Oct 5, 2026
2e0dc19
rustc_ast: Optimize `segments_span` and inline `.span()` helpers
joshtriplett Oct 5, 2026
2bc0ff8
Use a `Path::Ident` for macro placeholder paths (which are never read)
joshtriplett Oct 5, 2026
cd09dcb
Optimize `MacCall::span` to avoid combining spans repeatedly
joshtriplett Oct 5, 2026
83758aa
parse_path_inner: Add a fast path for common cases to avoid reconstru…
joshtriplett Oct 5, 2026
7351815
lower_qpath: Only call `p.num_segments()` once
joshtriplett Oct 5, 2026
b9e395e
lower_qpath: Refactor to move loop computing `segments` earlier
joshtriplett Oct 5, 2026
a2bd244
lower_qpath: Compute the span when we encounter the last prefix segment
joshtriplett Oct 5, 2026
fde2661
lower_qpath: Reuse the segment iterator rather than recreating it
joshtriplett Oct 5, 2026
2c9b53f
Fix clippy for changes to Path
joshtriplett Oct 5, 2026
93bc654
Fix rustfmt for changes to Path
joshtriplett Oct 5, 2026
b12956f
Clean up `rustc_hir_pretty::print_attr_item` using `PathSegment::from…
joshtriplett Oct 5, 2026
e0c81d4
rustc_ast: `Path::is_empty`: Add explanations for NoSpan and Ident
joshtriplett Oct 6, 2026
3ef66bd
rustc_ast: Update `Path` documentation, discuss non-canonical forms
joshtriplett Oct 6, 2026
410f4f5
rustc_ast: Add an explanation for why Path's span may differ from its…
joshtriplett Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3599,6 +3599,7 @@ name = "rustc_ast"
version = "0.0.0"
dependencies = [
"bitflags",
"either",
"memchr",
"rustc-literal-escaper",
"rustc_ast_ir",
Expand Down Expand Up @@ -3758,6 +3759,7 @@ dependencies = [
name = "rustc_builtin_macros"
version = "0.0.0"
dependencies = [
"itertools",
"rustc_ast",
"rustc_ast_pretty",
"rustc_attr_ir",
Expand Down Expand Up @@ -4589,6 +4591,7 @@ name = "rustc_parse"
version = "0.0.0"
dependencies = [
"bitflags",
"itertools",
"rustc-literal-escaper",
"rustc_ast",
"rustc_ast_pretty",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2024"
[dependencies]
# tidy-alphabetical-start
bitflags = "2.4.1"
either = "1.15.0"
memchr = "2.7.6"
rustc-literal-escaper = "0.0.8"
rustc_ast_ir = { path = "../rustc_ast_ir" }
Expand Down
251 changes: 213 additions & 38 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
//! - [`Attribute`]: Metadata associated with item.
//! - [`UnOp`], [`BinOp`], and [`BinOpKind`]: Unary and binary operators.

// ignore-tidy-file-filelength
Comment thread
nnethercote marked this conversation as resolved.

use std::borrow::{Borrow, Cow};
use std::{cmp, fmt};
use std::{cmp, fmt, iter};

pub use GenericArgs::*;
pub use UnsafeSource::*;
use either::Either;
pub use rustc_ast_ir::{FloatTy, IntTy, Movability, Mutability, Pinnedness, UintTy};
use rustc_data_structures::packed::Pu128;
use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher};
Expand Down Expand Up @@ -85,72 +88,222 @@ impl fmt::Display for Lifetime {

/// A "Path" is essentially Rust's notion of a name.
///
/// It's represented as a sequence of identifiers,
/// along with a bunch of supporting information.
/// We separate the common case of a single identifier (e.g. `x`) from the general case of a
/// sequence of identifiers that might also have generics attached (e.g. `std::cmp::PartialEq`,
/// `Vec::<T>::new`).
Comment thread
joshtriplett marked this conversation as resolved.
///
/// E.g., `std::cmp::PartialEq`.
/// Canonical form is desirable but not mandatory. Single idents may use General if their span
/// differs from the ident's span, or occasionally simply because they didn't get canonicalized.
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub struct Path {
pub span: Span,
/// The segments in the path: the things separated by `::`.
/// Global paths begin with `kw::PathRoot`.
pub segments: ThinVec<PathSegment>,
pub enum Path {
/// The common case of a single identifier (e.g. `x`)
Ident { ident: Ident, id: NodeId },
/// The common case of a general path, which must have at least one segment, and the span can be
/// trivially reconstructed from the spans of the first and last segments.
NoSpan {
/// The segments in the path: the things separated by `::`. Global paths begin with
/// `kw::PathRoot`.
segments: ThinVec<PathSegment>,
},
/// A fully general path where the path's span differs (e.g. from recovery). Boxed to avoid
/// making `Path` larger.
General(Box<(ThinVec<PathSegment>, Span)>),
}

// Succeeds if the path has a single segment that is arg-free and matches the given symbol.
impl PartialEq<Symbol> for Path {
#[inline]
fn eq(&self, name: &Symbol) -> bool {
if let [segment] = self.segments.as_ref()
&& segment == name
{
true
} else {
false
}
self.as_single_argless_name() == Some(*name)
}
}

// Succeeds if the path has segments that are arg-free and match the given symbols.
impl PartialEq<&[Symbol]> for Path {
#[inline]
fn eq(&self, names: &&[Symbol]) -> bool {
self.segments.iter().eq(*names)
match self {
Path::Ident { ident, .. } => {
let [name] = names else {
return false;
};
ident.name == *name
}
Path::NoSpan { segments } | Path::General((segments, _)) => segments.iter().eq(*names),
}
}
}

impl StableHash for Path {
fn stable_hash<Hcx: StableHashCtxt>(&self, hcx: &mut Hcx, hasher: &mut StableHasher) {
self.segments.len().stable_hash(hcx, hasher);
for segment in &self.segments {
segment.ident.stable_hash(hcx, hasher);
}
self.num_segments().stable_hash(hcx, hasher);
self.iter_idents().for_each(|ident| ident.stable_hash(hcx, hasher));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing, but it seems weird/wrong that args isn't hashed!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nnethercote From what I can tell, this is because hashing for AST paths was introduced for paths used in attributes, which can never have generic args. It's a hazard that would be an issue if those hashes were used anywhere else, though. I'll send a separate PR fixing it.

}
}

impl Path {
/// Convert a span and an identifier to the corresponding
/// one-segment path.
pub fn from_ident(ident: Ident) -> Path {
Path { segments: thin_vec![PathSegment::from_ident(ident)], span: ident.span }
Path::Ident { ident, id: DUMMY_NODE_ID }
}

/// Convert a set of segments and a span to the corresponding `Path`.
#[inline]
pub fn from_segments(segments: ThinVec<PathSegment>, span: Span) -> Path {
if let [segment] = segments.as_slice()
&& segment.ident.span == span
&& segment.args.is_none()
{
Path::Ident { ident: segment.ident, id: segment.id }
} else if !segments.is_empty() && segments_span(&segments) == span {
Path::NoSpan { segments }
} else {
Path::General(Box::new((segments, span)))
}
}

#[inline]
pub fn span(&self) -> Span {
match self {
Path::Ident { ident, .. } => ident.span,
Path::NoSpan { segments } => segments_span(segments),
Path::General((_, span)) => *span,
}
}

pub fn path_root(span: Span) -> Self {
Path::from_ident(Ident::new(kw::PathRoot, span))
}

pub fn is_global(&self) -> bool {
self.segments.first().is_some_and(|segment| segment.ident.name == kw::PathRoot)
self.iter_idents().next().is_some_and(|i| i.name == kw::PathRoot)
}

/// Checks if this path is just a simple one-word `PATH` - i.e. the inverse of
/// [`Path::from_ident`]
#[inline]
pub fn is_single_argless_ident(&self) -> bool {
self.segments.len() == 1 && self.segments[0].args.is_none()
self.as_single_argless_ident().is_some()
}

/// The inverse of [`Path::from_ident`] - if this path is just a simple one-word `PATH`
#[inline]
pub fn as_single_argless_ident(&self) -> Option<Ident> {
self.is_single_argless_ident().then(|| self.segments[0].ident)
// This can't *exclusively* handle the `Path::Ident` case, because a single ident can use
// `Path::General` if the path span differs from the ident span, or `Path::NoSpan` if it
// isn't in canonical form.
match self {
Path::Ident { ident, .. } => Some(*ident),
Path::NoSpan { segments } | Path::General((segments, _)) => {
let [segment] = segments.as_ref() else {
return None;
};
if segment.args.is_some() {
return None;
}
Some(segment.ident)
}
}
}

#[inline]
pub fn as_single_argless_name(&self) -> Option<Symbol> {
self.as_single_argless_ident().map(|i| i.name)
}

#[inline]
pub fn is_empty(&self) -> bool {
Comment thread
joshtriplett marked this conversation as resolved.
match self {
Path::General((segments, _)) => segments.is_empty(),
Comment thread
joshtriplett marked this conversation as resolved.
// Path::NoSpan always has non-empty segments. Path::Ident is non-empty by definition.
_ => false,
}
}

#[inline]
pub fn num_segments(&self) -> usize {
match self {
Path::Ident { .. } => 1,
Path::NoSpan { segments } | Path::General((segments, _)) => segments.len(),
}
}

#[inline]
pub fn iter_idents(&self) -> impl DoubleEndedIterator<Item = &Ident> + ExactSizeIterator {
match self {
Path::Ident { ident, .. } => Either::Left(iter::once(ident)),
Path::NoSpan { segments } | Path::General((segments, _)) => {
Either::Right(segments.iter().map(|s| &s.ident))
}
}
}

#[inline]
pub fn iter_segments(
&self,
) -> impl DoubleEndedIterator<Item = PathSegmentRef<'_>> + ExactSizeIterator {
match self {
&Path::Ident { ref ident, id } => {
Either::Left(iter::once(PathSegmentRef { ident, id, args: None }))
}
Path::NoSpan { segments } | Path::General((segments, _)) => {
Either::Right(segments.iter().map(PathSegment::as_ref))
}
}
}

#[inline]
pub fn last_segment(&self) -> Option<PathSegmentRef<'_>> {
match self {
&Path::Ident { ref ident, id } => Some(PathSegmentRef { ident, id, args: None }),
Path::NoSpan { segments } | Path::General((segments, _)) => {
segments.last().map(PathSegment::as_ref)
}
}
}

#[inline]
pub fn last_ident(&self) -> Option<Ident> {
match self {
&Path::Ident { ident, .. } => Some(ident),
Path::NoSpan { segments } | Path::General((segments, _)) => {
segments.last().map(|s| s.ident)
}
}
}

pub fn force_general_mut(&mut self) -> (&mut ThinVec<PathSegment>, &mut Span) {
match self {
Path::General((segments, span)) => return (segments, span),
Path::NoSpan { segments } => {
let span = segments_span(segments);
*self = Path::General(Box::new((std::mem::take(segments), span)))
}
&mut Path::Ident { ident, id } => {
*self = Path::General(Box::new((
thin_vec![PathSegment { ident, id, args: None }],
ident.span,
)));
}
}
match self {
Path::General((segments, span)) => (segments, span),
_ => unreachable!(),
}
}
}

#[inline]
fn segments_span(segments: &[PathSegment]) -> Span {
let last = segments.last().unwrap();
let last_span = match last.args.as_deref() {
Some(last_args) => last_args.span(),
None => last.ident.span,
};
segments[0].ident.span.to(last_span)
}

/// Joins multiple symbols with "::" into a path, e.g. "a::b::c". If the first
/// segment is `kw::PathRoot` it will be printed as empty, e.g. "::b::c".
///
Expand Down Expand Up @@ -240,12 +393,34 @@ impl PathSegment {
PathSegment::from_ident(Ident::new(kw::PathRoot, span))
}

#[inline]
pub fn span(&self) -> Span {
match &self.args {
Some(args) => self.ident.span.to(args.span()),
None => self.ident.span,
}
}

pub fn as_ref(&self) -> PathSegmentRef<'_> {
PathSegmentRef { ident: &self.ident, id: self.id, args: self.args.as_deref() }
}
}

#[derive(Copy, Clone, Debug)]
pub struct PathSegmentRef<'a> {
pub ident: &'a Ident,
pub id: NodeId,
pub args: Option<&'a GenericArgs>,
}

impl PathSegmentRef<'_> {
#[inline]
pub fn span(&self) -> Span {
match self.args {
Some(args) => self.ident.span.to(args.span()),
None => self.ident.span,
}
}
}

/// The generic arguments and associated item constraints of a path segment.
Expand Down Expand Up @@ -2060,7 +2235,11 @@ pub struct MacCall {

impl MacCall {
pub fn span(&self) -> Span {
self.path.span.to(self.args.dspan.entire())
let first_span = match &self.path {
Path::NoSpan { segments } => segments[0].ident.span,
_ => self.path.span(),
};
first_span.to(self.args.dspan.entire())
}
}

Expand Down Expand Up @@ -2588,14 +2767,7 @@ impl TyKind {
}

pub fn is_simple_path(&self) -> Option<Symbol> {
if let TyKind::Path(None, Path { segments, .. }) = &self
&& let [segment] = &segments[..]
&& segment.args.is_none()
{
Some(segment.ident.name)
} else {
None
}
if let TyKind::Path(None, path) = &self { path.as_single_argless_name() } else { None }
}

/// Returns `true` if this type is considered a scalar primitive (e.g.,
Expand Down Expand Up @@ -3340,7 +3512,7 @@ impl UseTree {
match self.kind {
UseTreeKind::Simple(Some(rename)) => rename,
UseTreeKind::Simple(None) => {
self.prefix.segments.last().expect("empty prefix in a simple import").ident
self.prefix.last_ident().expect("empty prefix in a simple import")
}
_ => panic!("`UseTree::ident` can only be used on a simple import"),
}
Expand All @@ -3350,7 +3522,10 @@ impl UseTree {
/// closing `}` or nested spans, `*` of glob spans or the end of the
/// identifier of simple spans.
pub fn span(&self) -> Span {
self.prefix.span.to(self.hi_span())
match self.kind {
UseTreeKind::Simple(None) => self.prefix.span(),
_ => self.prefix.span().to(self.hi_span()),
}
}

/// Returns the trailing element's span. So for a nested
Expand All @@ -3360,7 +3535,7 @@ impl UseTree {
/// path if no rename is specified.
pub fn hi_span(&self) -> Span {
match self.kind {
UseTreeKind::Simple(None) => self.prefix.span,
UseTreeKind::Simple(None) => self.prefix.span(),
UseTreeKind::Simple(Some(name)) => name.span,
UseTreeKind::Nested { span, .. } => span,
UseTreeKind::Glob(span) => span,
Expand Down Expand Up @@ -3994,7 +4169,7 @@ pub struct Delegation {

impl Delegation {
pub fn last_segment_span(&self) -> Span {
self.path.segments.last().unwrap().ident.span
self.path.last_ident().unwrap().span
}
}

Expand Down
Loading
Loading