From 02e3f1abd9151423f9d909d19e951d0d47b8d991 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Fri, 2 Oct 2026 23:29:24 -0700 Subject: [PATCH 01/30] Make Path an enum with a variant for just a single Ident (the common case) The majority of Path values in the compiler just need a single Ident, with no arguments, and no separate Span differing from the one in the Ident. For instance, local variables, item names, argument names, and so on. However, Path stored them as a span and a pointer to a vector (including len/capacity) of PathSegment structs, each containing an Ident, a NodeId, and an Option>. And, to make it even worse, these typically have allocated capacity for *four* PathSegment structs despite only having *one*. So, in total, a one-Ident Path took up: - 16 bytes for a Span and ThinVec pointer - 16 bytes for the ThinVec length and capacity - 4 PathSegments, each 24 bytes, for a total of 128 bytes, not counting any allocator overhead. On large crates like aws-sdk-ec2, this can be a substantial fraction of the memory usage of the AST. (And the HIR, but this commit doesn't try to deal with that yet.) Turn Path into an enum, with one variant `Path::Ident` for the single-Ident no-args case, and the other variant `Path::General` for any case with multiple segments, zero segments, any generic arguments, or a span that doesn't match the ident. For aws-sdk-ec2 (release-2026-10-02), 55% of all Path values (806434/1460321) can use Path::Ident. This commit *temporarily* increases the size of Path to 24 bytes; a subsequent commit will re-shrink it to 16 bytes. --- Cargo.lock | 3 + compiler/rustc_ast/Cargo.toml | 1 + compiler/rustc_ast/src/ast.rs | 218 ++++++++++++++---- compiler/rustc_ast/src/attr/mod.rs | 29 +-- compiler/rustc_ast/src/util/classify.rs | 4 +- .../src/delegation/generics.rs | 9 +- .../src/delegation/resolution.rs | 34 +-- compiler/rustc_ast_lowering/src/expr.rs | 4 +- compiler/rustc_ast_lowering/src/item.rs | 50 ++-- compiler/rustc_ast_lowering/src/lib.rs | 10 +- compiler/rustc_ast_lowering/src/path.rs | 50 ++-- .../rustc_ast_passes/src/ast_validation.rs | 8 +- compiler/rustc_ast_passes/src/feature_gate.rs | 2 +- compiler/rustc_ast_pretty/src/pprust/state.rs | 22 +- .../rustc_ast_pretty/src/pprust/state/item.rs | 10 +- compiler/rustc_attr_ir/src/attr.rs | 9 +- compiler/rustc_attr_ir/src/pretty_printing.rs | 2 +- .../src/attributes/diagnostic/mod.rs | 4 +- compiler/rustc_attr_parsing/src/interface.rs | 10 +- compiler/rustc_attr_parsing/src/parser.rs | 29 +-- compiler/rustc_builtin_macros/Cargo.toml | 1 + .../src/assert/context.rs | 6 +- compiler/rustc_builtin_macros/src/autodiff.rs | 6 +- compiler/rustc_builtin_macros/src/derive.rs | 2 +- .../src/deriving/coerce_pointee.rs | 2 +- .../src/deriving/generic/mod.rs | 17 +- compiler/rustc_builtin_macros/src/eii.rs | 11 +- compiler/rustc_builtin_macros/src/format.rs | 3 +- compiler/rustc_builtin_macros/src/lib.rs | 1 - .../src/proc_macro_harness.rs | 6 +- .../rustc_builtin_macros/src/test_harness.rs | 2 +- compiler/rustc_expand/src/build.rs | 21 +- compiler/rustc_expand/src/config.rs | 2 +- compiler/rustc_expand/src/expand.rs | 9 +- compiler/rustc_expand/src/mbe/transcribe.rs | 2 +- compiler/rustc_expand/src/placeholders.rs | 2 +- compiler/rustc_hir_pretty/src/lib.rs | 2 +- compiler/rustc_interface/src/interface.rs | 2 +- compiler/rustc_lint/src/internal.rs | 10 +- compiler/rustc_lint/src/levels.rs | 8 +- compiler/rustc_lint/src/unused.rs | 17 +- compiler/rustc_parse/Cargo.toml | 1 + .../rustc_parse/src/parser/diagnostics.rs | 101 ++++---- compiler/rustc_parse/src/parser/expr.rs | 58 +++-- compiler/rustc_parse/src/parser/function.rs | 6 +- compiler/rustc_parse/src/parser/generics.rs | 18 +- compiler/rustc_parse/src/parser/item.rs | 36 +-- compiler/rustc_parse/src/parser/mod.rs | 10 +- compiler/rustc_parse/src/parser/pat.rs | 6 +- compiler/rustc_parse/src/parser/path.rs | 65 +++--- compiler/rustc_parse/src/parser/stmt.rs | 11 +- compiler/rustc_parse/src/parser/ty.rs | 9 +- .../rustc_resolve/src/build_reduced_graph.rs | 22 +- compiler/rustc_resolve/src/check_unused.rs | 4 +- compiler/rustc_resolve/src/def_collector.rs | 2 +- .../rustc_resolve/src/diagnostics/impls.rs | 45 ++-- compiler/rustc_resolve/src/ident.rs | 2 +- compiler/rustc_resolve/src/late.rs | 46 ++-- .../rustc_resolve/src/late/diagnostics.rs | 133 +++++------ compiler/rustc_resolve/src/lib.rs | 14 +- compiler/rustc_resolve/src/macros.rs | 41 ++-- tests/ui-fulldeps/pprust-expr-roundtrip.rs | 3 +- tests/ui/stats/input-stats.stderr | 30 +-- 63 files changed, 707 insertions(+), 596 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39049cd7e2c6f..f8dd8db9529e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3599,6 +3599,7 @@ name = "rustc_ast" version = "0.0.0" dependencies = [ "bitflags", + "either", "memchr", "rustc-literal-escaper", "rustc_ast_ir", @@ -3758,6 +3759,7 @@ dependencies = [ name = "rustc_builtin_macros" version = "0.0.0" dependencies = [ + "itertools", "rustc_ast", "rustc_ast_pretty", "rustc_attr_ir", @@ -4589,6 +4591,7 @@ name = "rustc_parse" version = "0.0.0" dependencies = [ "bitflags", + "itertools", "rustc-literal-escaper", "rustc_ast", "rustc_ast_pretty", diff --git a/compiler/rustc_ast/Cargo.toml b/compiler/rustc_ast/Cargo.toml index fc9118f593ce5..0461d00214dfa 100644 --- a/compiler/rustc_ast/Cargo.toml +++ b/compiler/rustc_ast/Cargo.toml @@ -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" } diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 6a86e9af71826..6488a41371482 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -18,11 +18,14 @@ //! - [`Attribute`]: Metadata associated with item. //! - [`UnOp`], [`BinOp`], and [`BinOpKind`]: Unary and binary operators. +// ignore-tidy-file-filelength + 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}; @@ -85,29 +88,27 @@ 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. -/// -/// E.g., `std::cmp::PartialEq`. +/// We separate the common case 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::::new`). #[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, +pub enum Path { + /// The common case of a single identifier (e.g. `x`) + Ident { ident: Ident, id: NodeId }, + General { + /// The span of the whole path; might differ from the combined spans of the segments. + span: Span, + /// The segments in the path: the things separated by `::`. + /// Global paths begin with `kw::PathRoot`. + segments: ThinVec, + }, } // Succeeds if the path has a single segment that is arg-free and matches the given symbol. impl PartialEq 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) } } @@ -115,16 +116,22 @@ impl PartialEq for Path { 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::General { segments, .. } => segments.iter().eq(*names), + } } } impl StableHash for Path { fn stable_hash(&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)); } } @@ -132,22 +139,124 @@ 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 } + } + + #[inline] + pub fn span(&self) -> Span { + match self { + Path::Ident { ident, .. } => ident.span, + 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 { - 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. + match self { + Path::Ident { ident, .. } => Some(*ident), + 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 { + self.as_single_argless_ident().map(|i| i.name) + } + + #[inline] + pub fn is_empty(&self) -> bool { + let Path::General { segments, .. } = self else { + return false; + }; + segments.is_empty() + } + + #[inline] + pub fn num_segments(&self) -> usize { + let Path::General { segments, .. } = self else { + return 1; + }; + segments.len() + } + + #[inline] + pub fn iter_idents(&self) -> impl DoubleEndedIterator + ExactSizeIterator { + match self { + Path::Ident { ident, .. } => Either::Left(iter::once(ident)), + Path::General { segments, .. } => Either::Right(segments.iter().map(|s| &s.ident)), + } + } + + #[inline] + pub fn iter_segments( + &self, + ) -> impl DoubleEndedIterator> + ExactSizeIterator { + match self { + &Path::Ident { ref ident, id } => { + Either::Left(iter::once(PathSegmentRef { ident, id, args: None })) + } + Path::General { segments, .. } => { + Either::Right(segments.iter().map(PathSegment::as_ref)) + } + } + } + + #[inline] + pub fn last_segment(&self) -> Option> { + match self { + &Path::Ident { ref ident, id } => Some(PathSegmentRef { ident, id, args: None }), + Path::General { segments, .. } => segments.last().map(PathSegment::as_ref), + } + } + + #[inline] + pub fn last_ident(&self) -> Option { + match self { + &Path::Ident { ident, .. } => Some(ident), + Path::General { segments, .. } => segments.last().map(|s| s.ident), + } + } + + pub fn force_general_mut(&mut self) -> (&mut ThinVec, &mut Span) { + match self { + Path::General { segments, span } => (segments, span), + &mut Path::Ident { ident, id } => { + *self = Path::General { + segments: thin_vec![PathSegment { ident, id, args: None }], + span: ident.span, + }; + match self { + Path::Ident { .. } => unreachable!(), + Path::General { segments, span } => (segments, span), + } + } + } } } @@ -246,6 +355,26 @@ impl PathSegment { 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<'_> { + 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. @@ -2060,7 +2189,7 @@ pub struct MacCall { impl MacCall { pub fn span(&self) -> Span { - self.path.span.to(self.args.dspan.entire()) + self.path.span().to(self.args.dspan.entire()) } } @@ -2588,14 +2717,7 @@ impl TyKind { } pub fn is_simple_path(&self) -> Option { - 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., @@ -3340,7 +3462,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"), } @@ -3350,7 +3472,7 @@ 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()) + self.prefix.span().to(self.hi_span()) } /// Returns the trailing element's span. So for a nested @@ -3360,7 +3482,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, @@ -3994,7 +4116,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 } } @@ -4462,8 +4584,8 @@ mod size_asserts { static_assert_size!(AttrKind, 16); static_assert_size!(Attribute, 32); static_assert_size!(Block, 24); - static_assert_size!(Expr, 64); - static_assert_size!(ExprKind, 32); + static_assert_size!(Expr, 72); + static_assert_size!(ExprKind, 40); static_assert_size!(FieldDef, 80); static_assert_size!(Fn, 192); static_assert_size!(FnDecl, 24); @@ -4473,7 +4595,7 @@ mod size_asserts { static_assert_size!(ForeignItemKind, 16); static_assert_size!(GenericArg, 24); static_assert_size!(GenericArgs, 40); - static_assert_size!(GenericBound, 80); + static_assert_size!(GenericBound, 88); static_assert_size!(GenericParam, 80); static_assert_size!(Generics, 40); static_assert_size!(Impl, 80); @@ -4482,19 +4604,19 @@ mod size_asserts { static_assert_size!(Lifetime, 16); static_assert_size!(LitKind, 24); static_assert_size!(Local, 96); - static_assert_size!(MetaItem, 80); + static_assert_size!(MetaItem, 88); static_assert_size!(MetaItemKind, 40); static_assert_size!(MetaItemLit, 40); - static_assert_size!(NormalAttr, 80); + static_assert_size!(NormalAttr, 88); static_assert_size!(Param, 40); - static_assert_size!(Pat, 64); - static_assert_size!(PatKind, 48); - static_assert_size!(Path, 16); + static_assert_size!(Pat, 72); + static_assert_size!(PatKind, 56); + static_assert_size!(Path, 24); static_assert_size!(PathSegment, 24); static_assert_size!(QSelf, 24); static_assert_size!(Stmt, 32); static_assert_size!(StmtKind, 16); - static_assert_size!(TraitImplHeader, 64); + static_assert_size!(TraitImplHeader, 72); static_assert_size!(Ty, 56); static_assert_size!(TyKind, 40); // tidy-alphabetical-end diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 40a1b4bd32218..9790d36464d42 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -115,7 +115,7 @@ impl AttributeExt for Attribute { use SyntheticAttr::*; match &self.kind { AttrKind::Normal(normal) => { - Some(normal.item.path.segments.iter().map(|i| i.ident.name).collect()) + Some(normal.item.path.iter_idents().map(|i| i.name).collect()) } AttrKind::Synthetic(CfgTrace(_) | CfgAttrTrace(_)) => None, AttrKind::DocComment(_, _) => None, @@ -124,7 +124,7 @@ impl AttributeExt for Attribute { fn path_span(&self) -> Option { match &self.kind { - AttrKind::Normal(attr) => Some(attr.item.path.span), + AttrKind::Normal(attr) => Some(attr.item.path.span()), AttrKind::Synthetic(..) => unreachable!(), AttrKind::DocComment(_, _) => None, } @@ -132,16 +132,7 @@ impl AttributeExt for Attribute { fn path_matches(&self, name: &[Symbol]) -> bool { match &self.kind { - AttrKind::Normal(normal) => { - normal.item.path.segments.len() == name.len() - && normal - .item - .path - .segments - .iter() - .zip(name) - .all(|(s, n)| s.args.is_none() && s.ident.name == *n) - } + AttrKind::Normal(normal) => normal.item.path == name, AttrKind::Synthetic(..) | AttrKind::DocComment(..) => false, } } @@ -339,7 +330,7 @@ impl Attribute { impl AttrItem { pub fn name(&self) -> Option { - if let [seg] = &*self.path.segments { Some(seg.ident.name) } else { None } + self.path.as_single_argless_name() } pub fn meta_item_list(&self) -> Option> { @@ -411,11 +402,11 @@ impl AttrItem { impl MetaItem { /// For a single-segment meta item, returns its name; otherwise, returns `None`. pub fn ident(&self) -> Option { - if let [PathSegment { ident, .. }] = self.path.segments[..] { Some(ident) } else { None } + self.path.as_single_argless_ident() } pub fn name(&self) -> Option { - self.ident().map(|ident| ident.name) + self.path.as_single_argless_name() } pub fn has_name(&self, name: Symbol) -> bool { @@ -509,7 +500,7 @@ impl MetaItem { iter.next(); } let span = span.with_hi(segments.last().unwrap().ident.span.hi()); - Path { span, segments } + Path::General { span, segments } } Some(TokenTree::Delimited( _span, @@ -531,10 +522,10 @@ impl MetaItem { let kind = MetaItemKind::from_tokens(iter)?; let hi = match &kind { MetaItemKind::NameValue(lit) => lit.span.hi(), - MetaItemKind::List(..) => list_closing_paren_pos.unwrap_or(path.span.hi()), - _ => path.span.hi(), + MetaItemKind::List(..) => list_closing_paren_pos.unwrap_or(path.span().hi()), + _ => path.span().hi(), }; - let span = path.span.with_hi(hi); + let span = path.span().with_hi(hi); // FIXME: This parses `unsafe()` not as unsafe attribute syntax in `MetaItem`, // but as a parenthesized list. This (and likely `MetaItem`) should be changed in // such a way that builtin macros don't accept extraneous `unsafe()`. diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index 86a4073bd100f..e5c778fde2fe4 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -321,9 +321,7 @@ fn type_trailing_braced_mac_call(mut ty: &ast::Ty) -> Option<&ast::MacCall> { /// ^^^^^^^^^^^^^^^^^^^^^ /// ``` fn path_return_type(path: &ast::Path) -> Option<&ast::Ty> { - let last_segment = path.segments.last()?; - let args = last_segment.args.as_ref()?; - match &**args { + match path.last_segment()?.args? { ast::GenericArgs::Parenthesized(args) => match &args.output { ast::FnRetTy::Default(_) => None, ast::FnRetTy::Ty(ret) => Some(ret), diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index 836bf7df2bbd5..76635cbb7d5dc 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -309,7 +309,8 @@ impl<'hir> DelegationResolver<'_, 'hir> { let qself_is_none = delegation.qself.is_none(); - let parent_args = if let [.., parent_segment, _] = &delegation.path.segments[..] { + let parent_args = if let Some(parent_segment) = delegation.path.iter_segments().nth_back(1) + { let res = self.get_resolution_id(parent_segment.id)?; if !matches!(tcx.def_kind(res), DefKind::Mod) { assert_matches!( @@ -339,13 +340,13 @@ impl<'hir> DelegationResolver<'_, 'hir> { trait_impl: matches!(delegation_parent_kind, DefKind::Impl { of_trait: true }), sig_child_params: &tcx.generics_of(sig_id).own_params, child_args: self.get_user_args( - delegation.path.segments.last().expect("must be at least one segment"), + delegation.path.last_segment().expect("must be at least one segment"), ), }) } - fn get_user_args<'a>(&self, segment: &'a PathSegment) -> Option<&'a AngleBracketedArgs> { - let Some(args) = &segment.args else { return None }; + fn get_user_args<'a>(&self, segment: PathSegmentRef<'a>) -> Option<&'a AngleBracketedArgs> { + let Some(args) = segment.args else { return None }; let GenericArgs::AngleBracketed(args) = args else { self.tcx().dcx().span_delayed_bug( segment.span(), diff --git a/compiler/rustc_ast_lowering/src/delegation/resolution.rs b/compiler/rustc_ast_lowering/src/delegation/resolution.rs index 7678418c47e56..61c8c3f10eacb 100644 --- a/compiler/rustc_ast_lowering/src/delegation/resolution.rs +++ b/compiler/rustc_ast_lowering/src/delegation/resolution.rs @@ -68,7 +68,7 @@ pub(crate) fn resolve_type_relative_delegations( let res = r.partial_res_map.get(&delegation.id); let res = res.and_then(|res| res.base_res().opt_def_id()); - let ident = delegation.path.segments.last().map(|s| s.ident); + let ident = delegation.path.last_ident(); let span = delegation.last_segment_span(); @@ -240,23 +240,23 @@ impl<'tcx> DelegationResolver<'_, 'tcx> { let create_invalid_path_error = || tcx.dcx().span_delayed_bug(span, "invalid delegation path"); - match &delegation.path.segments[..] { - [] => return Err(create_invalid_path_error()), - [child] => { - let res = self.get_resolution_id(child.id)?; - if tcx.def_kind(res) != DefKind::Fn { - return Err(create_invalid_path_error()); - } + let mut segment_iter = delegation.path.iter_segments(); + let Some(child) = segment_iter.next_back() else { + return Err(create_invalid_path_error()); + }; + if let Some(parent) = segment_iter.next_back() { + let child_res = self.get_call_path_res(delegation, span)?; + let parent_res = self.get_resolution_id(parent.id)?; + + match (tcx.def_kind(child_res), tcx.def_kind(parent_res)) { + (DefKind::Fn, DefKind::Mod) => {} + (DefKind::AssocFn, DefKind::Trait | DefKind::Struct | DefKind::Enum) => {} + _ => return Err(create_invalid_path_error()), } - [.., parent, _] => { - let child_res = self.get_call_path_res(delegation, span)?; - let parent_res = self.get_resolution_id(parent.id)?; - - match (tcx.def_kind(child_res), tcx.def_kind(parent_res)) { - (DefKind::Fn, DefKind::Mod) => {} - (DefKind::AssocFn, DefKind::Trait | DefKind::Struct | DefKind::Enum) => {} - _ => return Err(create_invalid_path_error()), - } + } else { + let res = self.get_resolution_id(child.id)?; + if tcx.def_kind(res) != DefKind::Fn { + return Err(create_invalid_path_error()); } } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index f4bfec24e3b1b..6c8e9bb702e14 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -277,7 +277,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::MethodCall(MethodCall { seg, receiver, args, span }) => { let hir_seg = self.arena.alloc(self.lower_path_segment( e.span, - seg, + seg.as_ref(), ParamMode::Optional, GenericArgsMode::Err, ImplTraitContext::Disallowed(ImplTraitPosition::Path), @@ -656,7 +656,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } // Add generic args to the last element of the path. - let last_segment = path.segments.last_mut().unwrap(); + let last_segment = path.force_general_mut().0.last_mut().unwrap(); assert!(last_segment.args.is_none()); last_segment.args = Some(Box::new(GenericArgs::AngleBracketed(AngleBracketedArgs { span: DUMMY_SP, diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 6ec5903c5fb3b..7b480e7ca3a8a 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -534,7 +534,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_path_simple_eii(&mut self, id: NodeId, path: &Path) -> Option { let res = self.get_partial_res(id)?; let Some(did) = res.expect_full_res().opt_def_id() else { - self.dcx().span_delayed_bug(path.span, "should have errored in resolve"); + self.dcx().span_delayed_bug(path.span(), "should have errored in resolve"); return None; }; @@ -549,27 +549,28 @@ impl<'hir> LoweringContext<'_, 'hir> { vis_span: Span, attrs: &'hir [rustc_attr_ir::Attribute], ) -> hir::UseTree<'hir> { - let path = &tree.prefix; - let segments = path.segments.iter().cloned().collect(); - match tree.kind { UseTreeKind::Simple(rename) => { let mut ident = tree.ident(); - // First, apply the prefix to the path. - let mut path = Path { segments, span: path.span }; - // Correctly resolve `self` imports. - if path.segments.len() > 1 - && path.segments.last().unwrap().ident.name == kw::SelfLower - { - let _ = path.segments.pop(); - if rename.is_none() { - ident = path.segments.last().unwrap().ident; + let mut path = tree.prefix.clone(); + match &mut path { + Path::General { segments, .. } => { + if segments.len() > 1 + && segments.last().unwrap().ident.name == kw::SelfLower + { + let _ = segments.pop(); + if rename.is_none() { + ident = segments.last().unwrap().ident; + } + } } + // Can't end in `::self` + Path::Ident { .. } => {} } - let res = self.lower_import_res(id, path.span); + let res = self.lower_import_res(id, path.span()); let path = self.lower_use_path(res, &path, ParamMode::Explicit); let ident = self.lower_ident(ident); hir::UseTree { prefix: path, kind: hir::UseKind::Single(ident) } @@ -579,8 +580,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let res = self.lower_res(res); // Put the result in the appropriate namespace. let res = res.in_namespace(); - let path = Path { segments, span: path.span }; - let path = self.lower_use_path(res, &path, ParamMode::Explicit); + let path = self.lower_use_path(res, &tree.prefix, ParamMode::Explicit); hir::UseTree { prefix: path, kind: hir::UseKind::Glob } } UseTreeKind::Nested { items: ref trees, .. } => { @@ -588,7 +588,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let res = self.lower_res(res); // Put the result in the appropriate namespace. let res = res.in_namespace(); - let prefix = self.lower_use_path(res, &path, ParamMode::Explicit); + let prefix = self.lower_use_path(res, &tree.prefix, ParamMode::Explicit); let items = self.arena.alloc_from_iter(trees.iter().map(|use_tree| { let id = use_tree.id; let hir_id = self.lower_node_id(id); @@ -1688,17 +1688,17 @@ impl<'hir> LoweringContext<'_, 'hir> { // If the restriction path is not an ancestor of the item, // emit an error and recover by lowering the restriction to `Unrestricted`. self.dcx().emit_err(RestrictionAncestorOnly { - span: path.span, + span: path.span(), kind: resolving_kind, }); hir::RestrictionKind::Unrestricted } else { hir::RestrictionKind::Restricted(self.arena.alloc(hir::Path { res: did, - segments: self.arena.alloc_from_iter(path.segments.iter().map( + segments: self.arena.alloc_from_iter(path.iter_segments().map( |segment| { self.lower_path_segment( - path.span, + path.span(), segment, ParamMode::Explicit, GenericArgsMode::Err, @@ -1707,11 +1707,11 @@ impl<'hir> LoweringContext<'_, 'hir> { ) }, )), - span: self.lower_span(path.span), + span: self.lower_span(path.span()), })) } } else { - self.dcx().span_delayed_bug(path.span, "should have errored in resolve"); + self.dcx().span_delayed_bug(path.span(), "should have errored in resolve"); hir::RestrictionKind::Unrestricted } } @@ -1821,17 +1821,17 @@ impl<'hir> LoweringContext<'_, 'hir> { let define_opaque = define_opaque.iter().filter_map(|(id, path)| { let res = self.get_partial_res(*id); let Some(did) = res.and_then(|res| res.expect_full_res().opt_def_id()) else { - self.dcx().span_delayed_bug(path.span, "should have errored in resolve"); + self.dcx().span_delayed_bug(path.span(), "should have errored in resolve"); return None; }; let Some(did) = did.as_local() else { self.dcx().span_err( - path.span, + path.span(), "only opaque types defined in the local crate can be defined", ); return None; }; - Some((self.lower_span(path.span), did)) + Some((self.lower_span(path.span()), did)) }); let define_opaque = self.arena.alloc_from_iter(define_opaque); self.curr_owner.define_opaque = Some(define_opaque); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 4b6e897da4c87..22aa330af14c0 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -418,7 +418,7 @@ impl<'tcx> ResolverAstLowering<'tcx> { // Don't perform legacy const generics rewriting if the path already // has generic arguments. - if path.segments.last().unwrap().args.is_some() { + if path.last_segment().unwrap().args.is_some() { return None; } @@ -644,7 +644,7 @@ fn index_ast<'tcx>( // Lacking a better choice, we replace the contents with a macro call. // Unexpanded macros should never reach lowering, so this is not confusing. kind: dummy(Box::new(MacCall { - path: Path { span, segments: thin_vec![] }, + path: Path::General { span, segments: thin_vec![] }, args: Box::new(DelimArgs { dspan: DelimSpan::from_single(span), delim: Delimiter::Parenthesis, @@ -1866,15 +1866,13 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_lifetime(lt, LifetimeSource::PreciseCapturing, lt.ident.into()), ), PreciseCapturingArg::Arg(path, id) => { - let [segment] = path.segments.as_slice() else { - panic!(); - }; + let ident = path.as_single_argless_ident().unwrap(); let res = self.get_partial_res(*id).map_or(Res::Err, |partial_res| { partial_res.full_res().expect("no partial res expected for precise capture arg") }); hir::PreciseCapturingArg::Param(hir::PreciseCapturingNonLifetimeArg { hir_id: self.lower_node_id(*id), - ident: self.lower_ident(segment.ident), + ident: self.lower_ident(ident), res: self.lower_res(res), }) } diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 14f10e2b1c416..8b4266f16105c 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -55,7 +55,7 @@ impl<'hir> LoweringContext<'_, 'hir> { if let Some(async_def_id) = self.map_trait_to_async_trait(def_id) { res = Res::Def(DefKind::Trait, async_def_id); } else { - self.dcx().emit_err(AsyncBoundOnlyForFnTraits { span: p.span }); + self.dcx().emit_err(AsyncBoundOnlyForFnTraits { span: p.span() }); } } Res::Err => { @@ -65,7 +65,8 @@ impl<'hir> LoweringContext<'_, 'hir> { // This error isn't actually emitted AFAICT, but it's best to keep // it around in case the resolver doesn't always check the defkind // of an item or something. - self.dcx().emit_err(AsyncBoundNotOnTrait { span: p.span, descr: res.descr() }); + self.dcx() + .emit_err(AsyncBoundNotOnTrait { span: p.span(), descr: res.descr() }); } } } @@ -83,19 +84,19 @@ impl<'hir> LoweringContext<'_, 'hir> { // Only permit `impl Trait` in the final segment. E.g., we permit `Option`, // `option::Option::Xyz` and reject `option::Option::Xyz`. let itctx = |i| { - if i + 1 == p.segments.len() { + if i + 1 == p.num_segments() { itctx } else { ImplTraitContext::Disallowed(ImplTraitPosition::Path) } }; - let path_span_lo = p.span.shrink_to_lo(); - let proj_start = p.segments.len() - unresolved_segments; + let path_span_lo = p.span().shrink_to_lo(); + let proj_start = p.num_segments() - unresolved_segments; let path = self.arena.alloc(hir::Path { res, - segments: self.arena.alloc_from_iter(p.segments[..proj_start].iter().enumerate().map( - |(i, segment)| { + segments: self.arena.alloc_from_iter( + p.iter_segments().take(proj_start).enumerate().map(|(i, segment)| { let param_mode = match (qself_position, param_mode) { (Some(j), ParamMode::Optional) if i < j => { // This segment is part of the trait path in a @@ -132,18 +133,19 @@ impl<'hir> LoweringContext<'_, 'hir> { }; self.lower_path_segment( - p.span, + p.span(), segment, param_mode, generic_args_mode, itctx(i), bound_modifier_allowed_features.clone(), ) - }, - )), + }), + ), span: self.lower_span( - p.segments[..proj_start] - .last() + p.iter_segments() + .take(proj_start) + .next_back() .map_or(path_span_lo, |segment| path_span_lo.to(segment.span())), ), }); @@ -185,9 +187,9 @@ impl<'hir> LoweringContext<'_, 'hir> { // 2. `>::IntoIter` // 3. `<>::IntoIter>::Item` // * final path is `<<>::IntoIter>::Item>::clone` - for (i, segment) in p.segments.iter().enumerate().skip(proj_start) { + for (i, segment) in p.iter_segments().enumerate().skip(proj_start) { // If this is a type-dependent `T::method(..)`. - let generic_args_mode = if i + 1 == p.segments.len() + let generic_args_mode = if i + 1 == p.num_segments() && matches!(allow_return_type_notation, AllowReturnTypeNotation::Yes) { GenericArgsMode::ReturnTypeNotation @@ -196,7 +198,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; let hir_segment = self.arena.alloc(self.lower_path_segment( - p.span, + p.span(), segment, param_mode, generic_args_mode, @@ -206,7 +208,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let qpath = hir::QPath::TypeRelative(ty, hir_segment); // It's finished, return the extension of the right node type. - if i == p.segments.len() - 1 { + if i == p.num_segments() - 1 { return qpath; } @@ -218,11 +220,11 @@ impl<'hir> LoweringContext<'_, 'hir> { // We should've returned in the for loop above. self.dcx().span_bug( - p.span, + p.span(), format!( "lower_qpath: no final extension segment in {}..{}", proj_start, - p.segments.len() + p.num_segments() ), ); } @@ -236,9 +238,9 @@ impl<'hir> LoweringContext<'_, 'hir> { assert!(!res.is_empty()); self.arena.alloc(hir::UsePath { res, - segments: self.arena.alloc_from_iter(p.segments.iter().map(|segment| { + segments: self.arena.alloc_from_iter(p.iter_segments().map(|segment| { self.lower_path_segment( - p.span, + p.span(), segment, param_mode, GenericArgsMode::Err, @@ -246,14 +248,14 @@ impl<'hir> LoweringContext<'_, 'hir> { None, ) })), - span: self.lower_span(p.span), + span: self.lower_span(p.span()), }) } pub(crate) fn lower_path_segment( &mut self, path_span: Span, - segment: &PathSegment, + segment: PathSegmentRef<'_>, param_mode: ParamMode, generic_args_mode: GenericArgsMode, itctx: ImplTraitContext, @@ -263,7 +265,7 @@ impl<'hir> LoweringContext<'_, 'hir> { bound_modifier_allowed_features: Option>, ) -> hir::PathSegment<'hir> { debug!("path_span: {:?}, lower_path_segment(segment: {:?})", path_span, segment); - let (mut generic_args, infer_args) = if let Some(generic_args) = segment.args.as_deref() { + let (mut generic_args, infer_args) = if let Some(generic_args) = segment.args { match generic_args { GenericArgs::AngleBracketed(data) => { self.lower_angle_bracketed_parameter_data(data, param_mode, itctx) @@ -406,7 +408,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ); hir::PathSegment { - ident: self.lower_ident(segment.ident), + ident: self.lower_ident(*segment.ident), hir_id, res: self.lower_res(res), infer_args, diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 010afc6be5534..1338dd9b8d05d 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -1449,7 +1449,7 @@ impl Visitor<'_> for AstValidator<'_> { } if let (&Safety::Unsafe(span), &ImplPolarity::Negative(sp)) = (safety, polarity) { self.dcx().emit_err(diagnostics::UnsafeNegativeImpl { - span: sp.to(t.path.span), + span: sp.to(t.path.span()), negative: sp, r#unsafe: span, }); @@ -1465,7 +1465,7 @@ impl Visitor<'_> for AstValidator<'_> { Some(TraitOrImpl::TraitImpl { constness: *constness, polarity: *polarity, - trait_ref_span: t.path.span, + trait_ref_span: t.path.span(), }), |this| { walk_list!( @@ -1897,9 +1897,9 @@ impl Visitor<'_> for AstValidator<'_> { // Negative trait bounds are not allowed to have associated constraints if let BoundPolarity::Negative(_) = trait_ref.modifiers.polarity - && let Some(segment) = trait_ref.trait_ref.path.segments.last() + && let Some(segment) = trait_ref.trait_ref.path.last_segment() { - match segment.args.as_deref() { + match segment.args { Some(ast::GenericArgs::AngleBracketed(args)) => { for arg in &args.args { if let ast::AngleBracketedArg::Constraint(constraint) = arg { diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 45fa1863e715f..3a89116897315 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -163,7 +163,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { gate!( self, negative_impls, - span.to(of_trait.trait_ref.path.span), + span.to(of_trait.trait_ref.path.span()), "negative impls are experimental", "use marker types for now" ); diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index b76502814dbb7..b51675724a370 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -932,9 +932,9 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere } fn print_path(&mut self, path: &ast::Path, colons_before_params: bool, depth: usize) { - self.maybe_print_comment(path.span.lo()); + self.maybe_print_comment(path.span().lo()); - for (i, segment) in path.segments[..path.segments.len() - depth].iter().enumerate() { + for (i, segment) in path.iter_segments().take(path.num_segments() - depth).enumerate() { if i > 0 { self.word("::") } @@ -942,10 +942,10 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere } } - fn print_path_segment(&mut self, segment: &ast::PathSegment, colons_before_params: bool) { + fn print_path_segment(&mut self, segment: ast::PathSegmentRef<'_>, colons_before_params: bool) { if segment.ident.name != kw::PathRoot { - self.print_ident(segment.ident); - if let Some(args) = &segment.args { + self.print_ident(*segment.ident); + if let Some(args) = segment.args { self.print_generic_args(args, colons_before_params); } } @@ -1164,7 +1164,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere } fn attr_item_to_string(&self, ai: &ast::AttrItem) -> String { - Self::to_string(|s| s.print_attr_item(ai, ai.path.span)) + Self::to_string(|s| s.print_attr_item(ai, ai.path.span())) } fn tts_to_string(&self, tokens: &TokenStream) -> String { @@ -1883,14 +1883,14 @@ impl<'a> State<'a> { if qself.position > 0 { self.space(); self.word_space("as"); - let depth = path.segments.len() - qself.position; + let depth = path.num_segments() - qself.position; self.print_path(path, false, depth); } self.word(">"); - for item_segment in &path.segments[qself.position..] { + for item_segment in path.iter_segments().skip(qself.position) { self.word("::"); - self.print_ident(item_segment.ident); - if let Some(args) = &item_segment.args { + self.print_ident(*item_segment.ident); + if let Some(args) = item_segment.args { self.print_generic_args(args, colons_before_params) } } @@ -2411,7 +2411,7 @@ impl<'a> State<'a> { } pub(crate) fn path_segment_to_string(&self, p: &ast::PathSegment) -> String { - Self::to_string(|s| s.print_path_segment(p, false)) + Self::to_string(|s| s.print_path_segment(p.as_ref(), false)) } pub(crate) fn meta_list_item_to_string(&self, li: &ast::MetaItemInner) -> String { diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 6ce34cecc7e97..7901fde839328 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -898,14 +898,14 @@ impl<'a> State<'a> { } } ast::UseTreeKind::Glob(_) => { - if !tree.prefix.segments.is_empty() { + if !tree.prefix.is_empty() { self.print_path(&tree.prefix, false, 0); self.word("::"); } self.word("*"); } ast::UseTreeKind::Nested { items, .. } => { - if !tree.prefix.segments.is_empty() { + if !tree.prefix.is_empty() { self.print_path(&tree.prefix, false, 0); self.word("::"); } @@ -915,9 +915,9 @@ impl<'a> State<'a> { && !item .inner .prefix - .segments - .first() - .is_some_and(|seg| seg.ident.name == rustc_span::symbol::kw::SelfLower) + .iter_idents() + .next() + .is_some_and(|i| i.name == rustc_span::symbol::kw::SelfLower) { self.print_use_tree(&item.inner); } else { diff --git a/compiler/rustc_attr_ir/src/attr.rs b/compiler/rustc_attr_ir/src/attr.rs index 6068c11590a23..f8972fe0c0d32 100644 --- a/compiler/rustc_attr_ir/src/attr.rs +++ b/compiler/rustc_attr_ir/src/attr.rs @@ -42,13 +42,8 @@ impl IntoDiagArg for AttrPath { impl AttrPath { pub fn from_ast(path: &ast::Path, lower_span: impl Copy + Fn(Span) -> Span) -> Self { AttrPath { - segments: path - .segments - .iter() - .map(|i| i.ident.name) - .collect::>() - .into_boxed_slice(), - span: lower_span(path.span), + segments: path.iter_idents().map(|i| i.name).collect(), + span: lower_span(path.span()), } } } diff --git a/compiler/rustc_attr_ir/src/pretty_printing.rs b/compiler/rustc_attr_ir/src/pretty_printing.rs index cd8a0c0e1e96f..29428eb9607c9 100644 --- a/compiler/rustc_attr_ir/src/pretty_printing.rs +++ b/compiler/rustc_attr_ir/src/pretty_printing.rs @@ -112,7 +112,7 @@ impl PrintAttribute for Path { } fn print_attribute(&self, p: &mut Printer) { - p.word(join_path_idents(self.segments.iter().map(|seg| seg.ident))); + p.word(join_path_idents(self.iter_idents())); } } diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 53114d0ca9d8a..4df7f56147804 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use rustc_ast::PathSegment; +use rustc_ast::PathSegmentRef; use rustc_attr_ir::diagnostic::{ Directive, Filter, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue, Piece, Predicate, @@ -38,7 +38,7 @@ pub(crate) mod opaque; impl<'sess> crate::AttributeParser<'sess> { pub(crate) fn unknown_diagnostic_attr( &self, - segment: &PathSegment, + segment: PathSegmentRef<'_>, mut emit_lint: impl FnMut(LintId, MultiSpan, EmitAttribute), ) { const DIAGNOSTIC_ATTRIBUTES: [( diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index df89ffe57b9a0..303df4e4e07a5 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -185,7 +185,7 @@ impl<'sess> AttributeParser<'sess> { expected_safety: AttributeSafety, ) -> Option { let attr_item = attr.get_normal_item(); - let parts = attr_item.path.segments.iter().map(|seg| seg.ident.name).collect::>(); + let parts = attr_item.path.iter_idents().map(|i| i.name).collect::>(); let path = AttrPath::from_ast(&attr_item.path, identity); let args = ArgParser::from_attr_args( @@ -364,8 +364,7 @@ impl<'sess> AttributeParser<'sess> { ast::AttrKind::Normal(n) => { attr_paths.push(PathParser(&n.item.path)); let attr_path = AttrPath::from_ast(&n.item.path, lower_span); - let parts = - n.item.path.segments.iter().map(|seg| seg.ident.name).collect::>(); + let parts = n.item.path.iter_idents().map(|i| i.name).collect::>(); let inner_span = lower_span(n.item.span); if let Some(accept) = ATTRIBUTE_PARSERS.accepters.get(parts.as_slice()) { @@ -451,7 +450,10 @@ impl<'sess> AttributeParser<'sess> { cx.shared.cx.check_args_used(attr, &args) } } else if let [sym::diagnostic, _unknown, ..] = &*parts { - self.unknown_diagnostic_attr(&n.item.path.segments[1], &mut emit_lint); + self.unknown_diagnostic_attr( + n.item.path.iter_segments().nth(1).unwrap(), + &mut emit_lint, + ); } else { let attr = AttrItem { path: attr_path.clone(), diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index c85b1f8fee967..6014c5b8ff9a4 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -15,9 +15,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use rustc_ast::token::{self, Delimiter, MetaVarKind}; use rustc_ast::tokenstream::TokenStream; -use rustc_ast::{ - AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, PathSegment, StmtKind, UnOp, -}; +use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, StmtKind, UnOp}; use rustc_ast_pretty::pprust; use rustc_attr_ir::AttrPath; use rustc_errors::{Applicability, Diag, PResult}; @@ -42,30 +40,27 @@ pub type RefPathParser<'p> = PathParser<&'p Path>; impl> PathParser

{ pub fn get_attribute_path(&self) -> AttrPath { - AttrPath { - segments: self.segments().map(|s| s.name).collect::>().into_boxed_slice(), - span: self.span(), - } + AttrPath { segments: self.segments().map(|s| s.name).collect(), span: self.span() } } - pub fn segments(&self) -> impl Iterator { - self.0.borrow().segments.iter().map(|seg| &seg.ident) + pub fn segments(&self) -> impl DoubleEndedIterator + ExactSizeIterator { + self.0.borrow().iter_idents() } pub fn span(&self) -> Span { - self.0.borrow().span + self.0.borrow().span() } pub fn len(&self) -> usize { - self.0.borrow().segments.len() + self.0.borrow().num_segments() } pub fn segments_is(&self, segments: &[Symbol]) -> bool { - self.segments().map(|segment| &segment.name).eq(segments) + *self.0.borrow() == segments } pub fn word(&self) -> Option { - (self.len() == 1).then(|| **self.segments().next().as_ref().unwrap()) + self.0.borrow().as_single_argless_ident() } pub fn word_sym(&self) -> Option { @@ -329,7 +324,7 @@ pub struct MetaItemParser { impl MetaItemParser { /// For a single-segment meta item, returns its name; otherwise, returns `None`. pub fn ident(&self) -> Option { - if let [PathSegment { ident, .. }] = self.path.0.segments[..] { Some(ident) } else { None } + self.path.0.as_single_argless_ident() } pub fn span(&self) -> Span { @@ -455,7 +450,7 @@ fn expr_to_lit<'sess>( // Suggest adding quotation marks to turn an identifier into a string literal if let ExprKind::Path(None, ref path) = expr.kind - && let [_] = path.segments.as_slice() + && path.is_single_argless_ident() { err.multipart_suggestion( "you might have meant to write a string literal", @@ -657,8 +652,8 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { negative_sign: expr.span.until(val.span), }); } else if let StmtKind::Expr(expr) = &stmt.kind - && let ExprKind::Path(None, Path { segments, .. }) = &expr.kind - && segments.len() == 1 + && let ExprKind::Path(None, path) = &expr.kind + && path.num_segments() == 1 { while let token::Ident(..) | token::Literal(_) | token::Dot = self.parser.token.kind diff --git a/compiler/rustc_builtin_macros/Cargo.toml b/compiler/rustc_builtin_macros/Cargo.toml index f53bc5b33c899..71e89f95ed3cb 100644 --- a/compiler/rustc_builtin_macros/Cargo.toml +++ b/compiler/rustc_builtin_macros/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" doctest = false [dependencies] +itertools = "0.15.0" # tidy-alphabetical-start rustc_ast = { path = "../rustc_ast" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 11c97da0db5ad..7139cfcb637ef 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use rustc_ast::token::{self, Delimiter, IdentKind}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::{ @@ -249,9 +250,8 @@ impl<'cx, 'a> Context<'cx, 'a> { ExprKind::Move(local_expr, _) => { self.manage_cond_expr(local_expr); } - ExprKind::Path(_, Path { segments, .. }) if let [path_segment] = &segments[..] => { - let path_ident = path_segment.ident; - self.manage_initial_capture(expr, path_ident); + ExprKind::Path(_, path) if let Some(&ident) = path.iter_idents().exactly_one().ok() => { + self.manage_initial_capture(expr, ident); } ExprKind::Paren(local_expr) => { self.manage_cond_expr(local_expr); diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 21f424836e476..b4dc7544af842 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -54,9 +54,7 @@ mod llvm_enzyme { } } - let segments = &x.meta_item().unwrap().path.segments; - assert!(segments.len() == 1); - segments[0].ident + x.ident().unwrap() } fn name(x: &MetaItemInner) -> String { @@ -372,7 +370,7 @@ mod llvm_enzyme { (ast::AttrKind::Normal(a), ast::AttrKind::Normal(b)) => { let a = &a.item.path; let b = &b.item.path; - a.segments.iter().eq_by(&b.segments, |a, b| a.ident == b.ident) + a.iter_idents().eq(b.iter_idents()) } _ => false, } diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index cf9d0aef7582b..9fff156e503d5 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -139,7 +139,7 @@ fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) { } fn report_path_args(sess: &Session, meta: &ast::MetaItem) { - let span = meta.span.with_lo(meta.path.span.hi()); + let span = meta.span.with_lo(meta.path.span().hi()); match meta.kind { MetaItemKind::Word => {} diff --git a/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs b/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs index 7d0db12a450d6..22827581057a4 100644 --- a/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs +++ b/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs @@ -304,7 +304,7 @@ fn contains_maybe_sized_bound(bounds: &[GenericBound]) -> bool { fn is_sized_marker(path: &ast::Path) -> bool { const CORE_UNSIZE: [Symbol; 3] = [sym::core, sym::marker, sym::Sized]; const STD_UNSIZE: [Symbol; 3] = [sym::std, sym::marker, sym::Sized]; - let segments = || path.segments.iter().map(|segment| segment.ident.name); + let segments = || path.iter_idents().map(|i| i.name); if path.is_global() { segments().skip(1).eq(CORE_UNSIZE) || segments().skip(1).eq(STD_UNSIZE) } else { diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 8b15920511aca..ce7e627fb34c6 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -178,6 +178,7 @@ use std::ops::Not; use std::vec; pub(crate) use Substructure::*; +use itertools::Itertools; pub(crate) use rustc_ast as ast; use rustc_ast::token::{IdentKind, LitKind, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenTree}; @@ -402,8 +403,8 @@ fn find_type_parameters( } if let ast::TyKind::Path(_, path) = &ty.kind - && let Some(segment) = path.segments.first() - && self.ty_param_names.contains(&segment.ident.name) + && let Some(ident) = path.iter_idents().next() + && self.ty_param_names.contains(&ident.name) { self.type_params.push(TypeParameter { bound_generic_params: self.bound_generic_params_stack.clone(), @@ -586,12 +587,16 @@ impl<'a> TraitDef<'a> { let bounds: ThinVec<_> = self .additional_bounds .iter() - .map(|p| cx.trait_bound(ast::Path { span, ..p.clone() }, self.is_const)) + .map(|p| { + let mut p = p.clone(); + *p.force_general_mut().1 = span; + cx.trait_bound(p, self.is_const) + }) .chain( // Add a bound for the current trait. self.skip_path_as_bound.not().then(|| { let mut trait_path = self.path.clone(); - trait_path.span = span; + *trait_path.force_general_mut().1 = span; cx.trait_bound(trait_path, self.is_const) }), ) @@ -657,8 +662,8 @@ impl<'a> TraitDef<'a> { for field_ty_param in field_ty_params { // if we have already handled this type, skip it if let ast::TyKind::Path(_, p) = &field_ty_param.ty.kind - && let [sole_segment] = &*p.segments - && ty_param_names.contains(&sole_segment.ident.name) + && let Ok(sole_ident) = p.iter_idents().exactly_one() + && ty_param_names.contains(&sole_ident.name) { continue; } diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs index ed37599c9e85a..f7223f3a26514 100644 --- a/compiler/rustc_builtin_macros/src/eii.rs +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -259,10 +259,9 @@ fn name_for_impl_macro( if meta_item.is_word() { Ok(item_ident) } else if let Some([first]) = meta_item.meta_item_list() - && let Some(m) = first.meta_item() - && m.path.segments.len() == 1 + && let Some(ident) = first.ident() { - Ok(m.path.segments[0].ident) + Ok(ident) } else { Err(ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument { span: meta_item.span, @@ -607,9 +606,9 @@ pub(crate) fn eii_shared_macro( false } else if let Some([first]) = meta_item.meta_item_list() && let Some(m) = first.meta_item() - && m.path.segments.len() == 1 + && let Some(name) = m.name() { - m.path.segments[0].ident.name == kw::Default + name == kw::Default } else { ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument { span: meta_item.span, @@ -623,7 +622,7 @@ pub(crate) fn eii_shared_macro( } *eii_impl = Some(Box::new(EiiImpl { node_id: DUMMY_NODE_ID, - inner_span: meta_item.path.span, + inner_span: meta_item.path.span(), eii_macro_path: meta_item.path.clone(), impl_safety: meta_item.unsafety, span, diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index e52da1136dd64..ecc2c7b0d296b 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -211,8 +211,7 @@ fn make_format_args( && let [stmt] = block.stmts.as_slice() && let StmtKind::Expr(expr) = &stmt.kind && let ExprKind::Path(None, path) = &expr.kind - && path.segments.len() == 1 - && path.segments[0].args.is_none() + && path.is_single_argless_ident() { err.multipart_suggestion( "quote your inlined format argument to use as string literal", diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index c564e14776c91..ae72579592642 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -5,7 +5,6 @@ #![allow(internal_features)] #![feature(decl_macro)] #![feature(deref_patterns)] -#![feature(iter_order_by)] #![feature(proc_macro_internals)] #![feature(proc_macro_quote)] #![feature(try_blocks)] diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index ecf4f7e7fb1af..a8b4b3d34fd48 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -181,8 +181,8 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> { let prev_item = prev_attr.get_normal_item(); let item = attr.get_normal_item(); let path_str = pprust::path_to_string(&item.path); - let msg = if item.path.segments[0].ident.name - == prev_item.path.segments[0].ident.name + let msg = if item.path.iter_idents().next().unwrap().name + == prev_item.path.iter_idents().next().unwrap().name { format!( "only one `#[{path_str}]` attribute is allowed on any given function", @@ -232,7 +232,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> { if !self.is_proc_macro_crate { let path = &attr.get_normal_item().path; self.dcx.emit_err(diagnostics::AttributeOnlyUsableWithCrateType { - span: path.span, + span: path.span(), path: &pprust::path_to_string(&attr.get_normal_item().path), }); return; diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index d6167d30558a3..0416221b83486 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -304,7 +304,7 @@ fn add_main(cx: &mut TestCtxt<'_>, c: &mut ast::Crate) { ecx.path(sp, vec![test_ident, Ident::from_str_and_span(runner_name, sp)]) }); - test_runner.span = sp; + *test_runner.force_general_mut().1 = sp; let test_main_path_expr = ecx.expr_path(test_runner); let call_test_main = ecx.expr_call(sp, test_main_path_expr, thin_vec![mk_tests_slice(cx, sp)]); diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 2a4089b1213ab..cf79371a63152 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -24,7 +24,12 @@ impl<'a> ExtCtxt<'a> { self.path_all(span, false, strs, vec![]) } pub fn path_ident(&self, span: Span, id: Ident) -> ast::Path { - self.path(span, vec![id]) + let ident = id.with_span_pos(span); + if ident.span == span { + ast::Path::from_ident(ident) + } else { + ast::Path::General { segments: thin_vec![ast::PathSegment::from_ident(ident)], span } + } } pub fn path_global(&self, span: Span, strs: Vec) -> ast::Path { self.path_all(span, true, strs, vec![]) @@ -38,6 +43,12 @@ impl<'a> ExtCtxt<'a> { ) -> ast::Path { assert!(!idents.is_empty()); let add_root = global && !idents[0].is_path_segment_keyword(); + if !add_root + && args.is_empty() + && let &[ident] = idents.as_slice() + { + return self.path_ident(span, ident); + } let mut segments = ThinVec::with_capacity(idents.len() + add_root as usize); if add_root { segments.push(ast::PathSegment::path_root(span)); @@ -57,7 +68,7 @@ impl<'a> ExtCtxt<'a> { id: ast::DUMMY_NODE_ID, args, }); - ast::Path { span, segments } + ast::Path::General { span, segments } } pub fn macro_call( @@ -86,7 +97,7 @@ impl<'a> ExtCtxt<'a> { } pub fn ty_path(&self, path: ast::Path) -> Box { - self.ty(path.span, ast::TyKind::Path(None, path)) + self.ty(path.span(), ast::TyKind::Path(None, path)) } // Might need to take bounds as an argument in the future, if you ever want @@ -211,7 +222,7 @@ impl<'a> ExtCtxt<'a> { } pub fn trait_bound(&self, path: ast::Path, is_const: bool) -> ast::GenericBound { - ast::GenericBound::Trait(self.poly_trait_ref(path.span, path, is_const)) + ast::GenericBound::Trait(self.poly_trait_ref(path.span(), path, is_const)) } pub fn lifetime(&self, span: Span, ident: Ident) -> ast::Lifetime { @@ -306,7 +317,7 @@ impl<'a> ExtCtxt<'a> { } pub fn expr_path(&self, path: ast::Path) -> Box { - self.expr(path.span, ast::ExprKind::Path(None, path)) + self.expr(path.span(), ast::ExprKind::Path(None, path)) } pub fn expr_ident(&self, span: Span, id: Ident) -> Box { diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 3cab950963102..f1e3ef5c51dd6 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -341,7 +341,7 @@ impl<'a> StripUnconfigured<'a> { .to_attr_token_stream(), )); - let attr_item_path_span = attr_item.node.path.span; + let attr_item_path_span = attr_item.node.path.span(); let attr_tokens = Some(LazyAttrTokenStream::new_direct(AttrTokenStream::new(trees))); let attr = ast::attr::mk_attr_from_item( &self.sess.psess.attr_id_generator, diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 5a2442fbccf35..5024fda016f7a 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -422,7 +422,7 @@ impl Invocation { match &self.kind { InvocationKind::Bang { span, .. } => *span, InvocationKind::Attr { attr, .. } => attr.span, - InvocationKind::Derive { path, .. } => path.span, + InvocationKind::Derive { path, .. } => path.span(), InvocationKind::GlobDelegation { item, .. } => item.span, } } @@ -431,7 +431,7 @@ impl Invocation { match &mut self.kind { InvocationKind::Bang { span, .. } => span, InvocationKind::Attr { attr, .. } => &mut attr.span, - InvocationKind::Derive { path, .. } => &mut path.span, + InvocationKind::Derive { path, .. } => path.force_general_mut().1, InvocationKind::GlobDelegation { item, .. } => &mut item.span, } } @@ -2056,7 +2056,8 @@ fn build_single_delegations<'a, Node: InvocationCollectorNode>( suffixes.iter().map(move |&(ident, rename)| { let mut path = deleg.prefix.clone(); - path.segments.push(ast::PathSegment { ident, id: ast::DUMMY_NODE_ID, args: None }); + let (segments, _) = path.force_general_mut(); + segments.push(ast::PathSegment::from_ident(ident)); ast::Item { attrs: item.attrs.clone(), @@ -2291,7 +2292,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { crate::diagnostics::UnusedBuiltinAttribute { attr_name, macro_name: pprust::path_to_string(&call.path), - invoc_span: call.path.span, + invoc_span: call.path.span(), attr_span: attr.span, }, ); diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index 736a8d1de4c19..6d0074106569a 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -562,7 +562,7 @@ fn transcribe_pnr<'tx>( ) } ParseNtResult::Path(path) => { - mk_delimited(path.node.span, MetaVarKind::Path, TokenStream::from_ast(path)) + mk_delimited(path.node.span(), MetaVarKind::Path, TokenStream::from_ast(path)) } ParseNtResult::Vis(vis) => { mk_delimited(vis.node.span, MetaVarKind::Vis, TokenStream::from_ast(vis)) diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index ad6ae5481da39..e1293ed404806 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -16,7 +16,7 @@ pub(crate) fn placeholder( ) -> AstFragment { fn mac_placeholder() -> Box { Box::new(ast::MacCall { - path: ast::Path { span: DUMMY_SP, segments: ThinVec::new() }, + path: ast::Path::General { span: DUMMY_SP, segments: ThinVec::new() }, args: Box::new(ast::DelimArgs { dspan: ast::tokenstream::DelimSpan::dummy(), delim: Delimiter::Parenthesis, diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index d8c57bfbdb56b..e2e405c54ab5a 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -131,7 +131,7 @@ impl<'a> State<'a> { fn print_attr_item(&mut self, item: &AttrItem, span: Span) { let ib = self.ibox(0); - let path = ast::Path { + let path = ast::Path::General { span, segments: item .path diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 7a6a49f37278a..27a88c4c0fc7d 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -68,7 +68,7 @@ pub(crate) fn parse_cfg(sess: &Session, cfgs: Vec) -> Cfg { if parser.token == token::Eof && parser.dcx().has_errors().is_none() => { - if meta_item.path.segments.len() != 1 { + if meta_item.path.num_segments() != 1 { error!("argument key must be an identifier"); } match &meta_item.kind { diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index b32fa2515238a..cfde4b0a89d9e 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -513,10 +513,10 @@ declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]); impl EarlyLintPass for LintPassImpl { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { if let ast::ItemKind::Impl(ast::Impl { of_trait: Some(of_trait), .. }) = &item.kind - && let Some(last) = of_trait.trait_ref.path.segments.last() - && last.ident.name == sym::LintPass + && let Some(last_ident) = of_trait.trait_ref.path.last_ident() + && last_ident.name == sym::LintPass { - let expn_data = of_trait.trait_ref.path.span.ctxt().outer_expn_data(); + let expn_data = of_trait.trait_ref.path.span().ctxt().outer_expn_data(); let call_site = expn_data.call_site; if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass) && call_site.ctxt().outer_expn_data().kind @@ -524,7 +524,7 @@ impl EarlyLintPass for LintPassImpl { { cx.emit_span_lint( LINT_PASS_IMPL_WITHOUT_MACRO, - of_trait.trait_ref.path.span, + of_trait.trait_ref.path.span(), LintPassByHand, ); } @@ -690,7 +690,7 @@ declare_lint_pass!(BadUseOfFindAttr => [BAD_USE_OF_FIND_ATTR]); impl EarlyLintPass for BadUseOfFindAttr { fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &rustc_ast::Arm) { fn path_contains_attribute_kind(cx: &EarlyContext<'_>, path: &Path) { - for segment in &path.segments { + for segment in path.iter_segments() { if segment.ident.as_str() == "AttributeKind" { cx.emit_span_lint( BAD_USE_OF_FIND_ATTR, diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index f847d4c6ae904..49e1db306a443 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -759,8 +759,9 @@ where continue; } }; - let tool_ident = if meta_item.path.segments.len() > 1 { - Some(meta_item.path.segments.remove(0).ident) + let tool_ident = if meta_item.path.num_segments() > 1 { + let (segments, _) = meta_item.path.force_general_mut(); + Some(segments.remove(0).ident) } else { None }; @@ -771,8 +772,7 @@ where let (ids, name) = match lint_result { CheckLintNameResult::Ok(ids) => { - let name = - meta_item.path.segments.last().expect("empty lint name").ident.name; + let name = meta_item.path.last_ident().expect("empty lint name").name; (ids, name) } diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 361d1768087ea..ef40c085349d9 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1016,10 +1016,9 @@ impl EarlyLintPass for UnusedParens { let is_last = i == bounds.len() - 1; if let ast::GenericBound::Trait(poly_trait_ref) = &bounds[i] { - let fn_with_explicit_ret_ty = if let [.., segment] = - &*poly_trait_ref.trait_ref.path.segments - && let Some(args) = segment.args.as_ref() - && let ast::GenericArgs::Parenthesized(paren_args) = &**args + let fn_with_explicit_ret_ty = if let Some(segment) = + poly_trait_ref.trait_ref.path.last_segment() + && let Some(ast::GenericArgs::Parenthesized(paren_args)) = segment.args && let ast::FnRetTy::Ty(ret_ty) = &paren_args.output { self.in_no_bounds_pos.insert( @@ -1043,13 +1042,7 @@ impl EarlyLintPass for UnusedParens { let dyn2015_exception = cx.sess().psess.edition == Edition2015 && matches!(ty.kind, ast::TyKind::TraitObject(..)) && i == 0 - && poly_trait_ref - .trait_ref - .path - .segments - .first() - .map(|s| s.ident.name == kw::PathRoot) - .unwrap_or(false); + && poly_trait_ref.trait_ref.path.is_global(); if let ast::Parens::Yes = poly_trait_ref.parens && (is_last || !fn_with_explicit_ret_ty) @@ -1340,7 +1333,7 @@ impl UnusedImportBraces { // Trigger the lint if the nested item is a non-self single item let node_name = match tree.inner.kind { ast::UseTreeKind::Simple(rename) => { - let orig_ident = tree.inner.prefix.segments.last().unwrap().ident; + let orig_ident = tree.inner.prefix.last_ident().unwrap(); if orig_ident.name == kw::SelfLower { return; } diff --git a/compiler/rustc_parse/Cargo.toml b/compiler/rustc_parse/Cargo.toml index 2655e62391ef9..50a4270e751cc 100644 --- a/compiler/rustc_parse/Cargo.toml +++ b/compiler/rustc_parse/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start bitflags = "2.4.1" +itertools = "0.15.0" rustc-literal-escaper = "0.0.8" rustc_ast = { path = "../rustc_ast" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index fd4d012ebe2f9..601b2a95c981f 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -1,13 +1,13 @@ -use std::mem::take; use std::ops::{Deref, DerefMut}; use ast::token::IdentKind; +use itertools::Itertools; use rustc_ast::token::{self, Lit, LitKind, Token, TokenKind}; use rustc_ast::util::parser::{AssocOp, ExprPrecedence}; use rustc_ast::{ self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingMode, Block, BlockCheckMode, Expr, ExprKind, GenericArg, GenericArgs, Generics, Item, ItemKind, - Param, Pat, PatKind, Path, PathSegment, QSelf, Recovered, Ty, TyKind, + Param, Pat, PatKind, Path, PathSegment, PathSegmentRef, QSelf, Recovered, Ty, TyKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; @@ -84,7 +84,7 @@ impl RecoverQPath for Ty { Some(Box::new(self.clone())) } fn recovered(qself: Option>, path: ast::Path) -> Self { - Self { span: path.span, kind: TyKind::Path(qself, path), id: ast::DUMMY_NODE_ID } + Self { span: path.span(), kind: TyKind::Path(qself, path), id: ast::DUMMY_NODE_ID } } } @@ -94,7 +94,7 @@ impl RecoverQPath for Pat { self.to_ty() } fn recovered(qself: Option>, path: ast::Path) -> Self { - Self { span: path.span, kind: PatKind::Path(qself, path), id: ast::DUMMY_NODE_ID } + Self { span: path.span(), kind: PatKind::Path(qself, path), id: ast::DUMMY_NODE_ID } } } @@ -104,7 +104,7 @@ impl RecoverQPath for Expr { } fn recovered(qself: Option>, path: ast::Path) -> Self { Self { - span: path.span, + span: path.span(), kind: ExprKind::Path(qself, path), attrs: AttrVec::new(), id: ast::DUMMY_NODE_ID, @@ -764,7 +764,7 @@ impl<'a> Parser<'a> { let mut snapshot = self.create_snapshot_for_diagnostic(); if let [attr] = &expr.attrs[..] && let ast::AttrKind::Normal(attr_kind) = &attr.kind - && let [segment] = &attr_kind.item.path.segments[..] + && let Ok(segment) = attr_kind.item.path.iter_segments().exactly_one() && segment.ident.name == sym::cfg && let Some(args_span) = attr_kind.item.args.span() && let next_attr = match snapshot.parse_attribute(InnerAttrPolicy::Forbidden(None)) @@ -777,7 +777,7 @@ impl<'a> Parser<'a> { } && let ast::AttrKind::Normal(next_attr_kind) = next_attr.kind && let Some(next_attr_args_span) = next_attr_kind.item.args.span() - && let [next_segment] = &next_attr_kind.item.path.segments[..] + && let Ok(next_segment) = next_attr_kind.item.path.iter_segments().exactly_one() && next_segment.ident.name == sym::cfg { let next_expr = match snapshot.parse_expr() { @@ -877,7 +877,10 @@ impl<'a> Parser<'a> { // } debug!(?maybe_struct_name, ?self.token); let mut snapshot = self.create_snapshot_for_diagnostic(); - let path = Path { segments: ThinVec::new(), span: self.prev_token.span.shrink_to_lo() }; + let path = Path::General { + segments: ThinVec::new(), + span: self.prev_token.span.shrink_to_lo(), + }; let struct_expr = snapshot.parse_expr_struct(None, path, false); let block_tail = self.parse_block_tail(lo, s, AttemptLocalParseRecovery::No); return Some(match (struct_expr, block_tail) { @@ -1000,7 +1003,7 @@ impl<'a> Parser<'a> { /// up until one of the tokens in 'end' was encountered, and an error was emitted. pub(super) fn check_trailing_angle_brackets( &mut self, - segment: &PathSegment, + segment: PathSegmentRef<'_>, end: &[ExpTokenPair], ) -> Option { if !self.may_recover() { @@ -1031,8 +1034,7 @@ impl<'a> Parser<'a> { // have already been parsed): // // `x.foo::>>(3)` - let parsed_angle_bracket_args = - segment.args.as_ref().is_some_and(|args| args.is_angle_bracketed()); + let parsed_angle_bracket_args = segment.args.is_some_and(|args| args.is_angle_bracketed()); debug!( "check_trailing_angle_brackets: parsed_angle_bracket_args={:?}", @@ -1609,9 +1611,9 @@ impl<'a> Parser<'a> { ) -> PResult<'a, T> { self.expect(exp!(PathSep))?; - let mut path = ast::Path { segments: ThinVec::new(), span: DUMMY_SP }; - self.parse_path_segments(&mut path.segments, T::PATH_STYLE, None)?; - path.span = ty_span.to(self.prev_token.span); + let mut segments = ThinVec::new(); + self.parse_path_segments(&mut segments, T::PATH_STYLE, None)?; + let path = ast::Path::General { segments, span: ty_span.to(self.prev_token.span) }; self.dcx().emit_err(BadQPathStage2 { span: ty_span, @@ -2047,8 +2049,8 @@ impl<'a> Parser<'a> { // cc: https://github.com/rust-lang/rust/pull/146305 if let PatKind::Ref(_, _, _) = &inner_pat.kind && let PatKind::Path(_, path) = &pat.peel_refs().kind - && let [a, ..] = path.segments.as_slice() - && a.ident.name == kw::SelfLower => + && let Some(ident) = path.iter_idents().next() + && ident.name == kw::SelfLower => { let mut inner = inner_pat; let mut span_vec = vec![pat.span]; @@ -2547,33 +2549,39 @@ impl<'a> Parser<'a> { match &mut pat.kind { PatKind::Struct(qself @ None, path, ..) | PatKind::TupleStruct(qself @ None, path, _) - | PatKind::Path(qself @ None, path) => match &first_pat.kind { + | PatKind::Path(qself @ None, path) => match &mut first_pat.kind { PatKind::Ident(_, ident, _) => { - path.segments.insert(0, PathSegment::from_ident(*ident)); - path.span = new_span; + let (segments, span) = path.force_general_mut(); + segments.insert(0, PathSegment::from_ident(*ident)); + *span = new_span; show_sugg = true; first_pat = pat; } PatKind::Path(old_qself, old_path) => { - path.segments = old_path - .segments - .iter() - .cloned() - .chain(take(&mut path.segments)) - .collect(); - path.span = new_span; - *qself = old_qself.clone(); + let (segments, span) = path.force_general_mut(); + match old_path { + Path::Ident { ident, id } => segments.insert( + 0, + PathSegment { ident: *ident, id: *id, args: None }, + ), + Path::General { segments: old_segments, .. } => { + std::mem::swap(segments, old_segments); + segments.append(old_segments); + } + } + *span = new_span; + *qself = old_qself.take(); first_pat = pat; show_sugg = true; } _ => {} }, PatKind::Ident(BindingMode::NONE, ident, None) => { - match &first_pat.kind { + match &mut first_pat.kind { PatKind::Ident(_, old_ident, _) => { let path = PatKind::Path( None, - Path { + Path::General { span: new_span, segments: thin_vec![ PathSegment::from_ident(*old_ident), @@ -2584,14 +2592,11 @@ impl<'a> Parser<'a> { first_pat = self.mk_pat(new_span, path); show_sugg = true; } - PatKind::Path(old_qself, old_path) => { - let mut segments = old_path.segments.clone(); + PatKind::Path(_, old_path) => { + let (segments, span) = old_path.force_general_mut(); segments.push(PathSegment::from_ident(*ident)); - let path = PatKind::Path( - old_qself.clone(), - Path { span: new_span, segments }, - ); - first_pat = self.mk_pat(new_span, path); + *span = new_span; + first_pat.span = new_span; show_sugg = true; } _ => {} @@ -2739,19 +2744,17 @@ impl<'a> Parser<'a> { pub(crate) fn maybe_recover_bounds_doubled_colon(&mut self, ty: &Ty) -> PResult<'a, ()> { let TyKind::Path(qself, path) = &ty.kind else { return Ok(()) }; let qself_position = qself.as_ref().map(|qself| qself.position); - for (i, segments) in path.segments.windows(2).enumerate() { + for (i, (a, b)) in path.iter_segments().tuple_windows().enumerate() { if qself_position.is_some_and(|pos| i < pos) { continue; } - if let [a, b] = segments { - let (a_span, b_span) = (a.span(), b.span()); - let between_span = a_span.shrink_to_hi().to(b_span.shrink_to_lo()); - if self.span_to_snippet(between_span).as_deref() == Ok(":: ") { - return Err(self.dcx().create_err(DoubleColonInBound { - span: path.span.shrink_to_hi(), - between: between_span, - })); - } + let (a_span, b_span) = (a.span(), b.span()); + let between_span = a_span.shrink_to_hi().to(b_span.shrink_to_lo()); + if self.span_to_snippet(between_span).as_deref() == Ok(":: ") { + return Err(self.dcx().create_err(DoubleColonInBound { + span: path.span().shrink_to_hi(), + between: between_span, + })); } } Ok(()) @@ -2939,13 +2942,13 @@ impl<'a> Parser<'a> { let TyKind::Path(_, path) = ty.kind else { return origin_error; }; - let Some(GenericArgs::AngleBracketed(AngleBracketedArgs { span: _, ref args })) = - path.segments[0].args + let Some(GenericArgs::AngleBracketed(AngleBracketedArgs { span: _, args })) = + path.iter_segments().next().unwrap().args else { return origin_error; }; - let path_span = path.span; + let path_span = path.span(); let mut new_error = snapshot.dcx().create_err(FoundPathInGenerics { span: path_span, path: snapshot.span_to_snippet(path_span).unwrap(), diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 9e74a7b1ab38c..c578d733df621 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -5,7 +5,7 @@ use core::ops::{Bound, ControlFlow}; use ast::mut_visit::{self, MutVisitor}; use ast::token::IdentKind; -use ast::{ForLoopKind, MatchKind, Pat, Path, PathSegment, Recovered}; +use ast::{ForLoopKind, MatchKind, Pat, Path, Recovered}; use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, Token, TokenKind}; use rustc_ast::util::case::Case; use rustc_ast::util::classify; @@ -581,8 +581,10 @@ impl<'a> Parser<'a> { match self.parse_path(PathStyle::Expr) { Ok(path) => { let span_after_type = parser_snapshot_after_type.token.span; - let expr = - mk_expr(self, self.mk_ty(path.span, TyKind::Path(None, path.clone()))); + let expr = mk_expr( + self, + self.mk_ty(path.span(), TyKind::Path(None, path.clone())), + ); let args_span = self.look_ahead(1, |t| t.span).to(span_after_type); match self.token.kind { @@ -1052,18 +1054,11 @@ impl<'a> Parser<'a> { } break; } - ExprKind::Path(None, Path { ref segments, .. }) => { - match &segments[..] { - [PathSegment { ident, args: None, .. }] => { - trailing_dot = None; - fields.insert(start_idx, *ident) - } - _ => { - self.dcx() - .emit_err(crate::diagnostics::InvalidOffsetOf(current.span)); - break; - } - } + ExprKind::Path(None, ref path) + if let Some(ident) = path.as_single_argless_ident() => + { + trailing_dot = None; + fields.insert(start_idx, ident); break; } _ => { @@ -1267,7 +1262,7 @@ impl<'a> Parser<'a> { let fn_span_lo = self.token.span; let mut seg = self.parse_path_segment(PathStyle::Expr, None)?; - self.check_trailing_angle_brackets(&seg, &[exp!(OpenParen)]); + self.check_trailing_angle_brackets(seg.as_ref(), &[exp!(OpenParen)]); self.check_turbofish_missing_angle_brackets(&mut seg); if self.check(exp!(OpenParen)) { @@ -1567,20 +1562,20 @@ impl<'a> Parser<'a> { // MACRO INVOCATION expression if qself.is_some() { self.dcx() - .emit_err(crate::diagnostics::MacroInvocationWithQualifiedPath(path.span)); + .emit_err(crate::diagnostics::MacroInvocationWithQualifiedPath(path.span())); } - let lo = path.span; + let lo = path.span(); let mac = Box::new(MacCall { path, args: self.parse_delim_args()? }); (lo.to(self.prev_token.span), ExprKind::MacCall(mac)) } else if self.check(exp!(OpenBrace)) && let Some(expr) = self.maybe_parse_struct_expr(&qself, &path) { if qself.is_some() { - self.psess.gated_spans.gate(sym::more_qualified_paths, path.span); + self.psess.gated_spans.gate(sym::more_qualified_paths, path.span()); } return expr; } else { - (path.span, ExprKind::Path(qself, path)) + (path.span(), ExprKind::Path(qself, path)) }; let expr = self.mk_expr(span, kind); @@ -1835,8 +1830,7 @@ impl<'a> Parser<'a> { // Recover `break label aaaaa` if self.may_recover() && let ExprKind::Path(None, p) = &expr.kind - && let [segment] = &*p.segments - && let &ast::PathSegment { ident, args: None, .. } = segment + && let Some(ident) = p.as_single_argless_ident() && let Some(next) = self.parse_expr_opt()? { label = Some(self.recover_ident_into_label(ident)); @@ -3618,7 +3612,7 @@ impl<'a> Parser<'a> { self.dcx().emit_err(crate::diagnostics::StructLiteralNotAllowedHere { span: expr.span, sub: crate::diagnostics::StructLiteralNotAllowedHereSugg { - left: path.span.shrink_to_lo(), + left: path.span().shrink_to_lo(), right: expr.span.shrink_to_hi(), }, }); @@ -3743,9 +3737,9 @@ impl<'a> Parser<'a> { Ok(f) => Ok(f), Err(mut e) => { if pth == kw::Async { - async_block_err(&mut e, pth.span); + async_block_err(&mut e, pth.span()); } else { - e.span_label(pth.span, "while parsing this struct"); + e.span_label(pth.span(), "while parsing this struct"); } if let Some((ident, _)) = self.token.ident() @@ -3790,9 +3784,11 @@ impl<'a> Parser<'a> { // make sure the block is at the end by eating a `;`, // we shouldn't report such diagnostic for `let a = foo{return 43;}+bar;` // also skip the suggestion if the span cross macro boundaries - if might_be_stmt && snapshot.eat(exp!(Semi)) && pth.span.eq_ctxt(open_span) + if might_be_stmt + && snapshot.eat(exp!(Semi)) + && pth.span().eq_ctxt(open_span) { - let span = pth.span.between(open_span); + let span = pth.span().between(open_span); e.subdiagnostic(crate::diagnostics::MissingElseInLet { span }); self.restore_snapshot(snapshot); return Err(e); @@ -3841,9 +3837,9 @@ impl<'a> Parser<'a> { } Err(mut e) => { if pth == kw::Async { - async_block_err(&mut e, pth.span); + async_block_err(&mut e, pth.span()); } else { - e.span_label(pth.span, "while parsing this struct"); + e.span_label(pth.span(), "while parsing this struct"); if peek.is_some() { e.span_suggestion( self.prev_token.span.shrink_to_hi(), @@ -3881,7 +3877,7 @@ impl<'a> Parser<'a> { pth: ast::Path, recover: bool, ) -> PResult<'a, Box> { - let lo = pth.span; + let lo = pth.span(); let (fields, base, recovered_async) = self.parse_struct_fields(pth.clone(), recover, exp!(CloseBrace))?; let span = lo.to(self.token.span); @@ -4261,7 +4257,7 @@ impl MutVisitor for CondChecker<'_> { } } - let expr_span = lhs.span.to(path.span); + let expr_span = lhs.span.to(path.span()); if let Some(later_rhs) = find_let_some(rhs) && depth > 0 diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index d3aba0b98eb8b..54cb6aa4b7f35 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -793,13 +793,13 @@ impl<'a> Parser<'a> { if let Ok(t) = &ty { // Check for trailing angle brackets - if let TyKind::Path(_, Path { segments, .. }) = &t.kind - && let Some(segment) = segments.last() + if let TyKind::Path(_, path) = &t.kind + && let Some(segment) = path.last_segment() && let Some(guar) = this.check_trailing_angle_brackets(segment, &[exp!(CloseParen)]) { return Ok(( - dummy_arg(segment.ident, guar), + dummy_arg(*segment.ident, guar), Trailing::No, UsePreAttrPos::No, )); diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 5d2aa1bed5b6e..0a0a672b2dd90 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use rustc_ast::{ self as ast, AttrVec, DUMMY_NODE_ID, GenericBounds, GenericParam, GenericParamKind, TyKind, WhereClause, token, @@ -678,12 +679,13 @@ fn suggest_replacing_equality_pred_with_assoc_item_constraint( lhs_ty: ast::Ty, rhs_ty: ast::Ty, ) { - let TyKind::Path(qself, ast::Path { segments, .. }) = lhs_ty.kind else { return }; + let TyKind::Path(qself, path) = lhs_ty.kind else { return }; let mut parts = Vec::new(); let applicability = match qself { // We have something like `Ty::Item = Rhs`. - None if let [self_ty_seg, assoc_item_seg] = &segments[..] + None if let segments_iter = path.iter_segments() + && let Some((self_ty_seg, assoc_item_seg)) = segments_iter.collect_tuple() && self_ty_seg.ident.name != kw::PathRoot => { parts.push(( @@ -692,13 +694,17 @@ fn suggest_replacing_equality_pred_with_assoc_item_constraint( )); Applicability::HasPlaceholders } - Some(qself) if let [assoc_item_seg] = &segments[qself.position..] => { + Some(qself) + if let Ok(assoc_item_seg) = path.iter_segments().skip(qself.position).exactly_one() => + { parts.push((lhs_ty.span.until(qself.ty.span), String::new())); + let mut segments_iter = path.iter_segments().take(qself.position); // We have something like ` as self::Trait>::Item = Rhs`. - if let trait_segs @ [.., final_trait_seg] = &segments[..qself.position] { - parts.push((qself.ty.span.between(trait_segs[0].span()), ": ".into())); - let (span, snippet) = match &final_trait_seg.args { + if let Some(first_trait_seg) = segments_iter.next() { + let final_trait_seg = segments_iter.next_back().unwrap_or(first_trait_seg); + parts.push((qself.ty.span.between(first_trait_seg.span()), ": ".into())); + let (span, snippet) = match final_trait_seg.args { Some(args) => { let ast::GenericArgs::AngleBracketed(args) = args else { return }; let Some(args) = args.args.last() else { return }; diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 9fb16bc3fbf27..743ec4303a169 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -615,11 +615,11 @@ impl<'a> Parser<'a> { Err(mut err) => { // Maybe the user misspelled `macro_rules` (issue #91227) if self.token.is_ident() - && let [segment] = path.segments.as_slice() - && edit_distance("macro_rules", &segment.ident.to_string(), 2).is_some() + && let Some(ident) = path.as_single_argless_ident() + && edit_distance("macro_rules", &ident.to_string(), 2).is_some() { err.span_suggestion_verbose( - path.span, + path.span(), "perhaps you meant to define a macro", "macro_rules", Applicability::MachineApplicable, @@ -943,7 +943,7 @@ impl<'a> Parser<'a> { })) } else { let rename = rename(self)?; - let ident = rename.unwrap_or_else(|| path.segments.last().unwrap().ident); + let ident = rename.unwrap_or_else(|| path.last_ident().unwrap()); ItemKind::Delegation(Box::new(Delegation { id: DUMMY_NODE_ID, @@ -1341,16 +1341,16 @@ impl<'a> Parser<'a> { ) -> PResult<'a, UseTree> { let lo = self.token.span; - let mut prefix = ast::Path { segments: ThinVec::new(), span: lo.shrink_to_lo() }; + let mut prefix; let kind = if self.check(exp!(OpenBrace)) || self.check(exp!(Star)) || self.is_import_coupler() { // `use *;` or `use ::*;` or `use {...};` or `use ::{...};` let mod_sep_ctxt = self.token.span.ctxt(); - if self.eat_path_sep() { - prefix - .segments - .push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt))); - } + prefix = if self.eat_path_sep() { + ast::Path::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt)) + } else { + ast::Path::General { segments: ThinVec::new(), span: lo.shrink_to_lo() } + }; self.parse_use_tree_glob_or_nested(use_token_span, use_path)? } else { @@ -1358,7 +1358,8 @@ impl<'a> Parser<'a> { prefix = self.parse_path(PathStyle::Mod)?; if self.eat_path_sep() { - let use_path = UsePathList { elements: &prefix.segments, prev: use_path }; + let (segments, _) = prefix.force_general_mut(); + let use_path = UsePathList { elements: &*segments, prev: use_path }; self.parse_use_tree_glob_or_nested(use_token_span, Some(&use_path))? } else { // Recover from using a colon as path separator. @@ -1368,8 +1369,9 @@ impl<'a> Parser<'a> { }); // We parse the rest of the path and append it to the original prefix. - self.parse_path_segments(&mut prefix.segments, PathStyle::Mod, None)?; - prefix.span = lo.to(self.prev_token.span); + let (segments, span) = prefix.force_general_mut(); + self.parse_path_segments(segments, PathStyle::Mod, None)?; + *span = lo.to(self.prev_token.span); } UseTreeKind::Simple(self.parse_rename()?) @@ -2379,8 +2381,8 @@ impl<'a> Parser<'a> { format!("expected `,`, or `}}`, found {}", super::token_descr(&self.token)); // Try to recover extra trailing angle brackets - if let TyKind::Path(_, Path { segments, .. }) = &a_var.ty.kind - && let Some(last_segment) = segments.last() + if let TyKind::Path(_, path) = &a_var.ty.kind + && let Some(last_segment) = path.last_segment() { let guar = self.check_trailing_angle_brackets( last_segment, @@ -2872,11 +2874,11 @@ impl<'a> Parser<'a> { // Check if this looks like `macro_rules!(name) { ... }` // a common mistake when trying to define a macro. if let Some(path) = path - && path.segments.first().is_some_and(|seg| seg.ident.name == sym::macro_rules) + && path.iter_idents().next().is_some_and(|i| i.name == sym::macro_rules) && args.delim == Delimiter::Parenthesis { let replace = - if path.span.hi() + rustc_span::BytePos(1) < open.lo() { "" } else { " " }; + if path.span().hi() + rustc_span::BytePos(1) < open.lo() { "" } else { " " }; err.multipart_suggestion( "to define a macro, remove the parentheses around the macro name", vec![(open, replace.to_string()), (close, String::new())], diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 99d5a1fa6f257..16c6a7c232ba5 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -1535,7 +1535,7 @@ impl<'a> Parser<'a> { let path_str = pprust::path_to_string(&path); self.dcx() - .emit_err(IncorrectVisibilityRestriction { span: path.span, inner_str: path_str }); + .emit_err(IncorrectVisibilityRestriction { span: path.span(), inner_str: path_str }); Ok(()) } @@ -1611,13 +1611,15 @@ impl<'a> Parser<'a> { match restriction_kind { ParsingRestrictionKind::Impl => { self.dcx().emit_err(IncorrectImplRestriction { - span: path.span, + span: path.span(), inner_str: path_str, }); } ParsingRestrictionKind::Mut => { - self.dcx() - .emit_err(IncorrectMutRestriction { span: path.span, inner_str: path_str }); + self.dcx().emit_err(IncorrectMutRestriction { + span: path.span(), + inner_str: path_str, + }); } } Ok((RestrictionKind::Unrestricted, self.token.span.shrink_to_lo(), lo.to(end))) diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index f53cfc792bd69..ac8c0a3f2cc5b 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1556,11 +1556,11 @@ impl<'a> Parser<'a> { fn parse_pat_struct(&mut self, qself: Option>, path: Path) -> PResult<'a, PatKind> { if qself.is_some() { // Feature gate the use of qualified paths in patterns - self.psess.gated_spans.gate(sym::more_qualified_paths, path.span); + self.psess.gated_spans.gate(sym::more_qualified_paths, path.span()); } self.bump(); let (fields, etc) = self.parse_pat_fields().unwrap_or_else(|mut e| { - e.span_label(path.span, "while parsing the fields for this pattern"); + e.span_label(path.span(), "while parsing the fields for this pattern"); let guar = e.emit_err(); self.recover_stmt(); // When recovering, pretend we had `Foo { .. }`, to avoid cascading errors. @@ -1585,7 +1585,7 @@ impl<'a> Parser<'a> { ) })?; if qself.is_some() { - self.psess.gated_spans.gate(sym::more_qualified_paths, path.span); + self.psess.gated_spans.gate(sym::more_qualified_paths, path.span()); } Ok(PatKind::TupleStruct(qself, path, fields)) } diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 615d1bb221cc9..7a2101a8cba52 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -1,5 +1,6 @@ use std::mem; +use itertools::Itertools; use rustc_ast::token::{self, MetaVarKind, Token, TokenKind}; use rustc_ast::{ self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AssocItemConstraint, @@ -81,14 +82,15 @@ impl<'a> Parser<'a> { // if any (e.g., `U` in the `::*` examples // above). `path_span` has the span of that path, or an empty // span in the case of something like `::Bar`. - let (mut path, path_span); + let (mut path, segments, span); if self.eat_keyword(exp!(As)) { let path_lo = self.token.span; path = self.parse_path(PathStyle::Type)?; - path_span = path_lo.to(self.prev_token.span); + (segments, span) = path.force_general_mut(); + *span = path_lo.to(self.prev_token.span); } else { - path_span = self.token.span.to(self.token.span); - path = ast::Path { segments: ThinVec::new(), span: path_span }; + path = ast::Path::General { segments: ThinVec::new(), span: self.token.span }; + (segments, span) = path.force_general_mut(); } // See doc comment for `unmatched_angle_bracket_count`. @@ -103,12 +105,13 @@ impl<'a> Parser<'a> { self.expect(exp!(PathSep))?; } - let qself = Box::new(QSelf { ty, path_span, position: path.segments.len() }); + let qself = Box::new(QSelf { ty, path_span: *span, position: segments.len() }); if !is_import_coupler { - self.parse_path_segments(&mut path.segments, style, None)?; + self.parse_path_segments(segments, style, None)?; } - Ok((qself, Path { segments: path.segments, span: lo.to(self.prev_token.span) })) + *span = lo.to(self.prev_token.span); + Ok((qself, path)) } /// Recover from an invalid single colon, when the user likely meant a qualified path. @@ -163,7 +166,7 @@ impl<'a> Parser<'a> { style: PathStyle, ty_generics: Option<&Generics>, ) -> PResult<'a, Path> { - let reject_generics_if_mod_style = |parser: &Parser<'_>, path: Path| { + let reject_generics_if_mod_style = |parser: &Parser<'_>, mut path: Path| { // Ensure generic arguments don't end up in attribute paths, such as: // // macro_rules! m { @@ -172,25 +175,22 @@ impl<'a> Parser<'a> { // // m!(inline); //~ ERROR: unexpected generic arguments in path // - if style == PathStyle::Mod && path.segments.iter().any(|segment| segment.args.is_some()) + if style == PathStyle::Mod + && let Path::General { ref mut segments, .. } = path + && segments.iter().any(|s| s.args.is_some()) { - let span = path - .segments + let span = segments .iter() .filter_map(|segment| segment.args.as_ref()) .map(|arg| arg.span()) .collect::>(); parser.dcx().emit_err(diagnostics::GenericsInPath { span }); // Ignore these arguments to prevent unexpected behaviors. - let segments = path - .segments - .iter() - .map(|segment| PathSegment { ident: segment.ident, id: segment.id, args: None }) - .collect(); - Path { segments, ..path } - } else { - path + for segment in segments { + segment.args = None; + } } + path }; if let Some(path) = @@ -214,7 +214,15 @@ impl<'a> Parser<'a> { segments.push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt))); } self.parse_path_segments(&mut segments, style, ty_generics)?; - Ok(Path { segments, span: lo.to(self.prev_token.span) }) + let span = lo.to(self.prev_token.span); + if let [segment] = segments.as_slice() + && segment.args.is_none() + && segment.ident.span == span + { + Ok(Path::Ident { ident: segment.ident, id: segment.id }) + } else { + Ok(Path::General { segments, span }) + } } pub(super) fn parse_path_segments( @@ -242,7 +250,7 @@ impl<'a> Parser<'a> { // `PathStyle::Expr` is only provided at the root invocation and never in // `parse_path_segment` to recurse and therefore can be checked to maintain // this invariant. - self.check_trailing_angle_brackets(&segment, &[exp!(PathSep)]); + self.check_trailing_angle_brackets(segment.as_ref(), &[exp!(PathSep)]); } segments.push(segment); @@ -852,12 +860,7 @@ impl<'a> Parser<'a> { } // We can only resolve single-segment paths at the moment, because multi-segment paths // require type-checking: see `visit_generic_arg` in `src/librustc_resolve/late.rs`. - ast::ExprKind::Path(None, path) - if let [segment] = path.segments.as_slice() - && segment.args.is_none() => - { - true - } + ast::ExprKind::Path(None, path) if path.is_single_argless_ident() => true, ast::ExprKind::ConstBlock(_) => { self.psess.gated_spans.gate(sym::gca_min_const_items, expr.span); true @@ -981,15 +984,15 @@ impl<'a> Parser<'a> { if let GenericArg::Type(ty) = gen_arg { if let ast::TyKind::Path(qself, path) = &ty.kind && qself.is_none() - && let [seg] = path.segments.as_slice() + && let Ok(seg) = path.iter_segments().exactly_one() { - return Ok((false, seg.ident, seg.args.as_deref().cloned())); + return Ok((false, *seg.ident, seg.args.cloned())); } else if let ast::TyKind::TraitObject(bounds, ast::TraitObjectSyntax::None) = &ty.kind && let [ast::GenericBound::Trait(trait_ref)] = bounds.as_slice() && trait_ref.modifiers == ast::TraitBoundModifiers::NONE - && let [seg] = trait_ref.trait_ref.path.segments.as_slice() + && let Ok(seg) = trait_ref.trait_ref.path.iter_segments().exactly_one() { - return Ok((true, seg.ident, seg.args.as_deref().cloned())); + return Ok((true, *seg.ident, seg.args.cloned())); } } Err(()) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index d59ef93eded21..1a40442fc793b 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -3,6 +3,7 @@ use std::mem; use std::ops::Bound; use ast::Label; +use itertools::Itertools; use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind}; use rustc_ast::tokenstream::TokenTree; use rustc_ast::util::classify::{self, TrailingBrace}; @@ -630,7 +631,7 @@ impl<'a> Parser<'a> { (token::CloseBrace, StmtKind::Expr(expr)) if let ExprKind::Struct(expr) = &expr.kind && let None = expr.qself - && expr.path.segments.len() == 1 => + && expr.path.num_segments() == 1 => { // This is specific to "mistyped `if` condition followed by empty body" // @@ -1030,8 +1031,8 @@ impl<'a> Parser<'a> { } if self.token == token::Colon - && let ExprKind::Path(None, ast::Path { segments, .. }) = &expr.kind - && let [segment] = segments.as_slice() + && let ExprKind::Path(None, path) = &expr.kind + && let Some(ident) = path.iter_idents().exactly_one().ok() && self.look_ahead(1, |t| { t.is_metavar_block() || t.kind == token::OpenBrace @@ -1043,8 +1044,8 @@ impl<'a> Parser<'a> { let snapshot = self.create_snapshot_for_diagnostic(); let label = Label { ident: Ident::from_str_and_span( - &format!("'{}", segment.ident), - segment.ident.span, + &format!("'{}", ident), + ident.span, ), }; match self.parse_expr_labeled(label, false) { diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 29b83e006d195..c8279d68b0c8a 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -1370,7 +1370,7 @@ impl<'a> Parser<'a> { if let TyKind::Path(_, path) = &ty.peel_refs().kind => { ( - ty.span.until(path.span), + ty.span.until(path.span()), "consider removing the indirection", "", path, @@ -1445,7 +1445,7 @@ impl<'a> Parser<'a> { match self.parse_fn_decl(&mode, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) { Ok(decl) => { self.dcx().emit_err(ExpectedFnPathFoundFnKeyword { fn_token_span }); - Some(ast::Path { + Some(ast::Path::General { span: fn_token_span.to(self.prev_token.span), segments: thin_vec![ast::PathSegment { ident: Ident::new(sym::Fn, fn_token_span), @@ -1498,7 +1498,8 @@ impl<'a> Parser<'a> { fn_path: &mut ast::Path, lifetime_defs: &mut ThinVec, ) -> PResult<'a, ()> { - let fn_path_segment = fn_path.segments.last_mut().unwrap(); + let (fn_path_segments, _) = fn_path.force_general_mut(); + let fn_path_segment = fn_path_segments.last_mut().unwrap(); let generic_args = if let Some(p_args) = &fn_path_segment.args { *p_args.clone() } else { @@ -1610,7 +1611,7 @@ impl<'a> Parser<'a> { "for<{}> ", lifetimes.iter().map(|lt| lt.ident.as_str()).intersperse(", ").collect::(), ); - let before_fn_path = fn_path.span.shrink_to_lo(); + let before_fn_path = fn_path.span().shrink_to_lo(); self.dcx() .struct_span_err(generic_args_span, "`Fn` traits cannot take lifetime parameters") .with_multipart_suggestion( diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 09d7f5dd11006..57e687893fd11 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -253,13 +253,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // paths" right now, so on 2018 edition we only allow module-relative paths for now. // On 2015 edition visibilities are resolved as crate-relative by default, // so we are prepending a root segment if necessary. - let ident = path.segments.get(0).expect("empty path in visibility").ident; + let ident = path.iter_idents().next().expect("empty path in visibility"); let crate_root = if ident.is_path_segment_keyword() { None } else if ident.span.is_rust_2015() { Some(Segment::from_ident(Ident::new( kw::PathRoot, - path.span.shrink_to_lo().with_ctxt(ident.span.ctxt()), + path.span().shrink_to_lo().with_ctxt(ident.span.ctxt()), ))) } else { return Err(VisResolutionError::Relative2018( @@ -269,11 +269,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; let segments = crate_root .into_iter() - .chain(path.segments.iter().map(|seg| seg.into())) + .chain(path.iter_segments().map(|seg| seg.into())) .collect::>(); let expected_found_error = |res| { Err(VisResolutionError::ExpectedFound( - path.span, + path.span(), Segment::names_to_string(&segments), res, )) @@ -282,7 +282,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { &segments, None, parent_scope, - finalize.then(|| Finalize::new(id, path.span)), + finalize.then(|| Finalize::new(id, path.span())), None, None, ) { @@ -305,7 +305,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } Ok(vis.expect_local()) } else { - Err(VisResolutionError::AncestorOnly(path.span)) + Err(VisResolutionError::AncestorOnly(path.span())) } } } @@ -313,7 +313,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { expected_found_error(res) } } - PathResult::Module(..) => Err(VisResolutionError::ModuleOnly(path.span)), + PathResult::Module(..) => Err(VisResolutionError::ModuleOnly(path.span())), PathResult::NonModule(partial_res) => { expected_found_error(partial_res.expect_full_res()) } @@ -327,7 +327,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { message, }) } - PathResult::Indeterminate => Err(VisResolutionError::Indeterminate(path.span)), + PathResult::Indeterminate => { + Err(VisResolutionError::Indeterminate(path.span())) + } } } } @@ -615,7 +617,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { let mut prefix_iter = parent_prefix .iter() .cloned() - .chain(use_tree.prefix.segments.iter().map(|seg| seg.into())) + .chain(use_tree.prefix.iter_segments().map(|seg| seg.into())) .peekable(); // On 2015 edition imports are resolved as crate-relative by default, @@ -636,7 +638,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { .map(|ctxt| { Segment::from_ident(Ident::new( kw::PathRoot, - use_tree.prefix.span.shrink_to_lo().with_ctxt(ctxt), + use_tree.prefix.span().shrink_to_lo().with_ctxt(ctxt), )) }); diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 91e6188a8c63c..d096ef0ce5dd6 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -359,7 +359,7 @@ fn calc_unused_spans( } else { // Delete everything until the next import, to delete the trailing commas let inner = &nested[pos + 1].inner; - use_tree.inner.prefix.span.to(inner.prefix.span.shrink_to_lo()) + use_tree.inner.prefix.span().to(inner.prefix.span().shrink_to_lo()) }; // Try to collapse adjacent spans into a single one. This prevents all cases of @@ -400,7 +400,7 @@ fn calc_unused_spans( .unwrap() .inner .prefix - .span + .span() .shrink_to_lo()), ); // Right brace, from the end of the last item to the end of the nested group. diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index f3329c3d0398e..0cc4ca7e21b95 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -562,7 +562,7 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { if rustc_attr_parsing::is_builtin_attr(&normal.item) { self.r .builtin_attrs - .push((normal.item.path.segments[0].ident, self.parent_scope)); + .push((*normal.item.path.iter_idents().next().unwrap(), self.parent_scope)); } } AttrKind::Synthetic(CfgTrace(_) | CfgAttrTrace(_)) => {} diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 744e7c22265ea..6cea605671ea2 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1447,7 +1447,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { VisResolutionError::Relative2018(span, path) => { self.dcx().create_err(diagnostics::Relative2018 { span, - path_span: path.span, + path_span: path.span(), // intentionally converting to String, as the text would also be used as // in suggestion context path_str: pprust::path_to_string(&path), @@ -1756,7 +1756,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { segms.append(&mut path_segments.clone()); segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path { span: name_binding.span, segments: segms }; + let path = Path::General { span: name_binding.span, segments: segms }; if child_accessible // Remove invisible match if exists @@ -1769,7 +1769,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let is_stable = if is_stable && let Some(did) = did - && this.is_stable(did, path.span) + && this.is_stable(did, path.span()) { true } else { @@ -2564,11 +2564,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // doesn't start with `Crate`, prepend it (edition 2015 paths are relative // to the crate root without an explicit `crate::` prefix). let candidate_names = { - let filtered_segments: Vec<_> = - path.segments.iter().filter(|segment| segment.ident.name != kw::PathRoot).collect(); - let mut candidate_names: Vec = - filtered_segments.iter().map(|segment| segment.ident.name).collect(); + path.iter_idents().map(|i| i.name).filter(|&n| n != kw::PathRoot).collect(); if candidate_names.first() != Some(&kw::Crate) { candidate_names.insert(0, kw::Crate); } @@ -2614,11 +2611,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // Only apply if the result is strictly shorter than the original path. - if new_segments.len() >= path.segments.len() { + if new_segments.len() >= path.num_segments() { return; } - *path = Path { span: path.span, segments: new_segments }; + *path = Path::General { span: path.span(), segments: new_segments }; } fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) { @@ -2816,7 +2813,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module_path.map(|module_path| { // `import.module_path` is relative to the import's module, not to the // failing use site. - let mut path = Path { + let mut path = Path::General { span: ident.span, segments: module_path .into_iter() @@ -2827,7 +2824,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .collect(), }; self.shorten_import_path(res_def_id, &mut path, parent_scope.module); - path.segments.iter().map(|seg| seg.ident).collect() + path.iter_idents().copied().collect() }) } else { // Don't include `{{root}}` in suggestions - it's an internal symbol @@ -2939,7 +2936,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let ast::ExprKind::Struct(struct_expr) = &expr.kind else { return }; // We don't have to handle type-relative paths because they're forbidden in ADT // expressions, but that would change with `#[feature(more_qualified_paths)]`. - let Some(segment) = struct_expr.path.segments.last() else { return }; + let Some(segment) = struct_expr.path.last_segment() else { return }; let Some(partial_res) = self.partial_res_map.get(&segment.id) else { return }; let Some(Res::Def(_, def_id)) = partial_res.full_res() else { return; @@ -3101,23 +3098,21 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let is_mod = |res| matches!(res, Res::Def(DefKind::Mod, _)); let mut candidates = self.lookup_import_candidates(ident, TypeNS, parent_scope, is_mod); candidates - .sort_by_cached_key(|c| (c.path.segments.len(), pprust::path_to_string(&c.path))); - if let Some(candidate) = candidates.get(0) { - let path = { - // remove the possible common prefix of the path - let len = candidate.path.segments.len(); - let start_index = (0..=failed_segment_idx.min(len - 1)) - .find(|&i| path[i].ident.name != candidate.path.segments[i].ident.name) - .unwrap_or_default(); - let segments = - (start_index..len).map(|s| candidate.path.segments[s].clone()).collect(); - Path { segments, span: Span::default() } - }; + .sort_by_cached_key(|c| (c.path.num_segments(), pprust::path_to_string(&c.path))); + if let Some(candidate) = candidates.first_mut() { + // remove the possible common prefix of the path + let (segments, span) = candidate.path.force_general_mut(); + *span = Span::default(); + let len = segments.len(); + let start_index = (0..=failed_segment_idx.min(len - 1)) + .find(|&i| path[i].ident.name != segments[i].ident.name) + .unwrap_or_default(); + segments.drain(..start_index); ( message, String::from("unresolved import"), Some(( - vec![(ident.span, pprust::path_to_string(&path))], + vec![(ident.span, pprust::path_to_string(&candidate.path))], String::from("a similar path exists"), Applicability::MaybeIncorrect, )), diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 6a5a73715c840..1016d38748779 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -581,7 +581,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if ext.helper_attrs.contains(&ident.name) { let decl = self.arenas.new_pub_def_decl( Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat), - derive.span, + derive.span(), LocalExpnId::ROOT, ); result = Ok(decl); diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index ab595de3e39e7..3769c0b056057 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -534,15 +534,12 @@ impl PathSource<'_, '_, '_> { // this is not precise but usually more helpful than just "value". Some(ExprKind::Call(call_expr, _)) => match &call_expr.kind { // the case of `::some_crate()` - ExprKind::Path(_, path) - if let [segment, _] = path.segments.as_slice() - && segment.ident.name == kw::PathRoot => - { + ExprKind::Path(_, path) if path.num_segments() == 2 && path.is_global() => { "external crate" } ExprKind::Path(_, path) - if let Some(segment) = path.segments.last() - && let Some(c) = segment.ident.to_string().chars().next() + if let Some(ident) = path.last_ident() + && let Some(c) = ident.to_string().chars().next() && c.is_uppercase() => { "function, tuple struct or tuple variant" @@ -938,9 +935,8 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc // If we have a path that ends with `(..)`, then it must be // return type notation. Resolve that path in the *value* // namespace. - let source = if let Some(seg) = path.segments.last() - && let Some(args) = &seg.args - && matches!(**args, GenericArgs::ParenthesizedElided(..)) + let source = if let Some(seg) = path.last_segment() + && matches!(seg.args, Some(GenericArgs::ParenthesizedElided(..))) { PathSource::ReturnTypeNotation } else { @@ -958,7 +954,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc // This path is actually a bare trait object. In case of a bare `Fn`-trait // object with anonymous lifetimes, we need this rib to correctly place the // synthetic lifetimes. - let span = ty.span.shrink_to_lo().to(path.span.shrink_to_lo()); + let span = ty.span.shrink_to_lo().to(path.span().shrink_to_lo()); self.with_generic_param_rib( &[], RibKind::Normal, @@ -1069,7 +1065,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc } fn visit_poly_trait_ref(&mut self, tref: &'ast PolyTraitRef) { - let span = tref.span.shrink_to_lo().to(tref.trait_ref.path.span.shrink_to_lo()); + let span = tref.span.shrink_to_lo().to(tref.trait_ref.path.span().shrink_to_lo()); self.with_generic_param_rib( &tref.bound_generic_params, RibKind::Normal, @@ -1256,9 +1252,9 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc // it doesn't really matter which we try resolving first, but just like // `Ty::Param` we just fall back to the value namespace only if it's missing // from the type namespace. - let mut check_ns = |ns| { - self.maybe_resolve_ident_in_lexical_scope(path.segments[0].ident, ns).is_some() - }; + let ident = *path.iter_idents().next().unwrap(); + let mut check_ns = + |ns| self.maybe_resolve_ident_in_lexical_scope(ident, ns).is_some(); // Like `Ty::Param`, we try resolving this as both a const and a type. if !check_ns(TypeNS) && check_ns(ValueNS) { self.smart_resolve_path( @@ -2818,14 +2814,14 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } fn future_proof_import(&mut self, use_tree: &UseTree) { - if let [segment, rest @ ..] = use_tree.prefix.segments.as_slice() { - let ident = segment.ident; + let mut ident_iter = use_tree.prefix.iter_idents(); + if let Some(&ident) = ident_iter.next() { if ident.is_path_segment_keyword() || ident.span.is_rust_2015() { return; } let nss = match use_tree.kind { - UseTreeKind::Simple(..) if rest.is_empty() => &[TypeNS, ValueNS][..], + UseTreeKind::Simple(..) if ident_iter.next().is_none() => &[TypeNS, ValueNS][..], _ => &[TypeNS], }; let report_error = |this: &Self, ns| { @@ -3106,7 +3102,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } ItemKind::Delegation(delegation) => { - let span = delegation.path.segments.last().unwrap().ident.span; + let span = delegation.last_segment_span(); self.with_generic_param_rib( &[], RibKind::Item(HasGenericParams::Yes(span), def_kind), @@ -3464,7 +3460,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { RibKind::AssocItem, item.id, LifetimeBinderKind::Function, - delegation.path.segments.last().unwrap().ident.span, + delegation.last_segment_span(), |this| this.resolve_delegation(delegation, item.id, false), ); } @@ -3495,7 +3491,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { &None, &path, PathSource::Trait(AliasPossibility::No), - Finalize::new(trait_ref.ref_id, trait_ref.path.span), + Finalize::new(trait_ref.ref_id, trait_ref.path.span()), RecordPartialRes::Yes, None, ); @@ -3772,7 +3768,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { RibKind::AssocItem, item.id, LifetimeBinderKind::Function, - delegation.path.segments.last().unwrap().ident.span, + delegation.last_segment_span(), |this| { if !is_in_trait_impl { this.fill_delegation_inherent_fn_map( @@ -3903,7 +3899,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { match seen_trait_items.entry(id_in_trait) { Entry::Occupied(entry) => { let trait_span = decl.parent_module.unwrap().span.shrink_to_lo(); - let impl_span = self.current_trait_ref.as_ref().unwrap().1.path.span; + let impl_span = self.current_trait_ref.as_ref().unwrap().1.path.span(); self.report_error( span, ResolutionError::TraitImplDuplicate { @@ -3945,7 +3941,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { }; let trait_path = path_names_to_string(path); let trait_span = decl.parent_module.unwrap().span.shrink_to_lo(); - let impl_span = self.current_trait_ref.as_ref().unwrap().1.path.span; + let impl_span = self.current_trait_ref.as_ref().unwrap().1.path.span(); self.report_error( span, ResolutionError::TraitImplMismatch { @@ -4041,7 +4037,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { }) .unwrap_or_else(|| { DelegationResolution::Error(self.r.tcx.dcx().span_delayed_bug( - delegation.path.span, + delegation.path.span(), format!("bad resolution for delegation {item_id:?}"), )) }); @@ -4647,7 +4643,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { qself, &Segment::from_path(path), source, - Finalize::new(id, path.span), + Finalize::new(id, path.span()), RecordPartialRes::Yes, None, ); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index b2061cd9d9d91..a54572b7af0aa 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -2,13 +2,12 @@ use std::borrow::Cow; use std::iter; -use std::ops::Deref; use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt, Visitor, walk_ty}; use rustc_ast::{ self as ast, AngleBracketedArg, AssocItemKind, DUMMY_NODE_ID, Expr, ExprKind, GenericArg, GenericArgs, GenericParam, GenericParamKind, Item, ItemKind, MethodCall, NodeId, Path, - PathSegment, Ty, TyKind, + PathSegment, PathSegmentRef, Ty, TyKind, }; use rustc_ast_pretty::pprust::{path_to_string, where_bound_predicate_to_string}; use rustc_attr_ir::diagnostic::{CustomDiagnostic, FormatArgs}; @@ -82,10 +81,10 @@ fn is_self_value(path: &[Segment], namespace: Namespace) -> bool { fn path_to_string_without_assoc_item_bindings(path: &Path) -> String { let mut path = path.clone(); - for segment in &mut path.segments { + let segments = path.force_general_mut().0; + for segment in segments { let mut remove_args = false; - if let Some(args) = segment.args.as_deref_mut() - && let ast::GenericArgs::AngleBracketed(angle_bracketed) = args + if let Some(ast::GenericArgs::AngleBracketed(angle_bracketed)) = segment.args.as_deref_mut() { angle_bracketed.args.retain(|arg| matches!(arg, ast::AngleBracketedArg::Arg(_))); remove_args = angle_bracketed.args.is_empty(); @@ -102,11 +101,8 @@ fn import_candidate_to_enum_paths(suggestion: &ImportSuggestion) -> (String, Str let variant_path = &suggestion.path; let variant_path_string = path_names_to_string(variant_path); - let path_len = suggestion.path.segments.len(); - let enum_path = ast::Path { - span: suggestion.path.span, - segments: suggestion.path.segments[0..path_len - 1].iter().cloned().collect(), - }; + let mut enum_path = suggestion.path.clone(); + let _ = enum_path.force_general_mut().0.pop(); let enum_path_string = path_names_to_string(&enum_path); (variant_path_string, enum_path_string) @@ -246,7 +242,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if poly_trait_ref.modifiers != ast::TraitBoundModifiers::NONE { return; } - let Some(trait_seg) = poly_trait_ref.trait_ref.path.segments.last() else { + let Some(trait_seg) = poly_trait_ref.trait_ref.path.last_segment() else { return; }; let Some(partial_res) = this.r.partial_res_map.get(&trait_seg.id) else { @@ -291,8 +287,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let ast::TyKind::Path(None, bounded_path) = &where_bound.bounded_ty.kind else { continue; }; - let [ast::PathSegment { ident, args: None, .. }] = &bounded_path.segments[..] - else { + let Some(name) = bounded_path.as_single_argless_name() else { continue; }; @@ -307,7 +302,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { for bound in &where_bound.bounds { let ast::GenericBound::Trait(poly_trait_ref) = bound else { continue }; - record_bound(this, ident.name, poly_trait_ref); + record_bound(this, name, poly_trait_ref); } } }; @@ -900,18 +895,19 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { res: Option, qself: Option<&QSelf>, ) { + use itertools::Itertools; if let Some(Res::Def(DefKind::AssocFn, _)) = res && let PathSource::TraitItem(TypeNS, _) = source && let None = following_seg && let Some(qself) = qself && let TyKind::Path(None, ty_path) = &qself.ty.kind - && ty_path.segments.len() == 1 + && let Ok(ident) = ty_path.iter_idents().exactly_one() && self.diag_metadata.current_where_predicate.is_some() { err.span_suggestion_verbose( span, "you might have meant to use the return type notation syntax", - format!("{}::{}(..)", ty_path.segments[0].ident, path[path.len() - 1].ident), + format!("{ident}::{}(..)", path[path.len() - 1].ident), Applicability::MaybeIncorrect, ); } @@ -928,13 +924,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let TyKind::Path(_, path) = &ty.kind else { return; }; - for segment in &path.segments { - let Some(params) = &segment.args else { - continue; - }; - let ast::GenericArgs::AngleBracketed(params) = params.deref() else { - continue; - }; + for segment in path.iter_segments() { + let Some(ast::GenericArgs::AngleBracketed(params)) = segment.args else { continue }; for param in ¶ms.args { let ast::AngleBracketedArg::Constraint(constraint) = param else { continue; @@ -1760,6 +1751,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } fn suggest_at_operator_in_slice_pat_with_range(&self, err: &mut Diag<'_>, path: &[Segment]) { + use itertools::Itertools; let Some(pat) = self.diag_metadata.current_pat else { return }; let (bound, side, range) = match &pat.kind { ast::PatKind::Range(Some(bound), None, range) => (bound, Side::Start, range), @@ -1767,20 +1759,20 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { _ => return, }; if let ExprKind::Path(None, range_path) = &bound.kind - && let [segment] = &range_path.segments[..] + && let Ok(&ident) = range_path.iter_idents().exactly_one() && let [s] = path - && segment.ident == s.ident - && segment.ident.span.eq_ctxt(range.span) + && ident == s.ident + && ident.span.eq_ctxt(range.span) { // We've encountered `[first, rest..]` (#88404) or `[first, ..rest]` (#120591) // where the user might have meant `[first, rest @ ..]`. let (span, snippet) = match side { - Side::Start => (segment.ident.span.between(range.span), " @ ".into()), - Side::End => (range.span.to(segment.ident.span), format!("{} @ ..", segment.ident)), + Side::Start => (ident.span.between(range.span), " @ ".into()), + Side::End => (range.span.to(ident.span), format!("{ident} @ ..")), }; err.subdiagnostic(diagnostics::UnexpectedResUseAtOpInSlicePatWithRangeSugg { span, - ident: segment.ident, + ident, snippet, }); } @@ -1882,16 +1874,15 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { && let PathResult::Module(ModuleOrUniformRoot::Module(module)) = self.resolve_path(&Segment::from_path(self_ty_path), Some(TypeNS), None, source) && module.def_kind() == Some(DefKind::Trait) - && trait_ref.path.span == span + && trait_ref.path.span() == span && let PathSource::Trait(_) = source && let Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)) = res && let Ok(self_ty_str) = self.r.tcx.sess.source_map().span_to_snippet(self_ty.span) - && let Ok(trait_ref_str) = - self.r.tcx.sess.source_map().span_to_snippet(trait_ref.path.span) + && let Ok(trait_ref_str) = self.r.tcx.sess.source_map().span_to_snippet(span) { err.multipart_suggestion( "`impl` items mention the trait being implemented first and the type it is being implemented for second", - vec![(trait_ref.path.span, self_ty_str), (self_ty.span, trait_ref_str)], + vec![(span, self_ty_str), (self_ty.span, trait_ref_str)], Applicability::MaybeIncorrect, ); } @@ -2053,6 +2044,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { /// Given `where ::Baz: String`, suggest `where T: Bar`. fn restrict_assoc_type_in_where_clause(&self, span: Span, err: &mut Diag<'_>) -> bool { + use itertools::Itertools; // Detect that we are actually in a `where` predicate. let Some(ast::WherePredicate { kind: @@ -2088,13 +2080,14 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if !matches!(partial_res.full_res(), Some(Res::Def(DefKind::TyParam, _))) { return false; } - let ([ast::PathSegment { args: None, .. }], [ast::GenericBound::Trait(poly_trait_ref)]) = - (&type_param_path.segments[..], &bounds[..]) - else { + if !type_param_path.is_single_argless_ident() { + return false; + } + let [ast::GenericBound::Trait(poly_trait_ref)] = &bounds[..] else { return false; }; - let [ast::PathSegment { ident, args: None, id }] = - &poly_trait_ref.trait_ref.path.segments[..] + let Ok(ast::PathSegmentRef { ident, args: None, id }) = + poly_trait_ref.trait_ref.path.iter_segments().exactly_one() else { return false; }; @@ -2135,8 +2128,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let mut expr_kind = &args[0].kind; loop { match expr_kind { - ExprKind::Path(_, arg_name) if arg_name.segments.len() == 1 => { - if arg_name.segments[0].ident.name == kw::SelfLower { + ExprKind::Path(_, arg_name) if arg_name.num_segments() == 1 => { + if arg_name.iter_idents().next().unwrap().name == kw::SelfLower { let call_span = parent.span; let tail_args_span = if args.len() > 1 { Some(Span::new( @@ -3172,8 +3165,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { && let ast::ExprKind::Path(None, ref path) = lhs.kind && self.r.tcx.sess.source_map().is_line_before_span_empty(ident_span) { - let (span, text) = match path.segments.first() { - Some(seg) if let Some(name) = seg.ident.as_str().strip_prefix("let") => { + let (span, text) = match path.iter_idents().next() { + Some(ident) if let Some(name) = ident.as_str().strip_prefix("let") => { // a special case for #117894 let name = name.trim_prefix('_'); (ident_span, format!("let {name}")) @@ -3231,7 +3224,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let doc_visible = doc_visible && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); if module_def_id == def_id { - let path = Path { span: name_binding.span, segments: path_segments }; + let path = + Path::General { span: name_binding.span, segments: path_segments }; result = Some(( r.expect_module(module_def_id), ImportSuggestion { @@ -3264,9 +3258,10 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let mut variants = Vec::new(); enum_module.for_each_child(self.r, |_, ident, orig_ident_span, _, name_binding| { if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() { - let mut segms = enum_import_suggestion.path.segments.clone(); - segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path { span: name_binding.span, segments: segms }; + let mut path = enum_import_suggestion.path.clone(); + let (segments, span) = path.force_general_mut(); + segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + *span = name_binding.span; variants.push((path, def_id, kind)); } }); @@ -3307,8 +3302,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }) => { let dot_span = receiver.span.between(*span); let found_tuple_variant = variant_ctors.iter().any(|(path, _, ctor_kind)| { - *ctor_kind == CtorKind::Fn - && path.segments.last().is_some_and(|seg| seg.ident == *ident) + *ctor_kind == CtorKind::Fn && path.last_ident() == Some(*ident) }); (found_tuple_variant.then_some(dot_span), false) } @@ -3316,9 +3310,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { // otherwise suggest adding a variant after `Type`. ExprKind::Field(base, ident) => { let dot_span = base.span.between(ident.span); - let found_tuple_or_unit_variant = variant_ctors.iter().any(|(path, ..)| { - path.segments.last().is_some_and(|seg| seg.ident == *ident) - }); + let found_tuple_or_unit_variant = + variant_ctors.iter().any(|(path, ..)| path.last_ident() == Some(*ident)); (found_tuple_or_unit_variant.then_some(dot_span), false) } _ => (None, false), @@ -3488,6 +3481,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { path: &[Segment], source: PathSource<'_, 'ast, 'ra>, ) -> Option> { + use itertools::Itertools; let Some(item) = self.diag_metadata.current_item else { return None }; let ItemKind::Impl(impl_) = &item.kind else { return None }; let self_ty = &impl_.self_ty; @@ -3506,11 +3500,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { return None; }; - let Some(args) = parent_segment.args.as_ref() else { - return None; - }; - - let GenericArgs::AngleBracketed(angle) = args.as_ref() else { + let Some(GenericArgs::AngleBracketed(angle)) = parent_segment.args else { return None; }; @@ -3523,7 +3513,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { .filter_map(|(pos, arg)| { if let AngleBracketedArg::Arg(GenericArg::Type(ty)) = arg && let TyKind::Path(_, path) = &ty.kind - && let [segment] = path.segments.as_slice() + && let Ok(segment) = path.iter_segments().exactly_one() { Some((segment.id, pos)) } else { @@ -3560,12 +3550,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { && let GenericParamKind::Const { ty, .. } = &target_param.kind && let TyKind::Path(_, path) = &ty.kind { - let full_type = path - .segments - .iter() - .map(|seg| seg.ident.to_string()) - .collect::>() - .join("::"); + let full_type = path.iter_idents().join("::"); // Find the first impl param whose position in C // is strictly greater than our missing param's index @@ -3578,8 +3563,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { .find_map(|arg| { if let AngleBracketedArg::Arg(GenericArg::Type(ty)) = arg && let TyKind::Path(_, path) = &ty.kind - && let [segment] = path.segments.as_slice() - && segment.ident == impl_param.ident + && let Ok(segment) = path.iter_segments().exactly_one() + && *segment.ident == impl_param.ident { usage_to_pos.get(&segment.id).copied() } else { @@ -4061,7 +4046,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let ast::GenericBound::Trait(poly_trait_ref) = bound && let span = poly_trait_ref .span - .with_hi(poly_trait_ref.trait_ref.path.span.lo()) + .with_hi(poly_trait_ref.trait_ref.path.span().lo()) && !span.is_empty() { rm_inner_binders.insert(span); @@ -4672,7 +4657,10 @@ fn mk_where_bound_predicate( ty: &Ty, ) -> Option { let modified_segments = { - let mut segments = path.segments.clone(); + let Path::General { segments, .. } = path else { + return None; + }; + let mut segments = segments.clone(); let [preceding @ .., second_last, last] = segments.as_mut_slice() else { return None; }; @@ -4717,7 +4705,7 @@ fn mk_where_bound_predicate( bound_generic_params: ThinVec::new(), modifiers: ast::TraitBoundModifiers::NONE, trait_ref: ast::TraitRef { - path: ast::Path { segments: modified_segments, span: DUMMY_SP }, + path: ast::Path::General { segments: modified_segments, span: DUMMY_SP }, ref_id: DUMMY_NODE_ID, }, span: DUMMY_SP, @@ -4801,7 +4789,7 @@ pub(super) fn signal_label_shadowing(sess: &Session, orig: Span, shadower: Ident struct ParentPathVisitor<'a> { target: Ident, - parent: Option<&'a PathSegment>, + parent: Option>, stack: Vec<&'a Ty>, } @@ -4816,6 +4804,7 @@ impl<'a> ParentPathVisitor<'a> { impl<'a> Visitor<'a> for ParentPathVisitor<'a> { fn visit_ty(&mut self, ty: &'a Ty) { + use itertools::Itertools; if self.parent.is_some() { return; } @@ -4825,13 +4814,13 @@ impl<'a> Visitor<'a> for ParentPathVisitor<'a> { if let TyKind::Path(_, path) = &ty.kind // is this just `N`? - && let [segment] = path.segments.as_slice() - && segment.ident == self.target + && let Ok(&ident) = path.iter_idents().exactly_one() + && ident == self.target // parent is previous element in stack && let [.., parent_ty, _ty] = self.stack.as_slice() && let TyKind::Path(_, parent_path) = &parent_ty.kind { - self.parent = parent_path.segments.first(); + self.parent = parent_path.iter_segments().next(); } walk_ty(self, ty); diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index b92da685bc6a5..0274e4d47218f 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -374,7 +374,7 @@ struct Segment { impl Segment { fn from_path(path: &Path) -> Vec { - path.segments.iter().map(|s| s.into()).collect() + path.iter_segments().map(Segment::from).collect() } fn from_ident(ident: Ident) -> Segment { @@ -392,10 +392,10 @@ impl Segment { } } -impl<'a> From<&'a ast::PathSegment> for Segment { - fn from(seg: &'a ast::PathSegment) -> Segment { +impl<'a> From> for Segment { + fn from(seg: ast::PathSegmentRef<'a>) -> Segment { let has_generic_args = seg.args.is_some(); - let (args_span, has_lifetime_args) = if let Some(args) = seg.args.as_deref() { + let (args_span, has_lifetime_args) = if let Some(args) = seg.args { match args { GenericArgs::AngleBracketed(args) => { let found_lifetimes = args @@ -411,7 +411,7 @@ impl<'a> From<&'a ast::PathSegment> for Segment { (DUMMY_SP, false) }; Segment { - ident: seg.ident, + ident: *seg.ident, id: Some(seg.id), has_generic_args, has_lifetime_args, @@ -2647,7 +2647,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; // Don't perform legacy const generics rewriting if the path already // has generic arguments. - if path.segments.last().unwrap().args.is_some() { + if path.last_segment().unwrap().args.is_some() { return None; } @@ -2795,7 +2795,7 @@ fn names_to_string(names: impl Iterator) -> String { } fn path_names_to_string(path: &Path) -> String { - names_to_string(path.segments.iter().map(|seg| seg.ident.name)) + names_to_string(path.iter_idents().map(|i| i.name)) } /// A somewhat inefficient routine to obtain the name of a module. diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 29a82f809f124..434bb24753bf4 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -4,6 +4,7 @@ use std::mem; use std::sync::Arc; +use itertools::Itertools; use rustc_ast::{self as ast, Crate, DelegationSuffixes, NodeId}; use rustc_ast_pretty::pprust; use rustc_attr_ir::{Attribute, AttributeKind, CfgEntry, StabilityLevel, StrippedCfgItem}; @@ -104,16 +105,16 @@ pub(crate) fn sub_namespace_match( // `format!("{}", path)`, because that tries to insert // line-breaks and is slow. fn fast_print_path(path: &ast::Path) -> Symbol { - if let [segment] = path.segments.as_slice() { - segment.ident.name + if let Ok(ident) = path.iter_idents().exactly_one() { + ident.name } else { let mut path_str = String::with_capacity(64); - for (i, segment) in path.segments.iter().enumerate() { + for (i, ident) in path.iter_idents().enumerate() { if i != 0 { path_str.push_str("::"); } - if segment.ident.name != kw::PathRoot { - path_str.push_str(segment.ident.as_str()) + if ident.name != kw::PathRoot { + path_str.push_str(ident.as_str()) } } Symbol::intern(&path_str) @@ -439,7 +440,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { ) { Ok((Some(ext), _)) => { if !ext.helper_attrs.is_empty() { - let span = resolution.path.segments.last().unwrap().ident.span; + let span = resolution.path.last_ident().unwrap().span; let ctxt = Macros20NormalizedSyntaxContext::new(span.ctxt()); entry.helper_attrs.extend( ext.helper_attrs @@ -631,7 +632,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if deleg_impl.is_some() { if !matches!(res, Res::Err | Res::Def(DefKind::Trait, _)) { self.dcx().emit_err(MacroExpectedFound { - span: path.span, + span: path.span(), expected: "trait", article: "a", found: res.descr(), @@ -646,8 +647,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // Report errors for the resolved macro. - for (idx, segment) in path.segments.iter().enumerate() { - if let Some(args) = &segment.args { + for (idx, segment) in path.iter_segments().enumerate() { + if let Some(args) = segment.args { self.dcx().emit_err(diagnostics::GenericArgumentsInMacroPath { span: args.span() }); } if kind == MacroKind::Attr && segment.ident.as_str().starts_with("rustc") { @@ -669,7 +670,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { self.unused_macros.swap_remove(&def_id); if self.proc_macro_stubs.contains(&def_id) { self.dcx().emit_err(diagnostics::ProcMacroSameCrate { - span: path.span, + span: path.span(), is_test: self.tcx.sess.is_test_crate(), }); } @@ -701,7 +702,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let path_str = pprust::path_to_string(path); let mut err = MacroExpectedFound { - span: path.span, + span: path.span(), expected, article, found: res.descr(), @@ -711,12 +712,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; // Suggest moving the macro out of the derive() if the macro isn't Derive - if !path.span.from_expansion() + if !path.span().from_expansion() && kind == MacroKind::Derive && !ext.macro_kinds().contains(MacroKinds::DERIVE) && ext.macro_kinds().contains(MacroKinds::ATTR) { - err.remove_surrounding_derive = Some(RemoveSurroundingDerive { span: path.span }); + err.remove_surrounding_derive = Some(RemoveSurroundingDerive { span: path.span() }); err.add_as_non_derive = Some(AddAsNonDerive { macro_path: &path_str }); } @@ -737,7 +738,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } else { "custom inner attributes are unstable" }; - feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit(); + feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span(), msg).emit(); } Ok((ext, res)) @@ -773,7 +774,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_import: Option>, suggestion_span: Option, ) -> Result<(Option<&'ra Arc>, Res), Determinacy> { - let path_span = ast_path.span; + let path_span = ast_path.span(); let mut path = Segment::from_path(ast_path); // Possibly apply the macro helper hack @@ -1082,7 +1083,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path: &ast::Path, node_id: NodeId, ) { - let span = path.span; + let span = path.span(); if let Some(stability) = &ext.stability && let StabilityLevel::Unstable { reason, issue, implied_by, .. } = stability.level { @@ -1153,7 +1154,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // If such resolution is successful and gives the same result // (e.g. if the macro is re-imported), then silence the lint. let no_macro_rules = self.arenas.alloc_macro_rules_scope(MacroRulesScope::Empty); - let ident = path.segments[0].ident; + let ident = *path.iter_idents().next().unwrap(); let fallback_binding = self.reborrow().resolve_ident_in_scope_set( ident, ScopeSet::Macro(MacroKind::Bang), @@ -1180,10 +1181,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; self.tcx.sess.psess.buffer_lint( OUT_OF_SCOPE_MACRO_CALLS, - path.span, + path.span(), node_id, diagnostics::OutOfScopeMacroCalls { - span: path.span, + span: path.span(), path: pprust::path_to_string(path), location, }, @@ -1247,7 +1248,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path: &ast::Path, namespaces: &[Namespace], ) -> Result { - let span = path.span; + let span = path.span(); let path = &Segment::from_path(path); let parent_scope = self.invocation_parent_scopes[&expn_id]; diff --git a/tests/ui-fulldeps/pprust-expr-roundtrip.rs b/tests/ui-fulldeps/pprust-expr-roundtrip.rs index 266afab4d7b2f..735be0c7ebee4 100644 --- a/tests/ui-fulldeps/pprust-expr-roundtrip.rs +++ b/tests/ui-fulldeps/pprust-expr-roundtrip.rs @@ -48,8 +48,7 @@ fn expr(kind: ExprKind) -> Box { } fn make_x() -> Box { - let seg = PathSegment::from_ident(Ident::from_str("x")); - let path = Path { segments: thin_vec![seg], span: DUMMY_SP }; + let path = Path::from_ident(Ident::from_str("x")); expr(ExprKind::Path(None, path)) } diff --git a/tests/ui/stats/input-stats.stderr b/tests/ui/stats/input-stats.stderr index 420b01102e41e..bbc7a874325b2 100644 --- a/tests/ui/stats/input-stats.stderr +++ b/tests/ui/stats/input-stats.stderr @@ -10,26 +10,26 @@ ast-stats - Impl 144 (NN.N%) 1 ast-stats - Trait 144 (NN.N%) 1 ast-stats - Fn 288 (NN.N%) 2 ast-stats - Use 576 (NN.N%) 4 -ast-stats PathSegment 840 (NN.N%) 35 24 ast-stats Ty 784 (NN.N%) 14 56 ast-stats - Ptr 56 (NN.N%) 1 ast-stats - Ref 56 (NN.N%) 1 ast-stats - ImplicitSelf 112 (NN.N%) 2 ast-stats - Path 560 (NN.N%) 10 -ast-stats Expr 576 (NN.N%) 9 64 -ast-stats - InlineAsm 64 (NN.N%) 1 -ast-stats - Match 64 (NN.N%) 1 -ast-stats - Path 64 (NN.N%) 1 -ast-stats - Struct 64 (NN.N%) 1 -ast-stats - Lit 128 (NN.N%) 2 -ast-stats - Block 192 (NN.N%) 3 -ast-stats Pat 448 (NN.N%) 7 64 -ast-stats - Struct 64 (NN.N%) 1 -ast-stats - Wild 64 (NN.N%) 1 -ast-stats - Ident 320 (NN.N%) 5 +ast-stats Expr 648 (NN.N%) 9 72 +ast-stats - InlineAsm 72 (NN.N%) 1 +ast-stats - Match 72 (NN.N%) 1 +ast-stats - Path 72 (NN.N%) 1 +ast-stats - Struct 72 (NN.N%) 1 +ast-stats - Lit 144 (NN.N%) 2 +ast-stats - Block 216 (NN.N%) 3 +ast-stats Pat 504 (NN.N%) 7 72 +ast-stats - Struct 72 (NN.N%) 1 +ast-stats - Wild 72 (NN.N%) 1 +ast-stats - Ident 360 (NN.N%) 5 +ast-stats PathSegment 432 (NN.N%) 18 24 ast-stats GenericParam 400 (NN.N%) 5 80 -ast-stats GenericBound 320 (NN.N%) 4 80 -ast-stats - Trait 320 (NN.N%) 4 +ast-stats GenericBound 352 (NN.N%) 4 88 +ast-stats - Trait 352 (NN.N%) 4 ast-stats AssocItem 288 (NN.N%) 4 72 ast-stats - Fn 144 (NN.N%) 2 ast-stats - Type 144 (NN.N%) 2 @@ -58,7 +58,7 @@ ast-stats GenericArgs 40 (NN.N%) 1 40 ast-stats - AngleBracketed 40 (NN.N%) 1 ast-stats Crate 40 (NN.N%) 1 40 ast-stats ---------------------------------------------------------------- -ast-stats Total 6_872 126 +ast-stats Total 6_624 109 ast-stats ================================================================ hir-stats ================================================================ hir-stats HIR STATS: input_stats From 205f029e663ed35d2090e746f05fe97bdb71b470 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sat, 3 Oct 2026 11:20:36 -0700 Subject: [PATCH 02/30] Track path sizes in input-stats --- compiler/rustc_passes/src/input_stats.rs | 56 ++++++++++++++++++++++-- tests/ui/stats/input-stats.stderr | 4 ++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index d8524efb35bc2..80f4ff4ba3374 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -54,15 +54,23 @@ impl Node { /// There are some types in the AST and HIR tree that the visitors do not have /// a `visit_*` method for, and so we cannot measure these, which is /// unfortunate. +#[derive(Default)] struct StatCollector<'k> { tcx: Option>, nodes: FxHashMap<&'static str, Node>, seen: FxHashSet, + paths: PathStats, +} + +#[derive(Default)] +struct PathStats { + idents: usize, + generals: [usize; 5], + span_reconstructible: usize, } pub fn print_hir_stats(tcx: TyCtxt<'_>) { - let mut collector = - StatCollector { tcx: Some(tcx), nodes: FxHashMap::default(), seen: FxHashSet::default() }; + let mut collector = StatCollector { tcx: Some(tcx), ..StatCollector::default() }; tcx.hir_walk_toplevel_module(&mut collector); tcx.hir_walk_attributes(&mut collector); collector.print(tcx, "HIR STATS", "hir-stats"); @@ -71,8 +79,7 @@ pub fn print_hir_stats(tcx: TyCtxt<'_>) { pub fn print_ast_stats(tcx: TyCtxt<'_>, krate: &ast::Crate) { use rustc_ast::visit::Visitor; - let mut collector = - StatCollector { tcx: None, nodes: FxHashMap::default(), seen: FxHashSet::default() }; + let mut collector = StatCollector::default(); collector.visit_crate(krate); collector.print(tcx, "POST EXPANSION AST STATS", "ast-stats"); } @@ -194,6 +201,30 @@ impl<'k> StatCollector<'k> { "", usize_with_underscores(total_count), ); + _ = writeln!(s, "{prefix} {}", "-".repeat(banner_w)); + if self.tcx.is_none() { + _ = writeln!( + s, + "{prefix} - Path Ident: {} General segs 0: {} 1: {} 2: {} 3: {} 4+: {} Span reconstructible: {}", + self.paths.idents, + self.paths.generals[0], + self.paths.generals[1], + self.paths.generals[2], + self.paths.generals[3], + self.paths.generals[4], + self.paths.span_reconstructible, + ); + } else { + _ = writeln!( + s, + "{prefix} - Path segments 0: {} 1: {} 2: {} 3: {} 4+: {}", + self.paths.generals[0], + self.paths.generals[1], + self.paths.generals[2], + self.paths.generals[3], + self.paths.generals[4], + ); + } _ = writeln!(s, "{prefix} {}", "=".repeat(banner_w)); eprint!("{s}"); } @@ -457,6 +488,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { // HIR, which causes `p` to be double- or triple-counted. Instead just // walk the path internals (i.e. the segments) directly. let hir::Path { span: _, res: _, segments } = *tree.prefix; + self.paths.generals[segments.len().min(4)] += 1; ast_visit::walk_list!(self, visit_path_segment, segments); match tree.kind { hir::UseKind::Single(_) | hir::UseKind::Glob => {} @@ -537,6 +569,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { fn visit_path(&mut self, path: &hir::Path<'v>, _id: HirId) { self.record("Path", None, path); + self.paths.generals[path.segments.len().min(4)] += 1; hir_visit::walk_path(self, path) } @@ -766,6 +799,21 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { ast_visit::walk_path_segment(self, path_segment) } + fn visit_path(&mut self, path: &'v ast::Path) { + match path { + ast::Path::Ident { .. } => self.paths.idents += 1, + ast::Path::General { segments, span } => { + let len = segments.len().min(4); + self.paths.generals[len] += 1; + let len = segments.len(); + if len > 0 && segments[0].span().to(segments[len - 1].span()) == *span { + self.paths.span_reconstructible += 1; + } + } + } + ast_visit::walk_path(self, path); + } + // `GenericArgs` has one inline use (in `ast::AssocItemConstraint::gen_args`) and one // non-inline use (in `ast::PathSegment::args`). The latter case is more // common, so we implement `visit_generic_args` and tolerate the double diff --git a/tests/ui/stats/input-stats.stderr b/tests/ui/stats/input-stats.stderr index bbc7a874325b2..86b665f6d74d1 100644 --- a/tests/ui/stats/input-stats.stderr +++ b/tests/ui/stats/input-stats.stderr @@ -59,6 +59,8 @@ ast-stats - AngleBracketed 40 (NN.N%) 1 ast-stats Crate 40 (NN.N%) 1 40 ast-stats ---------------------------------------------------------------- ast-stats Total 6_624 109 +ast-stats ---------------------------------------------------------------- +ast-stats - Path Ident: 17 General segs 0: 0 1: 1 2: 2 3: 3 4+: 1 Span reconstructible: 7 ast-stats ================================================================ hir-stats ================================================================ hir-stats HIR STATS: input_stats @@ -121,4 +123,6 @@ hir-stats ImplItemId 8 (NN.N%) 2 4 hir-stats ForeignItemId 4 (NN.N%) 1 4 hir-stats ---------------------------------------------------------------- hir-stats Total 8_592 172 +hir-stats ---------------------------------------------------------------- +hir-stats - Path segments 0: 0 1: 20 2: 2 3: 3 4+: 1 hir-stats ================================================================ From dd03341bfed08fd285bd841bf3432445d30287b4 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 01:40:02 -0700 Subject: [PATCH 03/30] Avoid storing a span for Path values where we can reconstruct it from the segments If the span we would have stored matches the span from the first segment to the last, use a `Path::NoSpan` variant that just stores the segments. This works for the vast majority of Path values that otherwise used `Path::General`. We can then box the ones that remain `Path::General`, in order to keep `Path` 16 bytes and avoid growing it or the structures that contain it. --- compiler/rustc_ast/src/ast.rs | 111 ++++++++++++------ compiler/rustc_ast/src/attr/mod.rs | 2 +- compiler/rustc_ast_lowering/src/item.rs | 17 +-- compiler/rustc_ast_lowering/src/lib.rs | 2 +- compiler/rustc_expand/src/build.rs | 4 +- compiler/rustc_expand/src/placeholders.rs | 2 +- compiler/rustc_hir_pretty/src/lib.rs | 9 +- .../rustc_parse/src/parser/diagnostics.rs | 18 ++- compiler/rustc_parse/src/parser/item.rs | 2 +- compiler/rustc_parse/src/parser/path.rs | 17 +-- compiler/rustc_parse/src/parser/ty.rs | 8 +- compiler/rustc_passes/src/input_stats.rs | 15 +-- .../rustc_resolve/src/diagnostics/impls.rs | 12 +- .../rustc_resolve/src/late/diagnostics.rs | 7 +- tests/ui/stats/input-stats.stderr | 30 ++--- 15 files changed, 135 insertions(+), 121 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 6488a41371482..eeeb24d92787c 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -95,13 +95,15 @@ impl fmt::Display for Lifetime { pub enum Path { /// The common case of a single identifier (e.g. `x`) Ident { ident: Ident, id: NodeId }, - General { - /// The span of the whole path; might differ from the combined spans of the segments. - span: Span, - /// The segments in the path: the things separated by `::`. - /// Global paths begin with `kw::PathRoot`. + /// 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, }, + /// A fully general path where the path's span differs. Boxed to avoid making `Path` larger. + General(Box<(ThinVec, Span)>), } // Succeeds if the path has a single segment that is arg-free and matches the given symbol. @@ -123,7 +125,7 @@ impl PartialEq<&[Symbol]> for Path { }; ident.name == *name } - Path::General { segments, .. } => segments.iter().eq(*names), + Path::NoSpan { segments } | Path::General((segments, _)) => segments.iter().eq(*names), } } } @@ -142,11 +144,27 @@ impl Path { 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, 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::General { span, .. } => *span, + Path::NoSpan { segments } => segments_span(segments), + Path::General((_, span)) => *span, } } @@ -169,10 +187,11 @@ impl Path { #[inline] pub fn as_single_argless_ident(&self) -> Option { // 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. + // `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::General { segments, .. } => { + Path::NoSpan { segments } | Path::General((segments, _)) => { let [segment] = segments.as_ref() else { return None; }; @@ -191,25 +210,27 @@ impl Path { #[inline] pub fn is_empty(&self) -> bool { - let Path::General { segments, .. } = self else { - return false; - }; - segments.is_empty() + match self { + Path::General((segments, _)) => segments.is_empty(), + _ => false, + } } #[inline] pub fn num_segments(&self) -> usize { - let Path::General { segments, .. } = self else { - return 1; - }; - segments.len() + match self { + Path::Ident { .. } => 1, + Path::NoSpan { segments } | Path::General((segments, _)) => segments.len(), + } } #[inline] pub fn iter_idents(&self) -> impl DoubleEndedIterator + ExactSizeIterator { match self { Path::Ident { ident, .. } => Either::Left(iter::once(ident)), - Path::General { segments, .. } => Either::Right(segments.iter().map(|s| &s.ident)), + Path::NoSpan { segments } | Path::General((segments, _)) => { + Either::Right(segments.iter().map(|s| &s.ident)) + } } } @@ -221,7 +242,7 @@ impl Path { &Path::Ident { ref ident, id } => { Either::Left(iter::once(PathSegmentRef { ident, id, args: None })) } - Path::General { segments, .. } => { + Path::NoSpan { segments } | Path::General((segments, _)) => { Either::Right(segments.iter().map(PathSegment::as_ref)) } } @@ -231,7 +252,9 @@ impl Path { pub fn last_segment(&self) -> Option> { match self { &Path::Ident { ref ident, id } => Some(PathSegmentRef { ident, id, args: None }), - Path::General { segments, .. } => segments.last().map(PathSegment::as_ref), + Path::NoSpan { segments } | Path::General((segments, _)) => { + segments.last().map(PathSegment::as_ref) + } } } @@ -239,27 +262,37 @@ impl Path { pub fn last_ident(&self) -> Option { match self { &Path::Ident { ident, .. } => Some(ident), - Path::General { segments, .. } => segments.last().map(|s| s.ident), + Path::NoSpan { segments } | Path::General((segments, _)) => { + segments.last().map(|s| s.ident) + } } } pub fn force_general_mut(&mut self) -> (&mut ThinVec, &mut Span) { match self { - Path::General { segments, span } => (segments, span), + 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 { - segments: thin_vec![PathSegment { ident, id, args: None }], - span: ident.span, - }; - match self { - Path::Ident { .. } => unreachable!(), - Path::General { segments, span } => (segments, span), - } + *self = Path::General(Box::new(( + thin_vec![PathSegment { ident, id, args: None }], + ident.span, + ))); } } + match self { + Path::General((segments, span)) => (segments, span), + _ => unreachable!(), + } } } +fn segments_span(segments: &[PathSegment]) -> Span { + segments[0].ident.span.to(segments.last().unwrap().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". /// @@ -4584,8 +4617,8 @@ mod size_asserts { static_assert_size!(AttrKind, 16); static_assert_size!(Attribute, 32); static_assert_size!(Block, 24); - static_assert_size!(Expr, 72); - static_assert_size!(ExprKind, 40); + static_assert_size!(Expr, 64); + static_assert_size!(ExprKind, 32); static_assert_size!(FieldDef, 80); static_assert_size!(Fn, 192); static_assert_size!(FnDecl, 24); @@ -4595,7 +4628,7 @@ mod size_asserts { static_assert_size!(ForeignItemKind, 16); static_assert_size!(GenericArg, 24); static_assert_size!(GenericArgs, 40); - static_assert_size!(GenericBound, 88); + static_assert_size!(GenericBound, 80); static_assert_size!(GenericParam, 80); static_assert_size!(Generics, 40); static_assert_size!(Impl, 80); @@ -4604,19 +4637,19 @@ mod size_asserts { static_assert_size!(Lifetime, 16); static_assert_size!(LitKind, 24); static_assert_size!(Local, 96); - static_assert_size!(MetaItem, 88); + static_assert_size!(MetaItem, 80); static_assert_size!(MetaItemKind, 40); static_assert_size!(MetaItemLit, 40); - static_assert_size!(NormalAttr, 88); + static_assert_size!(NormalAttr, 80); static_assert_size!(Param, 40); - static_assert_size!(Pat, 72); - static_assert_size!(PatKind, 56); - static_assert_size!(Path, 24); + static_assert_size!(Pat, 64); + static_assert_size!(PatKind, 48); + static_assert_size!(Path, 16); static_assert_size!(PathSegment, 24); static_assert_size!(QSelf, 24); static_assert_size!(Stmt, 32); static_assert_size!(StmtKind, 16); - static_assert_size!(TraitImplHeader, 72); + static_assert_size!(TraitImplHeader, 64); static_assert_size!(Ty, 56); static_assert_size!(TyKind, 40); // tidy-alphabetical-end diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 9790d36464d42..e5c87ecda18be 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -500,7 +500,7 @@ impl MetaItem { iter.next(); } let span = span.with_hi(segments.last().unwrap().ident.span.hi()); - Path::General { span, segments } + Path::from_segments(segments, span) } Some(TokenTree::Delimited( _span, diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 7b480e7ca3a8a..89dd76fc9fdde 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -555,19 +555,12 @@ impl<'hir> LoweringContext<'_, 'hir> { // Correctly resolve `self` imports. let mut path = tree.prefix.clone(); - match &mut path { - Path::General { segments, .. } => { - if segments.len() > 1 - && segments.last().unwrap().ident.name == kw::SelfLower - { - let _ = segments.pop(); - if rename.is_none() { - ident = segments.last().unwrap().ident; - } - } + if path.num_segments() > 1 && path.last_ident().unwrap().name == kw::SelfLower { + let segments = path.force_general_mut().0; + let _ = segments.pop(); + if rename.is_none() { + ident = segments.last().unwrap().ident; } - // Can't end in `::self` - Path::Ident { .. } => {} } let res = self.lower_import_res(id, path.span()); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 22aa330af14c0..a3ec4fe4d5ec1 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -644,7 +644,7 @@ fn index_ast<'tcx>( // Lacking a better choice, we replace the contents with a macro call. // Unexpanded macros should never reach lowering, so this is not confusing. kind: dummy(Box::new(MacCall { - path: Path::General { span, segments: thin_vec![] }, + path: Path::from_segments(thin_vec![], span), args: Box::new(DelimArgs { dspan: DelimSpan::from_single(span), delim: Delimiter::Parenthesis, diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index cf79371a63152..f487cd441fac4 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -28,7 +28,7 @@ impl<'a> ExtCtxt<'a> { if ident.span == span { ast::Path::from_ident(ident) } else { - ast::Path::General { segments: thin_vec![ast::PathSegment::from_ident(ident)], span } + ast::Path::General(Box::new((thin_vec![ast::PathSegment::from_ident(ident)], span))) } } pub fn path_global(&self, span: Span, strs: Vec) -> ast::Path { @@ -68,7 +68,7 @@ impl<'a> ExtCtxt<'a> { id: ast::DUMMY_NODE_ID, args, }); - ast::Path::General { span, segments } + ast::Path::from_segments(segments, span) } pub fn macro_call( diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index e1293ed404806..21aacb085c5d3 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -16,7 +16,7 @@ pub(crate) fn placeholder( ) -> AstFragment { fn mac_placeholder() -> Box { Box::new(ast::MacCall { - path: ast::Path::General { span: DUMMY_SP, segments: ThinVec::new() }, + path: ast::Path::from_segments(ThinVec::new(), DUMMY_SP), args: Box::new(ast::DelimArgs { dspan: ast::tokenstream::DelimSpan::dummy(), delim: Delimiter::Parenthesis, diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index e2e405c54ab5a..4cfa66c676087 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -131,10 +131,8 @@ impl<'a> State<'a> { fn print_attr_item(&mut self, item: &AttrItem, span: Span) { let ib = self.ibox(0); - let path = ast::Path::General { - span, - segments: item - .path + let path = ast::Path::from_segments( + item.path .segments .iter() .map(|i| ast::PathSegment { @@ -143,7 +141,8 @@ impl<'a> State<'a> { id: DUMMY_NODE_ID, }) .collect(), - }; + span, + ); match &item.args { AttrArgs::Delimited(DelimArgs { dspan: _, delim, tokens }) => self.print_mac_common( diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 601b2a95c981f..f56b49893edcb 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -877,10 +877,7 @@ impl<'a> Parser<'a> { // } debug!(?maybe_struct_name, ?self.token); let mut snapshot = self.create_snapshot_for_diagnostic(); - let path = Path::General { - segments: ThinVec::new(), - span: self.prev_token.span.shrink_to_lo(), - }; + let path = Path::from_segments(ThinVec::new(), self.prev_token.span.shrink_to_lo()); let struct_expr = snapshot.parse_expr_struct(None, path, false); let block_tail = self.parse_block_tail(lo, s, AttemptLocalParseRecovery::No); return Some(match (struct_expr, block_tail) { @@ -1613,7 +1610,7 @@ impl<'a> Parser<'a> { let mut segments = ThinVec::new(); self.parse_path_segments(&mut segments, T::PATH_STYLE, None)?; - let path = ast::Path::General { segments, span: ty_span.to(self.prev_token.span) }; + let path = ast::Path::from_segments(segments, ty_span.to(self.prev_token.span)); self.dcx().emit_err(BadQPathStage2 { span: ty_span, @@ -2564,7 +2561,8 @@ impl<'a> Parser<'a> { 0, PathSegment { ident: *ident, id: *id, args: None }, ), - Path::General { segments: old_segments, .. } => { + Path::NoSpan { segments: old_segments } + | Path::General((old_segments, _)) => { std::mem::swap(segments, old_segments); segments.append(old_segments); } @@ -2581,13 +2579,13 @@ impl<'a> Parser<'a> { PatKind::Ident(_, old_ident, _) => { let path = PatKind::Path( None, - Path::General { - span: new_span, - segments: thin_vec![ + Path::from_segments( + thin_vec![ PathSegment::from_ident(*old_ident), PathSegment::from_ident(*ident), ], - }, + new_span, + ), ); first_pat = self.mk_pat(new_span, path); show_sugg = true; diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 743ec4303a169..f8473771c2886 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1349,7 +1349,7 @@ impl<'a> Parser<'a> { prefix = if self.eat_path_sep() { ast::Path::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt)) } else { - ast::Path::General { segments: ThinVec::new(), span: lo.shrink_to_lo() } + ast::Path::from_segments(ThinVec::new(), lo.shrink_to_lo()) }; self.parse_use_tree_glob_or_nested(use_token_span, use_path)? diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 7a2101a8cba52..5c068f43fe6ed 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -89,7 +89,7 @@ impl<'a> Parser<'a> { (segments, span) = path.force_general_mut(); *span = path_lo.to(self.prev_token.span); } else { - path = ast::Path::General { segments: ThinVec::new(), span: self.token.span }; + path = ast::Path::from_segments(ThinVec::new(), self.token.span); (segments, span) = path.force_general_mut(); } @@ -175,10 +175,8 @@ impl<'a> Parser<'a> { // // m!(inline); //~ ERROR: unexpected generic arguments in path // - if style == PathStyle::Mod - && let Path::General { ref mut segments, .. } = path - && segments.iter().any(|s| s.args.is_some()) - { + if style == PathStyle::Mod && path.iter_segments().any(|s| s.args.is_some()) { + let segments = path.force_general_mut().0; let span = segments .iter() .filter_map(|segment| segment.args.as_ref()) @@ -215,14 +213,7 @@ impl<'a> Parser<'a> { } self.parse_path_segments(&mut segments, style, ty_generics)?; let span = lo.to(self.prev_token.span); - if let [segment] = segments.as_slice() - && segment.args.is_none() - && segment.ident.span == span - { - Ok(Path::Ident { ident: segment.ident, id: segment.id }) - } else { - Ok(Path::General { segments, span }) - } + Ok(Path::from_segments(segments, span)) } pub(super) fn parse_path_segments( diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index c8279d68b0c8a..78e5c0804e5a9 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -1445,9 +1445,8 @@ impl<'a> Parser<'a> { match self.parse_fn_decl(&mode, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) { Ok(decl) => { self.dcx().emit_err(ExpectedFnPathFoundFnKeyword { fn_token_span }); - Some(ast::Path::General { - span: fn_token_span.to(self.prev_token.span), - segments: thin_vec![ast::PathSegment { + Some(ast::Path::from_segments( + thin_vec![ast::PathSegment { ident: Ident::new(sym::Fn, fn_token_span), id: DUMMY_NODE_ID, args: Some(Box::new(ast::GenericArgs::Parenthesized( @@ -1459,7 +1458,8 @@ impl<'a> Parser<'a> { } ))), }], - }) + fn_token_span.to(self.prev_token.span), + )) } Err(diag) => { diag.cancel(); diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index 80f4ff4ba3374..030e73f0ac0cf 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -65,8 +65,8 @@ struct StatCollector<'k> { #[derive(Default)] struct PathStats { idents: usize, + no_spans: usize, generals: [usize; 5], - span_reconstructible: usize, } pub fn print_hir_stats(tcx: TyCtxt<'_>) { @@ -205,14 +205,14 @@ impl<'k> StatCollector<'k> { if self.tcx.is_none() { _ = writeln!( s, - "{prefix} - Path Ident: {} General segs 0: {} 1: {} 2: {} 3: {} 4+: {} Span reconstructible: {}", + "{prefix} - Path Ident: {} Other segs 0: {} 1: {} 2: {} 3: {} 4+: {} NoSpan: {}", self.paths.idents, self.paths.generals[0], self.paths.generals[1], self.paths.generals[2], self.paths.generals[3], self.paths.generals[4], - self.paths.span_reconstructible, + self.paths.no_spans, ); } else { _ = writeln!( @@ -802,12 +802,13 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { fn visit_path(&mut self, path: &'v ast::Path) { match path { ast::Path::Ident { .. } => self.paths.idents += 1, - ast::Path::General { segments, span } => { + ast::Path::NoSpan { segments } | ast::Path::General((segments, _)) => { let len = segments.len().min(4); self.paths.generals[len] += 1; - let len = segments.len(); - if len > 0 && segments[0].span().to(segments[len - 1].span()) == *span { - self.paths.span_reconstructible += 1; + if let ast::Path::General(boxed) = path { + self.record("Path::General payload", None, boxed.as_ref()); + } else { + self.paths.no_spans += 1; } } } diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 6cea605671ea2..184b71c7c8fd1 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1756,7 +1756,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { segms.append(&mut path_segments.clone()); segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path::General { span: name_binding.span, segments: segms }; + let path = Path::from_segments(segms, name_binding.span); if child_accessible // Remove invisible match if exists @@ -2615,7 +2615,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { return; } - *path = Path::General { span: path.span(), segments: new_segments }; + *path = Path::from_segments(new_segments, path.span()); } fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) { @@ -2813,16 +2813,16 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module_path.map(|module_path| { // `import.module_path` is relative to the import's module, not to the // failing use site. - let mut path = Path::General { - span: ident.span, - segments: module_path + let mut path = Path::from_segments( + module_path .into_iter() .chain(std::iter::once(ident.name)) .map(|name| { ast::PathSegment::from_ident(Ident::with_dummy_span(name)) }) .collect(), - }; + ident.span, + ); self.shorten_import_path(res_def_id, &mut path, parent_scope.module); path.iter_idents().copied().collect() }) diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index a54572b7af0aa..ba82ba0b6d02b 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -3224,8 +3224,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let doc_visible = doc_visible && (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id)); if module_def_id == def_id { - let path = - Path::General { span: name_binding.span, segments: path_segments }; + let path = Path::from_segments(path_segments, name_binding.span); result = Some(( r.expect_module(module_def_id), ImportSuggestion { @@ -4657,7 +4656,7 @@ fn mk_where_bound_predicate( ty: &Ty, ) -> Option { let modified_segments = { - let Path::General { segments, .. } = path else { + let (Path::NoSpan { segments } | Path::General((segments, _))) = path else { return None; }; let mut segments = segments.clone(); @@ -4705,7 +4704,7 @@ fn mk_where_bound_predicate( bound_generic_params: ThinVec::new(), modifiers: ast::TraitBoundModifiers::NONE, trait_ref: ast::TraitRef { - path: ast::Path::General { segments: modified_segments, span: DUMMY_SP }, + path: ast::Path::from_segments(modified_segments, DUMMY_SP), ref_id: DUMMY_NODE_ID, }, span: DUMMY_SP, diff --git a/tests/ui/stats/input-stats.stderr b/tests/ui/stats/input-stats.stderr index 86b665f6d74d1..7e7b4034beacd 100644 --- a/tests/ui/stats/input-stats.stderr +++ b/tests/ui/stats/input-stats.stderr @@ -15,21 +15,21 @@ ast-stats - Ptr 56 (NN.N%) 1 ast-stats - Ref 56 (NN.N%) 1 ast-stats - ImplicitSelf 112 (NN.N%) 2 ast-stats - Path 560 (NN.N%) 10 -ast-stats Expr 648 (NN.N%) 9 72 -ast-stats - InlineAsm 72 (NN.N%) 1 -ast-stats - Match 72 (NN.N%) 1 -ast-stats - Path 72 (NN.N%) 1 -ast-stats - Struct 72 (NN.N%) 1 -ast-stats - Lit 144 (NN.N%) 2 -ast-stats - Block 216 (NN.N%) 3 -ast-stats Pat 504 (NN.N%) 7 72 -ast-stats - Struct 72 (NN.N%) 1 -ast-stats - Wild 72 (NN.N%) 1 -ast-stats - Ident 360 (NN.N%) 5 +ast-stats Expr 576 (NN.N%) 9 64 +ast-stats - InlineAsm 64 (NN.N%) 1 +ast-stats - Match 64 (NN.N%) 1 +ast-stats - Path 64 (NN.N%) 1 +ast-stats - Struct 64 (NN.N%) 1 +ast-stats - Lit 128 (NN.N%) 2 +ast-stats - Block 192 (NN.N%) 3 +ast-stats Pat 448 (NN.N%) 7 64 +ast-stats - Struct 64 (NN.N%) 1 +ast-stats - Wild 64 (NN.N%) 1 +ast-stats - Ident 320 (NN.N%) 5 ast-stats PathSegment 432 (NN.N%) 18 24 ast-stats GenericParam 400 (NN.N%) 5 80 -ast-stats GenericBound 352 (NN.N%) 4 88 -ast-stats - Trait 352 (NN.N%) 4 +ast-stats GenericBound 320 (NN.N%) 4 80 +ast-stats - Trait 320 (NN.N%) 4 ast-stats AssocItem 288 (NN.N%) 4 72 ast-stats - Fn 144 (NN.N%) 2 ast-stats - Type 144 (NN.N%) 2 @@ -58,9 +58,9 @@ ast-stats GenericArgs 40 (NN.N%) 1 40 ast-stats - AngleBracketed 40 (NN.N%) 1 ast-stats Crate 40 (NN.N%) 1 40 ast-stats ---------------------------------------------------------------- -ast-stats Total 6_624 109 +ast-stats Total 6_464 109 ast-stats ---------------------------------------------------------------- -ast-stats - Path Ident: 17 General segs 0: 0 1: 1 2: 2 3: 3 4+: 1 Span reconstructible: 7 +ast-stats - Path Ident: 17 Other segs 0: 0 1: 1 2: 2 3: 3 4+: 1 NoSpan: 7 ast-stats ================================================================ hir-stats ================================================================ hir-stats HIR STATS: input_stats From cd21804532c1c467b7156940c0c500709a7a6b47 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 11:50:45 -0700 Subject: [PATCH 04/30] rustc_ast_lowering: Cache computed path spans --- compiler/rustc_ast_lowering/src/path.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 8b4266f16105c..9df10b91afd6d 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -45,6 +45,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let partial_res = self.get_partial_res(id).unwrap_or_else(|| PartialRes::new(Res::Err)); let base_res = partial_res.base_res(); let unresolved_segments = partial_res.unresolved_segments(); + let span = p.span(); let mut res = self.lower_res(base_res); @@ -55,7 +56,7 @@ impl<'hir> LoweringContext<'_, 'hir> { if let Some(async_def_id) = self.map_trait_to_async_trait(def_id) { res = Res::Def(DefKind::Trait, async_def_id); } else { - self.dcx().emit_err(AsyncBoundOnlyForFnTraits { span: p.span() }); + self.dcx().emit_err(AsyncBoundOnlyForFnTraits { span }); } } Res::Err => { @@ -65,8 +66,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // This error isn't actually emitted AFAICT, but it's best to keep // it around in case the resolver doesn't always check the defkind // of an item or something. - self.dcx() - .emit_err(AsyncBoundNotOnTrait { span: p.span(), descr: res.descr() }); + self.dcx().emit_err(AsyncBoundNotOnTrait { span, descr: res.descr() }); } } } @@ -91,7 +91,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } }; - let path_span_lo = p.span().shrink_to_lo(); + let path_span_lo = span.shrink_to_lo(); let proj_start = p.num_segments() - unresolved_segments; let path = self.arena.alloc(hir::Path { res, @@ -133,7 +133,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; self.lower_path_segment( - p.span(), + span, segment, param_mode, generic_args_mode, @@ -198,7 +198,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; let hir_segment = self.arena.alloc(self.lower_path_segment( - p.span(), + span, segment, param_mode, generic_args_mode, @@ -220,7 +220,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // We should've returned in the for loop above. self.dcx().span_bug( - p.span(), + span, format!( "lower_qpath: no final extension segment in {}..{}", proj_start, @@ -236,11 +236,12 @@ impl<'hir> LoweringContext<'_, 'hir> { param_mode: ParamMode, ) -> &'hir hir::UsePath<'hir> { assert!(!res.is_empty()); + let path_span = p.span(); self.arena.alloc(hir::UsePath { res, segments: self.arena.alloc_from_iter(p.iter_segments().map(|segment| { self.lower_path_segment( - p.span(), + path_span, segment, param_mode, GenericArgsMode::Err, @@ -248,7 +249,7 @@ impl<'hir> LoweringContext<'_, 'hir> { None, ) })), - span: self.lower_span(p.span()), + span: self.lower_span(path_span), }) } From cb76f45c8179f9b9bc63b78c6c793069b48e1792 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 11:54:17 -0700 Subject: [PATCH 05/30] Inline `segments_span` --- compiler/rustc_ast/src/ast.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index eeeb24d92787c..f3adaf7b43b4a 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -289,6 +289,7 @@ impl Path { } } +#[inline] fn segments_span(segments: &[PathSegment]) -> Span { segments[0].ident.span.to(segments.last().unwrap().span()) } From c0b3c9e639dd3c005c3507208a345365b2104c61 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 12:04:38 -0700 Subject: [PATCH 06/30] rustc_ast_lowering: Cache spans in restriction lowering --- compiler/rustc_ast_lowering/src/item.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 89dd76fc9fdde..ab18c772781ca 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1676,14 +1676,12 @@ impl<'hir> LoweringContext<'_, 'hir> { RestrictionKind::Restricted { path, id, shorthand: _ } => { let res = self.get_partial_res(*id); let parent_module = self.tcx.parent_module(hir_id); + let span = path.span(); if let Some(did) = res.and_then(|res| res.expect_full_res().opt_def_id()) { if !self.tcx.is_descendant_of(parent_module, did) { // If the restriction path is not an ancestor of the item, // emit an error and recover by lowering the restriction to `Unrestricted`. - self.dcx().emit_err(RestrictionAncestorOnly { - span: path.span(), - kind: resolving_kind, - }); + self.dcx().emit_err(RestrictionAncestorOnly { span, kind: resolving_kind }); hir::RestrictionKind::Unrestricted } else { hir::RestrictionKind::Restricted(self.arena.alloc(hir::Path { @@ -1691,7 +1689,7 @@ impl<'hir> LoweringContext<'_, 'hir> { segments: self.arena.alloc_from_iter(path.iter_segments().map( |segment| { self.lower_path_segment( - path.span(), + span, segment, ParamMode::Explicit, GenericArgsMode::Err, @@ -1700,11 +1698,11 @@ impl<'hir> LoweringContext<'_, 'hir> { ) }, )), - span: self.lower_span(path.span()), + span: self.lower_span(span), })) } } else { - self.dcx().span_delayed_bug(path.span(), "should have errored in resolve"); + self.dcx().span_delayed_bug(span, "should have errored in resolve"); hir::RestrictionKind::Unrestricted } } From ab3bb3f10bcc612b552ed67045b0d6e86bcdaf47 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 12:12:51 -0700 Subject: [PATCH 07/30] rustc_ast_lowering: Defer calling `.span()` for an error path until actually erroring lower_import_res wanted the Span but only used it to report an error; pass in the &Path instead and defer calling `.span()`. --- compiler/rustc_ast_lowering/src/item.rs | 2 +- compiler/rustc_ast_lowering/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index ab18c772781ca..d69775391ecbb 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -563,7 +563,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - let res = self.lower_import_res(id, path.span()); + let res = self.lower_import_res(id, &path); let path = self.lower_use_path(res, &path, ParamMode::Explicit); let ident = self.lower_ident(ident); hir::UseTree { prefix: path, kind: hir::UseKind::Single(ident) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index a3ec4fe4d5ec1..66ab771053019 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -943,7 +943,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.get_partial_res(id).map_or(Res::Err, |pr| pr.expect_full_res()) } - fn lower_import_res(&self, id: NodeId, span: Span) -> PerNS> { + fn lower_import_res(&self, id: NodeId, path: &ast::Path) -> PerNS> { let per_ns = self .curr_owner .owner @@ -962,7 +962,7 @@ impl<'hir> LoweringContext<'_, 'hir> { if per_ns.is_empty() { // Propagate the error to all namespaces, just to be sure. - self.dcx().span_delayed_bug(span, "no resolution for an import"); + self.dcx().span_delayed_bug(path.span(), "no resolution for an import"); let err = Some(Res::Err); return PerNS { type_ns: err, value_ns: err, macro_ns: err }; } From 230042e49fc676f63e1df999cce4d2529e36564e Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 12:21:42 -0700 Subject: [PATCH 08/30] rustc_resolve: Don't recompute `use_tree.span()` in a loop --- compiler/rustc_resolve/src/build_reduced_graph.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 57e687893fd11..f039c575ca4eb 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -759,9 +759,10 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { } } ast::UseTreeKind::Nested { ref items, .. } => { + let use_tree_span = use_tree.span(); for tree in items { let id = tree.id; - let feed = self.create_def(id, None, DefKind::Use, use_tree.span()); + let feed = self.create_def(id, None, DefKind::Use, use_tree_span); self.build_reduced_graph_for_use_tree( item, &tree.inner, From b3e12092fbed011829782035058f702d6653a5b8 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 12:23:28 -0700 Subject: [PATCH 09/30] `UseTree::span()`: Add a fast path for `UseTreeKind::Simple(None)` We don't need to call `self.hi_span()` in this case because we know it'll be `self.prefix.span()`. --- compiler/rustc_ast/src/ast.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index f3adaf7b43b4a..50e48534aef1f 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3506,7 +3506,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 From 30a9c8232b73ea421419a239849ad68a999c8781 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 12:30:00 -0700 Subject: [PATCH 10/30] `report_path_args`: Defer error span computation until actually erroring --- compiler/rustc_builtin_macros/src/derive.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index 9fff156e503d5..edd518fa519c9 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -139,15 +139,15 @@ fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) { } fn report_path_args(sess: &Session, meta: &ast::MetaItem) { - let span = meta.span.with_lo(meta.path.span().hi()); + let span = || meta.span.with_lo(meta.path.span().hi()); match meta.kind { MetaItemKind::Word => {} MetaItemKind::List(..) => { - sess.dcx().emit_err(diagnostics::DerivePathArgsList { span }); + sess.dcx().emit_err(diagnostics::DerivePathArgsList { span: span() }); } MetaItemKind::NameValue(..) => { - sess.dcx().emit_err(diagnostics::DerivePathArgsValue { span }); + sess.dcx().emit_err(diagnostics::DerivePathArgsValue { span: span() }); } } } From f16bcbc803cc879c0dacbaca92d3681d1c97c640 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 12:31:14 -0700 Subject: [PATCH 11/30] `check_stability_and_deprecation`: Defer error span computation until actually erroring --- compiler/rustc_resolve/src/macros.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 434bb24753bf4..cca6db7ad0483 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -1083,11 +1083,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path: &ast::Path, node_id: NodeId, ) { - let span = path.span(); if let Some(stability) = &ext.stability && let StabilityLevel::Unstable { reason, issue, implied_by, .. } = stability.level { let feature = stability.feature; + let span = path.span(); let is_allowed = |feature| self.features.enabled(feature) || span.allows_unstable(feature); @@ -1104,6 +1104,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } if let Some(depr) = &ext.deprecation { + let span = path.span(); let path = pprust::path_to_string(path); stability::early_report_macro_deprecation( &mut self.lint_buffer, From bec00984b2b643624d9dc6451aef7abef7e7d93c Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 12:38:09 -0700 Subject: [PATCH 12/30] `MetaItem::from_tokens`: Avoid redundant span calculations Avoid doing `path.span().with_hi(path.span().hi())` along some paths. --- compiler/rustc_ast/src/attr/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index e5c87ecda18be..262b67ade8f93 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -520,12 +520,13 @@ impl MetaItem { }; let list_closing_paren_pos = iter.peek().map(|tt| tt.span().hi()); let kind = MetaItemKind::from_tokens(iter)?; - let hi = match &kind { - MetaItemKind::NameValue(lit) => lit.span.hi(), - MetaItemKind::List(..) => list_closing_paren_pos.unwrap_or(path.span().hi()), - _ => path.span().hi(), + let span = match &kind { + MetaItemKind::NameValue(lit) => path.span().with_hi(lit.span.hi()), + MetaItemKind::List(..) if let Some(list_closing_paren_pos) = list_closing_paren_pos => { + path.span().with_hi(list_closing_paren_pos) + } + _ => path.span(), }; - let span = path.span().with_hi(hi); // FIXME: This parses `unsafe()` not as unsafe attribute syntax in `MetaItem`, // but as a parenthesized list. This (and likely `MetaItem`) should be changed in // such a way that builtin macros don't accept extraneous `unsafe()`. From ec9c4a6bd1eda4d2d060aabf06ef03ef33e51d2b Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 18:38:03 -0700 Subject: [PATCH 13/30] Inline `Segment::from` This provides a non-trivial performance improvement. --- compiler/rustc_resolve/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 0274e4d47218f..98578428a6edf 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -393,6 +393,7 @@ impl Segment { } impl<'a> From> for Segment { + #[inline] fn from(seg: ast::PathSegmentRef<'a>) -> Segment { let has_generic_args = seg.args.is_some(); let (args_span, has_lifetime_args) = if let Some(args) = seg.args { From 5a90626c7990ab020d2b98d0cf559b3a40ef2b85 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 20:18:08 -0700 Subject: [PATCH 14/30] Add fast paths to `Segment::from_path` `Segment::from_path` is on hot paths, so give it a fast path for `Path::Ident`. --- compiler/rustc_resolve/src/lib.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 98578428a6edf..38a25fc21e6b3 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -374,7 +374,18 @@ struct Segment { impl Segment { fn from_path(path: &Path) -> Vec { - path.iter_segments().map(Segment::from).collect() + match path { + &Path::Ident { ident, id } => vec![Segment { + ident, + id: Some(id), + has_generic_args: false, + has_lifetime_args: false, + args_span: DUMMY_SP, + }], + Path::NoSpan { segments } | Path::General((segments, _)) => { + segments.iter().map(|s| s.as_ref().into()).collect() + } + } } fn from_ident(ident: Ident) -> Segment { From b66ab4e122fdff159a4b819c2095f269d7e68f09 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 20:36:13 -0700 Subject: [PATCH 15/30] Remove redundant clones of attribute paths --- compiler/rustc_attr_parsing/src/interface.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 303df4e4e07a5..0fdef1be414c8 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -436,7 +436,7 @@ impl<'sess> AttributeParser<'sess> { parsed_description: ParsedDescription::Attribute, template: &accept.template, attr_safety: n.item.unsafety, - attr_path: attr_path.clone(), + attr_path, #[cfg(debug_assertions)] has_target_been_checked: false, }; @@ -456,7 +456,7 @@ impl<'sess> AttributeParser<'sess> { ); } else { let attr = AttrItem { - path: attr_path.clone(), + path: attr_path, args: self.lower_attr_args(&n.item.args, lower_span), id: HashIgnoredAttrId { attr_id: attr.id }, style: attr.style, @@ -464,7 +464,7 @@ impl<'sess> AttributeParser<'sess> { }; self.check_attribute_safety( - &attr_path, + &attr.path, inner_span, n.item.unsafety, AttributeSafety::Normal, From 0e50314ac0dfc9310e35300fdae0193c5de364b2 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 20:53:04 -0700 Subject: [PATCH 16/30] Remove redundant collections of attribute paths AttrPath::from_ast collects idents into a boxed slice, so we don't need to do the same in callers of it; we can just borrow from the AttrPath. --- compiler/rustc_attr_parsing/src/interface.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 0fdef1be414c8..b5d74c93c88e1 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -185,12 +185,10 @@ impl<'sess> AttributeParser<'sess> { expected_safety: AttributeSafety, ) -> Option { let attr_item = attr.get_normal_item(); - let parts = attr_item.path.iter_idents().map(|i| i.name).collect::>(); - let path = AttrPath::from_ast(&attr_item.path, identity); let args = ArgParser::from_attr_args( &attr_item.args, - &parts, + path.segments.as_ref(), &sess.psess, emit_errors, allow_expr_metavar, @@ -364,10 +362,10 @@ impl<'sess> AttributeParser<'sess> { ast::AttrKind::Normal(n) => { attr_paths.push(PathParser(&n.item.path)); let attr_path = AttrPath::from_ast(&n.item.path, lower_span); - let parts = n.item.path.iter_idents().map(|i| i.name).collect::>(); + let parts = attr_path.segments.as_ref(); let inner_span = lower_span(n.item.span); - if let Some(accept) = ATTRIBUTE_PARSERS.accepters.get(parts.as_slice()) { + if let Some(accept) = ATTRIBUTE_PARSERS.accepters.get(parts) { self.check_attribute_safety( &attr_path, inner_span, @@ -376,13 +374,13 @@ impl<'sess> AttributeParser<'sess> { &mut emit_lint, ); self.check_attribute_stability(&attr_path, attr_span, accept.stability); - if let [part] = parts.as_slice() { + if let [part] = parts { debug_assert!(BUILTIN_ATTRIBUTE_SET.contains(part)); } let Some(args) = ArgParser::from_attr_args( &n.item.args, - &parts, + parts, &self.sess.psess, self.should_emit, AllowExprMetavar::No, @@ -449,7 +447,7 @@ impl<'sess> AttributeParser<'sess> { if !cx.shared.has_lint_been_emitted.load(Ordering::Relaxed) { cx.shared.cx.check_args_used(attr, &args) } - } else if let [sym::diagnostic, _unknown, ..] = &*parts { + } else if let [sym::diagnostic, _unknown, ..] = parts { self.unknown_diagnostic_attr( n.item.path.iter_segments().nth(1).unwrap(), &mut emit_lint, From 2e0dc1928ce6b848ef0a816a99d26c1bcaa51976 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 22:10:35 -0700 Subject: [PATCH 17/30] rustc_ast: Optimize `segments_span` and inline `.span()` helpers `segments_span` was calling `PathSegment::span()`, which combined two spans, but `segments_span` only needs the last one to call `.to` on. Inline `PathSegment::span()` and `PathSegmentRef::span()`, which helps various hot paths. --- compiler/rustc_ast/src/ast.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 50e48534aef1f..22c30f82e71b9 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -291,7 +291,12 @@ impl Path { #[inline] fn segments_span(segments: &[PathSegment]) -> Span { - segments[0].ident.span.to(segments.last().unwrap().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 @@ -383,6 +388,7 @@ 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()), @@ -403,6 +409,7 @@ pub struct PathSegmentRef<'a> { } impl PathSegmentRef<'_> { + #[inline] pub fn span(&self) -> Span { match self.args { Some(args) => self.ident.span.to(args.span()), From 2bc0ff851861c65570c0571f146713f0809aafb9 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 23:16:57 -0700 Subject: [PATCH 18/30] Use a `Path::Ident` for macro placeholder paths (which are never read) Macro placeholders were allocating an empty `ThinVec` and a `Path::General` with a `Box` to hold the empty `ThinVec`. Use `Path::Ident` with `Ident::dummy()` instead, to avoid these allocations. --- compiler/rustc_expand/src/placeholders.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 21aacb085c5d3..0d687db3bc41b 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -5,7 +5,6 @@ use rustc_ast::{self as ast}; use rustc_data_structures::fx::FxHashMap; use rustc_span::{DUMMY_SP, Ident}; use smallvec::{SmallVec, smallvec}; -use thin_vec::ThinVec; use crate::expand::{AstFragment, AstFragmentKind}; @@ -16,7 +15,7 @@ pub(crate) fn placeholder( ) -> AstFragment { fn mac_placeholder() -> Box { Box::new(ast::MacCall { - path: ast::Path::from_segments(ThinVec::new(), DUMMY_SP), + path: ast::Path::from_ident(Ident::dummy()), args: Box::new(ast::DelimArgs { dspan: ast::tokenstream::DelimSpan::dummy(), delim: Delimiter::Parenthesis, From cd09dcb7f9ab25258f86e169b0b2b2c466ae8cff Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 4 Oct 2026 23:44:32 -0700 Subject: [PATCH 19/30] Optimize `MacCall::span` to avoid combining spans repeatedly `MacCall::span` called `self.path.span().to(...)`, but only really needs the start of the path span. Use the initial ident's span as the starting point, instead. This is the same kind of optimization paths already do; path spans always used only the start and end tokens to compute the span, and this likewise uses the start of the path and the macro args to compute the span of the macro call. --- compiler/rustc_ast/src/ast.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 22c30f82e71b9..1f4965c292ab0 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2230,7 +2230,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()) } } From 83758aadb3d9c20b1279098108e25eb9850da292 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 00:42:59 -0700 Subject: [PATCH 20/30] parse_path_inner: Add a fast path for common cases to avoid reconstructing a span In the common cases for `Path::Ident` and `Path::NoSpan`, we can avoid locally combining our token spans and passing that to `from_segments` (which will in turn combine the token spans for comparison). We can compare the endpoints of those spans instead. It's possible to make this fast path completely comprehensive for a very marginally larger performance win, but that would make the fast path twice as long and more complicated, duplicating the whole contents of `Path::from_segments`. Instead, we delegate to `Path::from_segments` the unusual cases where the endpoints don't match but the combined spans would. --- compiler/rustc_parse/src/parser/path.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 5c068f43fe6ed..3e7ccf31932ee 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -212,8 +212,23 @@ impl<'a> Parser<'a> { segments.push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt))); } self.parse_path_segments(&mut segments, style, ty_generics)?; - let span = lo.to(self.prev_token.span); - Ok(Path::from_segments(segments, span)) + // Fast-paths to avoid redundant span construction in `Path::from_segments`, and to avoid + // the cost of combining spans too early. + let hi = self.prev_token.span; + if let [segment] = segments.as_slice() + && segment.args.is_none() + && segment.ident.span == lo + && lo == hi + { + return Ok(Path::Ident { ident: segment.ident, id: segment.id }); + } + let first_span = segments[0].ident.span; + let last = segments.last().unwrap(); + let last_span = last.args.as_deref().map(|a| a.span()).unwrap_or(last.ident.span); + if first_span == lo && last_span == hi { + return Ok(Path::NoSpan { segments }); + } + Ok(Path::from_segments(segments, lo.to(hi))) } pub(super) fn parse_path_segments( From 7351815b680ca4bed910242d3747d1c9a18dc870 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 01:06:12 -0700 Subject: [PATCH 21/30] lower_qpath: Only call `p.num_segments()` once --- compiler/rustc_ast_lowering/src/path.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 9df10b91afd6d..c1ca5fc08ccc9 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -46,6 +46,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let base_res = partial_res.base_res(); let unresolved_segments = partial_res.unresolved_segments(); let span = p.span(); + let p_num_segments = p.num_segments(); let mut res = self.lower_res(base_res); @@ -84,7 +85,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // Only permit `impl Trait` in the final segment. E.g., we permit `Option`, // `option::Option::Xyz` and reject `option::Option::Xyz`. let itctx = |i| { - if i + 1 == p.num_segments() { + if i + 1 == p_num_segments { itctx } else { ImplTraitContext::Disallowed(ImplTraitPosition::Path) @@ -92,7 +93,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; let path_span_lo = span.shrink_to_lo(); - let proj_start = p.num_segments() - unresolved_segments; + let proj_start = p_num_segments - unresolved_segments; let path = self.arena.alloc(hir::Path { res, segments: self.arena.alloc_from_iter( @@ -189,7 +190,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // * final path is `<<>::IntoIter>::Item>::clone` for (i, segment) in p.iter_segments().enumerate().skip(proj_start) { // If this is a type-dependent `T::method(..)`. - let generic_args_mode = if i + 1 == p.num_segments() + let generic_args_mode = if i + 1 == p_num_segments && matches!(allow_return_type_notation, AllowReturnTypeNotation::Yes) { GenericArgsMode::ReturnTypeNotation @@ -208,7 +209,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let qpath = hir::QPath::TypeRelative(ty, hir_segment); // It's finished, return the extension of the right node type. - if i == p.num_segments() - 1 { + if i == p_num_segments - 1 { return qpath; } @@ -223,8 +224,7 @@ impl<'hir> LoweringContext<'_, 'hir> { span, format!( "lower_qpath: no final extension segment in {}..{}", - proj_start, - p.num_segments() + proj_start, p_num_segments ), ); } From b9e395e87ade806cfcb46e5615aff7787dc32cf6 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 01:07:21 -0700 Subject: [PATCH 22/30] lower_qpath: Refactor to move loop computing `segments` earlier This will allow having the same loop save the needed span for the subsequent call. --- compiler/rustc_ast_lowering/src/path.rs | 95 +++++++++++++------------ 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index c1ca5fc08ccc9..92eb917bf03a5 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -94,55 +94,56 @@ impl<'hir> LoweringContext<'_, 'hir> { let path_span_lo = span.shrink_to_lo(); let proj_start = p_num_segments - unresolved_segments; - let path = self.arena.alloc(hir::Path { - res, - segments: self.arena.alloc_from_iter( - p.iter_segments().take(proj_start).enumerate().map(|(i, segment)| { - let param_mode = match (qself_position, param_mode) { - (Some(j), ParamMode::Optional) if i < j => { - // This segment is part of the trait path in a - // qualified path - one of `a`, `b` or `Trait` - // in `::T::U::method`. - ParamMode::Explicit - } - _ => param_mode, - }; + let segments = self.arena.alloc_from_iter( + p.iter_segments().take(proj_start).enumerate().map(|(i, segment)| { + let param_mode = match (qself_position, param_mode) { + (Some(j), ParamMode::Optional) if i < j => { + // This segment is part of the trait path in a + // qualified path - one of `a`, `b` or `Trait` + // in `::T::U::method`. + ParamMode::Explicit + } + _ => param_mode, + }; - let generic_args_mode = match base_res { - // `a::b::Trait(Args)` - Res::Def(DefKind::Trait, _) if i + 1 == proj_start => { - GenericArgsMode::ParenSugar - } - // `a::b::Trait(Args)::TraitItem` - Res::Def(DefKind::AssocFn, _) - | Res::Def(DefKind::AssocConst, _) - | Res::Def(DefKind::AssocTy, _) - if i + 2 == proj_start => - { - GenericArgsMode::ParenSugar - } - Res::Def(DefKind::AssocFn, _) if i + 1 == proj_start => { - match allow_return_type_notation { - AllowReturnTypeNotation::Yes => GenericArgsMode::ReturnTypeNotation, - AllowReturnTypeNotation::No => GenericArgsMode::Err, - } + let generic_args_mode = match base_res { + // `a::b::Trait(Args)` + Res::Def(DefKind::Trait, _) if i + 1 == proj_start => { + GenericArgsMode::ParenSugar + } + // `a::b::Trait(Args)::TraitItem` + Res::Def(DefKind::AssocFn, _) + | Res::Def(DefKind::AssocConst, _) + | Res::Def(DefKind::AssocTy, _) + if i + 2 == proj_start => + { + GenericArgsMode::ParenSugar + } + Res::Def(DefKind::AssocFn, _) if i + 1 == proj_start => { + match allow_return_type_notation { + AllowReturnTypeNotation::Yes => GenericArgsMode::ReturnTypeNotation, + AllowReturnTypeNotation::No => GenericArgsMode::Err, } - // Avoid duplicated errors. - Res::Err => GenericArgsMode::Silence, - // An error - _ => GenericArgsMode::Err, - }; - - self.lower_path_segment( - span, - segment, - param_mode, - generic_args_mode, - itctx(i), - bound_modifier_allowed_features.clone(), - ) - }), - ), + } + // Avoid duplicated errors. + Res::Err => GenericArgsMode::Silence, + // An error + _ => GenericArgsMode::Err, + }; + + self.lower_path_segment( + span, + segment, + param_mode, + generic_args_mode, + itctx(i), + bound_modifier_allowed_features.clone(), + ) + }), + ); + let path = self.arena.alloc(hir::Path { + res, + segments, span: self.lower_span( p.iter_segments() .take(proj_start) From a2bd244ccf91b2f13bd30e191d3a2d4af30214a6 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 01:09:59 -0700 Subject: [PATCH 23/30] lower_qpath: Compute the span when we encounter the last prefix segment This avoids separately accessing that segment afterwards to compute the span. --- compiler/rustc_ast_lowering/src/path.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 92eb917bf03a5..70a94c7dd3a10 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -94,6 +94,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let path_span_lo = span.shrink_to_lo(); let proj_start = p_num_segments - unresolved_segments; + let mut last_prefix_span = path_span_lo; let segments = self.arena.alloc_from_iter( p.iter_segments().take(proj_start).enumerate().map(|(i, segment)| { let param_mode = match (qself_position, param_mode) { @@ -131,6 +132,10 @@ impl<'hir> LoweringContext<'_, 'hir> { _ => GenericArgsMode::Err, }; + if i + 1 == proj_start { + last_prefix_span = path_span_lo.to(segment.span()); + } + self.lower_path_segment( span, segment, @@ -141,16 +146,8 @@ impl<'hir> LoweringContext<'_, 'hir> { ) }), ); - let path = self.arena.alloc(hir::Path { - res, - segments, - span: self.lower_span( - p.iter_segments() - .take(proj_start) - .next_back() - .map_or(path_span_lo, |segment| path_span_lo.to(segment.span())), - ), - }); + let path = + self.arena.alloc(hir::Path { res, segments, span: self.lower_span(last_prefix_span) }); if let Some(bound_modifier_allowed_features) = bound_modifier_allowed_features { path.span = self.mark_span_with_reason( From fde26612c6dbeaf00ccdba92e0d9017ee693cdc0 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 01:40:35 -0700 Subject: [PATCH 24/30] lower_qpath: Reuse the segment iterator rather than recreating it --- compiler/rustc_ast_lowering/src/path.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 70a94c7dd3a10..f7d89fce0a6ac 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -95,8 +95,9 @@ impl<'hir> LoweringContext<'_, 'hir> { let path_span_lo = span.shrink_to_lo(); let proj_start = p_num_segments - unresolved_segments; let mut last_prefix_span = path_span_lo; - let segments = self.arena.alloc_from_iter( - p.iter_segments().take(proj_start).enumerate().map(|(i, segment)| { + let mut segment_iter = p.iter_segments().enumerate(); + let segments = self.arena.alloc_from_iter(segment_iter.by_ref().take(proj_start).map( + |(i, segment)| { let param_mode = match (qself_position, param_mode) { (Some(j), ParamMode::Optional) if i < j => { // This segment is part of the trait path in a @@ -144,8 +145,8 @@ impl<'hir> LoweringContext<'_, 'hir> { itctx(i), bound_modifier_allowed_features.clone(), ) - }), - ); + }, + )); let path = self.arena.alloc(hir::Path { res, segments, span: self.lower_span(last_prefix_span) }); @@ -186,7 +187,9 @@ impl<'hir> LoweringContext<'_, 'hir> { // 2. `>::IntoIter` // 3. `<>::IntoIter>::Item` // * final path is `<<>::IntoIter>::Item>::clone` - for (i, segment) in p.iter_segments().enumerate().skip(proj_start) { + // + // We've already consumed the prefix from `segment_iter`, so we can resume it here. + for (i, segment) in segment_iter { // If this is a type-dependent `T::method(..)`. let generic_args_mode = if i + 1 == p_num_segments && matches!(allow_return_type_notation, AllowReturnTypeNotation::Yes) From 2c9b53f7d878ee47ab0f97a570acfe8bc4bbc8ec Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 04:09:03 -0700 Subject: [PATCH 25/30] Fix clippy for changes to Path --- .../src/attrs/deprecated_cfg_attr.rs | 9 +--- .../src/attrs/mixed_attributes_style.rs | 8 +--- .../src/attrs/unnecessary_clippy_cfg.rs | 4 +- .../clippy/clippy_lints/src/attrs/utils.rs | 9 ++-- .../clippy/clippy_lints/src/collapsible_if.rs | 7 +-- .../clippy_lints/src/crate_in_macro_def.rs | 10 +--- .../src/field_scoped_visibility_modifiers.rs | 4 +- .../clippy/clippy_lints/src/format_args.rs | 9 ++-- .../src/misc_early/unneeded_field_pattern.rs | 7 +-- .../src/needless_arbitrary_self_type.rs | 6 ++- .../src/nonstandard_macro_braces.rs | 2 +- .../clippy_lints/src/redundant_field_names.rs | 5 +- .../src/returns/needless_return.rs | 7 +-- .../src/single_component_path_imports.rs | 46 +++++++++++-------- .../src/unnecessary_self_imports.rs | 7 +-- .../src/unsafe_removed_from_name.rs | 7 +-- .../clippy/clippy_lints/src/visibility.rs | 8 ++-- .../clippy/clippy_utils/src/ast_utils/mod.rs | 9 +++- .../clippy_utils/src/check_proc_macro.rs | 14 +++--- 19 files changed, 85 insertions(+), 93 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs b/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs index d67a194b02062..4cacbbc862d5f 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs @@ -19,14 +19,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &MsrvStack) { // check for `rustfmt_skip` and `rustfmt::skip` && let Some(skip_item) = &items[1].meta_item() && (skip_item.has_name(sym::rustfmt_skip) - || skip_item - .path - .segments - .last() - .expect("empty path in attribute") - .ident - .name - == sym::skip) + || skip_item.path.last_ident().expect("empty path in attribute").name == sym::skip) // Only lint outer attributes, because custom inner attributes are unstable // Tracking issue: https://github.com/rust-lang/rust/issues/54726 && attr.style == AttrStyle::Outer diff --git a/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs b/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs index c9cbf29f2b670..22d5d8a293e9c 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs @@ -20,13 +20,7 @@ impl From<&AttrKind> for SimpleAttrKind { fn from(value: &AttrKind) -> Self { match value { AttrKind::Normal(attr) => { - let path_symbols = attr - .item - .path - .segments - .iter() - .map(|seg| seg.ident.name) - .collect::>(); + let path_symbols = attr.item.path.iter_idents().map(|i| i.name).collect::>(); Self::Normal(path_symbols) }, AttrKind::Synthetic(synthetic) => match &**synthetic { diff --git a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs index 9e9fbb2c52582..1683dec2336d2 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs @@ -22,8 +22,8 @@ pub(super) fn check( let mut clippy_lints = Vec::with_capacity(items.len()); for item in items { if let Some(meta_item) = item.meta_item() - && let [part1, _] = meta_item.path.segments.as_slice() - && part1.ident.name == sym::clippy + && let Some((part1_ident, _)) = meta_item.path.iter_idents().collect_tuple() + && part1_ident.name == sym::clippy { clippy_lints.push(item.span()); } diff --git a/src/tools/clippy/clippy_lints/src/attrs/utils.rs b/src/tools/clippy/clippy_lints/src/attrs/utils.rs index 8305754028f10..cd6ab3c1af29d 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/utils.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/utils.rs @@ -53,9 +53,12 @@ pub(super) fn extract_clippy_lint(lint: &MetaItemInner) -> Option { /// Returns the lint namespace, if any, as well as the lint name. (`None`, `None`) means /// the lint had less than 1 or more than 2 segments. pub(super) fn namespace_and_lint(lint: &MetaItemInner) -> (Option, Option) { - match lint.meta_item().map_or_default(|m| m.path.segments.as_slice()) { - [name] => (None, Some(name.ident.name)), - [namespace, name] => (Some(namespace.ident.name), Some(name.ident.name)), + let Some(mut idents) = lint.meta_item().map(|m| m.path.iter_idents()) else { + return (None, None); + }; + match (idents.next(), idents.next(), idents.next()) { + (Some(name), None, None) => (None, Some(name.name)), + (Some(namespace), Some(name), None) => (Some(namespace.name), Some(name.name)), _ => (None, None), } } diff --git a/src/tools/clippy/clippy_lints/src/collapsible_if.rs b/src/tools/clippy/clippy_lints/src/collapsible_if.rs index 10814cdb4c585..71e314c1b5175 100644 --- a/src/tools/clippy/clippy_lints/src/collapsible_if.rs +++ b/src/tools/clippy/clippy_lints/src/collapsible_if.rs @@ -231,6 +231,7 @@ impl CollapsibleIf { inner_if_expr: &Expr<'_>, expected_lint_name: Symbol, ) -> bool { + use itertools::Itertools; match cx.tcx.hir_attrs(inner_if_expr.hir_id) { [] => { // There aren't any attributes, so just check for significant tokens @@ -242,9 +243,9 @@ impl CollapsibleIf { if matches!(Level::from_opt_symbol(attr.name()), Some(Level::Expect)) && let Some(metas) = attr.meta_item_list() && let Some(MetaItemInner::MetaItem(meta_item)) = metas.first() - && let [tool, lint_name] = meta_item.path.segments.as_slice() - && tool.ident.name == sym::clippy - && [expected_lint_name, sym::style, sym::all].contains(&lint_name.ident.name) => + && let Some((tool, lint_name)) = meta_item.path.iter_idents().collect_tuple() + && tool.name == sym::clippy + && [expected_lint_name, sym::style, sym::all].contains(&lint_name.name) => { // There is an `expect` attribute -- check that there is no _other_ significant text let span_before_attr = inner_if.span.split_at(1).1.until(attr.span()); diff --git a/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs b/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs index ddd666477612a..740f0d4187aaf 100644 --- a/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs +++ b/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind}; +use rustc_ast::ast::{Attribute, Item, ItemKind}; use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_errors::Applicability; @@ -71,13 +71,7 @@ impl EarlyLintPass for CrateInMacroDef { } fn is_macro_export(attr: &Attribute) -> bool { - if let AttrKind::Normal(normal) = &attr.kind - && let [segment] = normal.item.path.segments.as_slice() - { - segment.ident.name == sym::macro_export - } else { - false - } + attr.has_name(sym::macro_export) } fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option { diff --git a/src/tools/clippy/clippy_lints/src/field_scoped_visibility_modifiers.rs b/src/tools/clippy/clippy_lints/src/field_scoped_visibility_modifiers.rs index 20a7ee9d28941..3e05bd5086aad 100644 --- a/src/tools/clippy/clippy_lints/src/field_scoped_visibility_modifiers.rs +++ b/src/tools/clippy/clippy_lints/src/field_scoped_visibility_modifiers.rs @@ -59,7 +59,9 @@ impl EarlyLintPass for FieldScopedVisibilityModifiers { let VisibilityKind::Restricted { path, .. } = &field.vis.kind else { continue; }; - if !path.segments.is_empty() && path.segments[0].ident.name == rustc_span::symbol::kw::SelfLower { + if let Some(ident) = path.iter_idents().next() + && ident.name == rustc_span::symbol::kw::SelfLower + { // pub(self) is equivalent to not using pub at all, so we ignore it continue; } diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs index a072e3dfff503..a6d20252673af 100644 --- a/src/tools/clippy/clippy_lints/src/format_args.rs +++ b/src/tools/clippy/clippy_lints/src/format_args.rs @@ -635,15 +635,14 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { if !matches!(arg.kind, FormatArgumentKind::Captured(_)) && let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind - && let [segment] = path.segments.as_slice() - && segment.args.is_none() + && let Some(name) = path.as_single_argless_name() && let Some(arg_span) = format_arg_removal_span(self.format_args, index) && let Some(pos_span) = pos.span { let replacement = match usage { - FormatParamUsage::Argument => segment.ident.name.to_string(), - FormatParamUsage::Width => format!("{}$", segment.ident.name), - FormatParamUsage::Precision => format!(".{}$", segment.ident.name), + FormatParamUsage::Argument => name.to_string(), + FormatParamUsage::Width => format!("{name}$"), + FormatParamUsage::Precision => format!(".{name}$"), }; fixes.push((pos_span, replacement)); fixes.push((arg_span, String::new())); diff --git a/src/tools/clippy/clippy_lints/src/misc_early/unneeded_field_pattern.rs b/src/tools/clippy/clippy_lints/src/misc_early/unneeded_field_pattern.rs index 96c4cedda7ed4..f9cd951e97cf4 100644 --- a/src/tools/clippy/clippy_lints/src/misc_early/unneeded_field_pattern.rs +++ b/src/tools/clippy/clippy_lints/src/misc_early/unneeded_field_pattern.rs @@ -9,12 +9,7 @@ use super::UNNEEDED_FIELD_PATTERN; pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { if let PatKind::Struct(_, ref npat, ref pfields, _) = pat.kind { let mut wilds = 0; - let type_name = npat - .segments - .last() - .expect("A path must have at least one segment") - .ident - .name; + let type_name = npat.last_ident().expect("A path must have at least one segment").name; for field in pfields { if let PatKind::Wild = field.pat.kind { diff --git a/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs b/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs index 9d7b3c0d27224..3ba01bf28991f 100644 --- a/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs +++ b/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs @@ -65,6 +65,8 @@ enum Mode { impl EarlyLintPass for NeedlessArbitrarySelfType { fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { + use itertools::Itertools; + // Bail out if the parameter it's not a receiver or was not written by the user if !p.is_self() || p.span.from_expansion() { return; @@ -84,8 +86,8 @@ impl EarlyLintPass for NeedlessArbitrarySelfType { }; let span = p.span.to(p.ty.span); - if let [segment] = &path.segments[..] - && segment.ident.name == kw::SelfUpper + if let Ok(ident) = path.iter_idents().exactly_one() + && ident.name == kw::SelfUpper { span_lint_and_then( cx, diff --git a/src/tools/clippy/clippy_lints/src/nonstandard_macro_braces.rs b/src/tools/clippy/clippy_lints/src/nonstandard_macro_braces.rs index 46a251b7b7278..41a30b4974f9d 100644 --- a/src/tools/clippy/clippy_lints/src/nonstandard_macro_braces.rs +++ b/src/tools/clippy/clippy_lints/src/nonstandard_macro_braces.rs @@ -53,7 +53,7 @@ impl MacroBraces { impl EarlyLintPass for MacroBraces { fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { - if let Some(last_segment) = mac.path.segments.last() + if let Some(last_segment) = mac.path.last_segment() && let name = last_segment.ident.as_str() && let Some(&braces) = self.macro_braces.0.get(name) && let Some(snip) = snippet_opt(cx.sess(), mac.span().with_lo(last_segment.span().lo())) diff --git a/src/tools/clippy/clippy_lints/src/redundant_field_names.rs b/src/tools/clippy/clippy_lints/src/redundant_field_names.rs index cfcbdffdd8b3c..1f7b34ab95381 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_field_names.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_field_names.rs @@ -54,9 +54,8 @@ impl EarlyLintPass for RedundantFieldNames { for field in &se.fields { if !field.is_shorthand && let ExprKind::Path(None, path) = &field.expr.kind - && let [segment] = path.segments.as_slice() - && segment.args.is_none() - && segment.ident == field.ident + && let Some(ident) = path.as_single_argless_ident() + && ident == field.ident && field.span.eq_ctxt(field.ident.span) && !field.span.in_external_macro(cx.sess().source_map()) { diff --git a/src/tools/clippy/clippy_lints/src/returns/needless_return.rs b/src/tools/clippy/clippy_lints/src/returns/needless_return.rs index 64a136d96a610..be9a4cf618cd4 100644 --- a/src/tools/clippy/clippy_lints/src/returns/needless_return.rs +++ b/src/tools/clippy/clippy_lints/src/returns/needless_return.rs @@ -121,6 +121,7 @@ fn check_final_expr<'tcx>( replacement: RetReplacement<'tcx>, match_ty_opt: Option>, ) { + use itertools::Itertools; let peeled_drop_expr = expr.peel_drop_temps(); match &peeled_drop_expr.kind { // simple return is always "bad" @@ -186,10 +187,10 @@ fn check_final_expr<'tcx>( && let metas = attr.meta_item_list() && let Some(lst) = metas && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() - && let [tool, lint_name] = meta_item.path.segments.as_slice() - && tool.ident.name == sym::clippy + && let Some((tool, lint)) = meta_item.path.iter_idents().collect_tuple() + && tool.name == sym::clippy && matches!( - lint_name.ident.name, + lint.name, sym::needless_return | sym::style | sym::all | sym::warnings ) { diff --git a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs index dfcab8c74434a..89855c4953614 100644 --- a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs +++ b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs @@ -103,20 +103,24 @@ struct ImportUsageVisitor { impl Visitor<'_> for ImportUsageVisitor { fn visit_expr(&mut self, expr: &Expr) { if let ExprKind::Path(_, path) = &expr.kind - && path.segments.len() > 1 - && path.segments[0].ident.name == kw::SelfLower + && let mut path_iter = path.iter_idents() + && let Some(ident) = path_iter.next() + && ident.name == kw::SelfLower + && let Some(ident) = path_iter.next() { - self.imports_referenced_with_self.push(path.segments[1].ident.name); + self.imports_referenced_with_self.push(ident.name); } walk_expr(self, expr); } fn visit_ty(&mut self, ty: &Ty) { if let TyKind::Path(_, path) = &ty.kind - && path.segments.len() > 1 - && path.segments[0].ident.name == kw::SelfLower + && let mut path_iter = path.iter_idents() + && let Some(ident) = path_iter.next() + && ident.name == kw::SelfLower + && let Some(ident) = path_iter.next() { - self.imports_referenced_with_self.push(path.segments[1].ident.name); + self.imports_referenced_with_self.push(ident.name); } } } @@ -175,6 +179,7 @@ impl SingleComponentPathImports { single_use_usages: &mut Vec, macros: &mut Vec, ) { + use itertools::Itertools; if item.span.from_expansion() || item.vis.kind.is_pub() { return; } @@ -187,12 +192,12 @@ impl SingleComponentPathImports { macros.push(ident.name); }, ItemKind::Use(use_tree) => { - let segments = &use_tree.prefix.segments; + let num_segments = use_tree.prefix.num_segments(); // keep track of `use some_module;` usages - if segments.len() == 1 { + if num_segments == 1 { if let UseTreeKind::Simple(None) = use_tree.kind { - let name = segments[0].ident.name; + let name = use_tree.prefix.iter_idents().next().unwrap().name; if !macros.contains(&name) { single_use_usages.push(SingleUse { name, @@ -205,15 +210,14 @@ impl SingleComponentPathImports { return; } - if segments.is_empty() { + if num_segments == 0 { // keep track of `use {some_module, some_other_module};` usages if let UseTreeKind::Nested { items, .. } = &use_tree.kind { for tree in items { - let segments = &tree.inner.prefix.segments; - if segments.len() == 1 - && let UseTreeKind::Simple(None) = tree.inner.kind + if let UseTreeKind::Simple(None) = tree.inner.kind + && let Ok(ident) = tree.inner.prefix.iter_idents().exactly_one() { - let name = segments[0].ident.name; + let name = ident.name; if !macros.contains(&name) { single_use_usages.push(SingleUse { name, @@ -227,19 +231,21 @@ impl SingleComponentPathImports { } } // keep track of `use self::some_module` usages - else if segments[0].ident.name == kw::SelfLower { + else if let mut ident_iter = use_tree.prefix.iter_idents() + && let Some(ident) = ident_iter.next() + && ident.name == kw::SelfLower + { // simple case such as `use self::module::SomeStruct` - if segments.len() > 1 { - imports_reused_with_self.push(segments[1].ident.name); + if let Some(ident) = ident_iter.next() { + imports_reused_with_self.push(ident.name); return; } // nested case such as `use self::{module1::Struct1, module2::Struct2}` if let UseTreeKind::Nested { items, .. } = &use_tree.kind { for tree in items { - let segments = &tree.inner.prefix.segments; - if !segments.is_empty() { - imports_reused_with_self.push(segments[0].ident.name); + if let Some(ident) = tree.inner.prefix.iter_idents().next() { + imports_reused_with_self.push(ident.name); } } } diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs b/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs index cd31d728ead14..eab491fa762a3 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs @@ -41,7 +41,7 @@ impl EarlyLintPass for UnnecessarySelfImports { self_tree, is_toplevel, } = self_import; - let Some(last_segment) = tree.prefix.segments.last() else { + let Some(last_segment) = tree.prefix.last_segment() else { return; }; @@ -103,10 +103,11 @@ struct SelfImport<'a> { // XXX: rewrite as a generator returning `SelfImport`s, if those ever get stabilized fn for_each_self_import<'a>(tree: &'a UseTree, emit_lint: impl Fn(SelfImport<'a>) + Copy) { fn inner<'a>(tree: &'a UseTree, emit_lint: impl Fn(SelfImport<'a>) + Copy, is_toplevel: bool) { + use itertools::Itertools; if let UseTreeKind::Nested { items, .. } = &tree.kind { if let [self_tree] = &**items - && let [self_seg] = &*self_tree.inner.prefix.segments - && self_seg.ident.name == kw::SelfLower + && let Ok(self_ident) = self_tree.inner.prefix.iter_idents().exactly_one() + && self_ident.name == kw::SelfLower { emit_lint(SelfImport { tree, diff --git a/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs index 20b5d36b8d5f3..3b029d6aeba1e 100644 --- a/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs +++ b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs @@ -42,12 +42,7 @@ fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) { if new_name.as_str() == "_" { return; } - let old_name = use_tree - .prefix - .segments - .last() - .expect("use paths cannot be empty") - .ident; + let old_name = use_tree.prefix.last_ident().expect("use paths cannot be empty"); unsafe_to_safe_check(old_name, new_name, cx, span); }, UseTreeKind::Simple(None) | UseTreeKind::Glob(_) => {}, diff --git a/src/tools/clippy/clippy_lints/src/visibility.rs b/src/tools/clippy/clippy_lints/src/visibility.rs index 7bbdb7af75eb5..83708142225e6 100644 --- a/src/tools/clippy/clippy_lints/src/visibility.rs +++ b/src/tools/clippy/clippy_lints/src/visibility.rs @@ -106,7 +106,7 @@ impl EarlyLintPass for Visibility { if (**path == kw::Super || **path == kw::SelfLower || **path == kw::Crate) && !*shorthand - && let [.., last] = &*path.segments + && let Some(last) = path.last_ident() && !is_from_proc_macro(cx, item.vis.span) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] @@ -119,7 +119,7 @@ impl EarlyLintPass for Visibility { diag.span_suggestion( item.vis.span, "remove it", - format!("pub({})", last.ident), + format!("pub({last})"), Applicability::MachineApplicable, ); }, @@ -127,7 +127,7 @@ impl EarlyLintPass for Visibility { } if *shorthand - && let [.., last] = &*path.segments + && let Some(last) = path.last_ident() && !is_from_proc_macro(cx, item.vis.span) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] @@ -140,7 +140,7 @@ impl EarlyLintPass for Visibility { diag.span_suggestion( item.vis.span, "add it", - format!("pub(in {})", last.ident), + format!("pub(in {last})"), Applicability::MachineApplicable, ); }, diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 0774fa729bc12..3289a467ade02 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -95,13 +95,20 @@ pub fn eq_maybe_qself(l: Option<&QSelf>, r: Option<&QSelf>) -> bool { } pub fn eq_path(l: &Path, r: &Path) -> bool { - over(&l.segments, &r.segments, eq_path_seg) + l.num_segments() == r.num_segments() + && l.iter_segments() + .zip(r.iter_segments()) + .all(|(a, b)| eq_path_seg_ref(a, b)) } fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool { eq_id(l.ident, r.ident) && both(l.args.as_ref(), r.args.as_ref(), |l, r| eq_generic_args(l, r)) } +fn eq_path_seg_ref(l: PathSegmentRef<'_>, r: PathSegmentRef<'_>) -> bool { + eq_id(*l.ident, *r.ident) && both(l.args, r.args, |l, r| eq_generic_args(l, r)) +} + fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool { match (l, r) { (AngleBracketed(l), AngleBracketed(r)) => over(&l.args, &r.args, eq_angle_arg), diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs index 828fb1d3313b4..eb2cc12ed7455 100644 --- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs +++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs @@ -472,14 +472,14 @@ fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) { TyKind::Path(qself_path, path) => { let start = if qself_path.is_some() { Pat::Str("<") - } else if let Some(first) = path.segments.first() { - ident_search_pat(first.ident).0 + } else if let Some(first_ident) = path.iter_idents().next() { + ident_search_pat(*first_ident).0 } else { // this shouldn't be possible, but sure Pat::Str("") }; - let end = if let Some(last) = path.segments.last() { - match last.args.as_deref() { + let end = if let Some(last) = path.last_segment() { + match last.args { // last `>` in `std::foo::Bar` Some(GenericArgs::AngleBracketed(_)) => Pat::Str(">"), Some(GenericArgs::Parenthesized(par_args)) => match &par_args.output { @@ -498,7 +498,7 @@ fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) { // last `..` in `(..)` -- `)` gets stripped Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."), // `bar` in `std::foo::bar` - None => ident_search_pat(last.ident).1, + None => ident_search_pat(*last.ident).1, } } else { // this shouldn't be possible @@ -524,8 +524,8 @@ fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) { } }, TyKind::MacCall(mac_call) => { - let start = if let Some(first) = mac_call.path.segments.first() { - ident_search_pat(first.ident).0 + let start = if let Some(first) = mac_call.path.iter_idents().next() { + ident_search_pat(*first).0 } else { Pat::Str("") }; From 93bc6547aa13a64b66371bd200ffafa8f9a972d2 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 11:57:47 -0700 Subject: [PATCH 26/30] Fix rustfmt for changes to Path --- src/tools/rustfmt/src/expr.rs | 2 +- src/tools/rustfmt/src/header.rs | 4 +-- src/tools/rustfmt/src/imports.rs | 22 ++++++------ src/tools/rustfmt/src/macros.rs | 6 ++-- src/tools/rustfmt/src/modules.rs | 8 ++--- src/tools/rustfmt/src/modules/visitor.rs | 12 +++---- src/tools/rustfmt/src/pairs.rs | 2 +- src/tools/rustfmt/src/patterns.rs | 2 +- src/tools/rustfmt/src/skip.rs | 21 +++++------- src/tools/rustfmt/src/spanned.rs | 2 +- src/tools/rustfmt/src/types.rs | 43 +++++++++++------------- src/tools/rustfmt/src/utils.rs | 8 ++--- src/tools/rustfmt/src/visitor.rs | 11 +++--- 13 files changed, 67 insertions(+), 76 deletions(-) diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 48f17fb0fd1a8..0bd38d4a2de10 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -1557,7 +1557,7 @@ pub(crate) fn rewrite_call( pub(crate) fn is_simple_expr(expr: &ast::Expr) -> bool { match expr.kind { ast::ExprKind::Lit(..) => true, - ast::ExprKind::Path(ref qself, ref path) => qself.is_none() && path.segments.len() <= 1, + ast::ExprKind::Path(ref qself, ref path) => qself.is_none() && path.num_segments() <= 1, ast::ExprKind::AddrOf(_, _, ref expr) | ast::ExprKind::Cast(ref expr, _) | ast::ExprKind::Field(ref expr, _) diff --git a/src/tools/rustfmt/src/header.rs b/src/tools/rustfmt/src/header.rs index 5a9d2beb010b4..0fa649ce8d167 100644 --- a/src/tools/rustfmt/src/header.rs +++ b/src/tools/rustfmt/src/header.rs @@ -84,9 +84,7 @@ impl<'a> HeaderPart<'a> { ast::VisibilityKind::Public => Cow::from("pub"), ast::VisibilityKind::Inherited => Cow::from(""), ast::VisibilityKind::Restricted { ref path, .. } => { - let ast::Path { ref segments, .. } = **path; - let mut segments_iter = - segments.iter().map(|seg| rewrite_ident(context, seg.ident)); + let mut segments_iter = path.iter_idents().map(|&i| rewrite_ident(context, i)); if path.is_global() { segments_iter .next() diff --git a/src/tools/rustfmt/src/imports.rs b/src/tools/rustfmt/src/imports.rs index f062eaa332d92..4e38ba095bc2d 100644 --- a/src/tools/rustfmt/src/imports.rs +++ b/src/tools/rustfmt/src/imports.rs @@ -30,7 +30,7 @@ use crate::visitor::FmtVisitor; /// Returns a name imported by a `use` declaration. /// E.g., returns `Ordering` for `std::cmp::Ordering` and `self` for `std::cmp::self`. pub(crate) fn path_to_imported_ident(path: &ast::Path) -> symbol::Ident { - path.segments.last().unwrap().ident + path.last_ident().unwrap() } /// Returns all but the last portion of the module path, except in the case of @@ -185,12 +185,12 @@ impl UseSegment { } } - fn from_path_segment( + fn from_ident( context: &RewriteContext<'_>, - path_seg: &ast::PathSegment, + ident: symbol::Ident, modsep: bool, ) -> Option { - let name = rewrite_ident(context, path_seg.ident); + let name = rewrite_ident(context, ident); if name.is_empty() { return None; } @@ -446,8 +446,8 @@ impl UseTree { let mut modsep = leading_modsep; - for p in &a.prefix.segments { - if let Some(use_segment) = UseSegment::from_path_segment(context, p, modsep) { + for &ident in a.prefix.iter_idents() { + if let Some(use_segment) = UseSegment::from_ident(context, ident, modsep) { result.path.push(use_segment); modsep = false; } @@ -458,7 +458,7 @@ impl UseTree { match a.kind { UseTreeKind::Glob(_) => { // in case of a global path and the glob starts at the root, e.g., "::*" - if a.prefix.segments.len() == 1 && leading_modsep { + if a.prefix.num_segments() == 1 && leading_modsep { let kind = UseSegmentKind::Ident("".to_owned(), None); result.path.push(UseSegment { kind, @@ -480,7 +480,7 @@ impl UseTree { list.iter().map(|tree| &tree.inner), "}", ",", - |tree| tree.prefix.span.lo(), + |tree| tree.prefix.span().lo(), |tree| tree.hi_span().hi(), |_| Ok("".to_owned()), // We only need comments for now. context.snippet_provider.span_after(a.span(), "{"), @@ -490,7 +490,7 @@ impl UseTree { // in case of a global path and the nested list starts at the root, // e.g., "::{foo, bar}" - if a.prefix.segments.len() == 1 && leading_modsep { + if a.prefix.num_segments() == 1 && leading_modsep { let kind = UseSegmentKind::Ident("".to_owned(), None); result.path.push(UseSegment { kind, @@ -515,8 +515,8 @@ impl UseTree { // bypass the call to path_to_imported_ident which would get only the ident and // lose the path root, e.g., `that` in `::that`. // The span of `a.prefix` contains the leading colons. - let name = if a.prefix.segments.len() == 2 && leading_modsep { - context.snippet(a.prefix.span).to_owned() + let name = if a.prefix.num_segments() == 2 && leading_modsep { + context.snippet(a.prefix.span()).to_owned() } else { rewrite_ident(context, path_to_imported_ident(&a.prefix)).to_owned() }; diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 9a88569b7909b..f4bc07161610b 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -105,9 +105,9 @@ impl Rewrite for MacroArg { /// Rewrite macro name without using pretty-printer if possible. fn rewrite_macro_name(context: &RewriteContext<'_>, path: &ast::Path) -> String { - if path.segments.len() == 1 { + if let Some(ident) = path.as_single_argless_ident() { // Avoid using pretty-printer in the common case. - format!("{}!", rewrite_ident(context, path.segments[0].ident)) + format!("{}!", rewrite_ident(context, ident)) } else { format!("{}!", pprust::path_to_string(path)) } @@ -165,7 +165,7 @@ pub(crate) fn rewrite_macro( let should_skip = context .skip_context .macros - .skip(context.snippet(mac.path.span)); + .skip(context.snippet(mac.path.span())); if should_skip { Err(RewriteError::SkipFormatting) } else { diff --git a/src/tools/rustfmt/src/modules.rs b/src/tools/rustfmt/src/modules.rs index 99e72dcebd8ea..bed958cf20ba3 100644 --- a/src/tools/rustfmt/src/modules.rs +++ b/src/tools/rustfmt/src/modules.rs @@ -607,8 +607,8 @@ fn find_path_value(attrs: &[ast::Attribute]) -> Option { fn is_cfg_if(item: &ast::Item) -> bool { match item.kind { ast::ItemKind::MacCall(ref mac) => { - if let Some(first_segment) = mac.path.segments.first() { - if first_segment.ident.name == Symbol::intern("cfg_if") { + if let Some(first_ident) = mac.path.iter_idents().next() { + if first_ident.name == Symbol::intern("cfg_if") { return true; } } @@ -621,8 +621,8 @@ fn is_cfg_if(item: &ast::Item) -> bool { fn is_cfg_select(item: &ast::Item) -> bool { match item.kind { ast::ItemKind::MacCall(ref mac) => { - if let Some(last_segment) = mac.path.segments.last() { - if last_segment.ident.name == Symbol::intern("cfg_select") { + if let Some(last_ident) = mac.path.last_ident() { + if last_ident.name == Symbol::intern("cfg_select") { return true; } } diff --git a/src/tools/rustfmt/src/modules/visitor.rs b/src/tools/rustfmt/src/modules/visitor.rs index 886128763c87a..d838fc8f8f13f 100644 --- a/src/tools/rustfmt/src/modules/visitor.rs +++ b/src/tools/rustfmt/src/modules/visitor.rs @@ -53,9 +53,9 @@ impl<'a, 'ast: 'a> CfgIfVisitor<'a> { // extern crate cfg_if; // cfg_if! {..} // ``` - match mac.path.segments.first() { - Some(first_segment) => { - if first_segment.ident.name != Symbol::intern("cfg_if") { + match mac.path.iter_idents().next() { + Some(first_ident) => { + if first_ident.name != Symbol::intern("cfg_if") { return Err("Expected cfg_if"); } } @@ -112,9 +112,9 @@ impl<'a, 'ast: 'a> CfgSelectVisitor<'a> { // use std::cfg_select; // cfg_select! {..} // ``` - match mac.path.segments.last() { - Some(last_segment) => { - if last_segment.ident.name != Symbol::intern("cfg_select") { + match mac.path.last_ident() { + Some(last_ident) => { + if last_ident.name != Symbol::intern("cfg_select") { return Err("Expected cfg_select"); } } diff --git a/src/tools/rustfmt/src/pairs.rs b/src/tools/rustfmt/src/pairs.rs index b4e390f206167..91260dd75167c 100644 --- a/src/tools/rustfmt/src/pairs.rs +++ b/src/tools/rustfmt/src/pairs.rs @@ -276,7 +276,7 @@ struct PairList<'a, 'b, T: Rewrite> { fn is_ident_or_bool_lit(expr: &ast::Expr) -> bool { match &expr.kind { - ast::ExprKind::Path(None, path) if path.segments.len() == 1 => true, + ast::ExprKind::Path(None, path) => path.num_segments() == 1, ast::ExprKind::Lit(token::Lit { kind: token::LitKind::Bool, .. diff --git a/src/tools/rustfmt/src/patterns.rs b/src/tools/rustfmt/src/patterns.rs index dd0c09b6e9609..39dc28cd1ffde 100644 --- a/src/tools/rustfmt/src/patterns.rs +++ b/src/tools/rustfmt/src/patterns.rs @@ -68,7 +68,7 @@ fn is_short_pattern_inner(context: &RewriteContext<'_>, pat: &ast::Pat) -> bool | ast::PatKind::Guard(..) => false, ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1, ast::PatKind::TupleStruct(_, ref path, ref subpats) => { - path.segments.len() <= 1 && subpats.len() <= 1 + path.num_segments() <= 1 && subpats.len() <= 1 } PatKind::Deref(ref p) | ast::PatKind::Ref(ref p, _, _) | ast::PatKind::Paren(ref p) => { is_short_pattern_inner(context, &*p) diff --git a/src/tools/rustfmt/src/skip.rs b/src/tools/rustfmt/src/skip.rs index cd3860d3d7f0a..81e5d23719a77 100644 --- a/src/tools/rustfmt/src/skip.rs +++ b/src/tools/rustfmt/src/skip.rs @@ -87,18 +87,15 @@ static RUSTFMT: &str = "rustfmt"; static SKIP: &str = "skip"; /// Say if you're playing with `rustfmt`'s skip attribute -pub(crate) fn is_skip_attr(segments: &[ast::PathSegment]) -> bool { - if segments.len() < 2 || segments[0].ident.to_string() != RUSTFMT { - return false; - } - match segments.len() { - 2 => segments[1].ident.to_string() == SKIP, - 3 => { - segments[1].ident.to_string() == SKIP - && ["macros", "attributes"] - .iter() - .any(|&n| n == pprust::path_segment_to_string(&segments[2])) - } +pub(crate) fn is_skip_attr(path: &ast::Path) -> bool { + let mut idents = path.iter_idents().map(|i| i.as_str()); + match std::array::from_fn(|_| idents.next()) { + [ + Some("rustfmt"), + Some("skip"), + None | Some("macros" | "attributes"), + None, + ] => true, _ => false, } } diff --git a/src/tools/rustfmt/src/spanned.rs b/src/tools/rustfmt/src/spanned.rs index 90331ce926e5c..5ee84ca373e62 100644 --- a/src/tools/rustfmt/src/spanned.rs +++ b/src/tools/rustfmt/src/spanned.rs @@ -200,7 +200,7 @@ impl Spanned for ast::PreciseCapturingArg { fn span(&self) -> Span { match self { ast::PreciseCapturingArg::Lifetime(lt) => lt.ident.span, - ast::PreciseCapturingArg::Arg(path, _) => path.span, + ast::PreciseCapturingArg::Arg(path, _) => path.span(), } } } diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs index fcf8321f18994..7bc338a70b302 100644 --- a/src/tools/rustfmt/src/types.rs +++ b/src/tools/rustfmt/src/types.rs @@ -1,5 +1,3 @@ -use std::ops::Deref; - use rustc_ast::ast::{self, FnRetTy, Mutability, Term}; use rustc_span::{BytePos, Pos, Span, symbol::kw}; use tracing::debug; @@ -51,7 +49,7 @@ pub(crate) fn rewrite_path( result.push_str("::"); } - let mut span_lo = path.span.lo(); + let mut span_lo = path.span().lo(); if let Some(qself) = qself { result.push('<'); @@ -66,14 +64,14 @@ pub(crate) fn rewrite_path( } // 3 = ">::".len() - let shape = shape.sub_width(3, path.span)?; + let shape = shape.sub_width(3, path.span())?; result = rewrite_path_segments( PathContext::Type, result, - path.segments.iter().take(skip_count), + path.iter_segments().take(skip_count), span_lo, - path.span.hi(), + path.span().hi(), context, shape, )?; @@ -86,9 +84,9 @@ pub(crate) fn rewrite_path( rewrite_path_segments( path_context, result, - path.segments.iter().skip(skip_count), + path.iter_segments().skip(skip_count), span_lo, - path.span.hi(), + path.span().hi(), context, shape, ) @@ -104,7 +102,7 @@ fn rewrite_path_segments<'a, I>( shape: Shape, ) -> RewriteResult where - I: Iterator, + I: Iterator>, { let mut first = true; let shape = shape.visual_indent(0); @@ -264,14 +262,14 @@ impl Rewrite for ast::AssocItemConstraintKind { // so that invariants described above will hold for the next segment. fn rewrite_segment( path_context: PathContext, - segment: &ast::PathSegment, + segment: ast::PathSegmentRef<'_>, span_lo: &mut BytePos, span_hi: BytePos, context: &RewriteContext<'_>, shape: Shape, ) -> RewriteResult { let mut result = String::with_capacity(128); - result.push_str(rewrite_ident(context, segment.ident)); + result.push_str(rewrite_ident(context, *segment.ident)); let ident_len = result.len(); let span = mk_sp(*span_lo, span_hi); @@ -1169,6 +1167,7 @@ fn join_bounds_inner( need_indent: bool, force_newline: bool, ) -> RewriteResult { + use itertools::Itertools; debug_assert!(!items.is_empty()); let generic_bounds_in_order = is_generic_bounds_in_order(items); @@ -1182,19 +1181,17 @@ fn join_bounds_inner( // that contains more than one item let is_item_with_multi_items_array = |item: &ast::GenericBound| match item { ast::GenericBound::Trait(ref poly_trait_ref, ..) => { - let segments = &poly_trait_ref.trait_ref.path.segments; - if segments.len() > 1 { - true + let Ok(segment) = poly_trait_ref.trait_ref.path.iter_segments().exactly_one() else { + return true; + }; + if let Some(args_in) = segment.args { + matches!( + args_in, + ast::GenericArgs::AngleBracketed(bracket_args) + if bracket_args.args.len() > 1 + ) } else { - if let Some(args_in) = &segments[0].args { - matches!( - args_in.deref(), - ast::GenericArgs::AngleBracketed(bracket_args) - if bracket_args.args.len() > 1 - ) - } else { - false - } + false } } ast::GenericBound::Use(args, _) => args.len() > 1, diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index f7933ac52f75c..301113e61af55 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use rustc_ast::YieldKind; use rustc_ast::ast::{ self, Attribute, ImplRestriction, MetaItem, MetaItemInner, MetaItemKind, MutRestriction, - NodeId, Path, RestrictionKind, Visibility, VisibilityKind, + NodeId, RestrictionKind, Visibility, VisibilityKind, }; use rustc_ast_pretty::pprust; use rustc_feature::is_builtin_attr_name; @@ -59,8 +59,7 @@ pub(crate) fn format_visibility( VisibilityKind::Public => Cow::from("pub "), VisibilityKind::Inherited => Cow::from(""), VisibilityKind::Restricted { ref path, .. } => { - let Path { ref segments, .. } = **path; - let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident)); + let mut segments_iter = path.iter_idents().map(|&i| rewrite_ident(context, i)); if path.is_global() { segments_iter .next() @@ -101,8 +100,7 @@ fn format_restriction( id: _, shorthand, } => { - let Path { ref segments, .. } = **path; - let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident)); + let mut segments_iter = path.iter_idents().map(|&i| rewrite_ident(context, i)); if path.is_global() && segments_iter.next().is_none() { panic!("non-global path in {kw}(restricted)?"); } diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs index 5043038d4fc09..0cd4b0bc75893 100644 --- a/src/tools/rustfmt/src/visitor.rs +++ b/src/tools/rustfmt/src/visitor.rs @@ -884,7 +884,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { } else { match &attr.kind { ast::AttrKind::Normal(ref normal) - if self.is_unknown_rustfmt_attr(&normal.item.path.segments) => + if self.is_unknown_rustfmt_attr(&normal.item.path) => { let file_name = self.psess.span_to_filename(attr.span); self.report.append( @@ -920,11 +920,12 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { false } - fn is_unknown_rustfmt_attr(&self, segments: &[ast::PathSegment]) -> bool { - if segments[0].ident.to_string() != "rustfmt" { - return false; + fn is_unknown_rustfmt_attr(&self, path: &ast::Path) -> bool { + match path.iter_idents().next() { + None => false, + Some(ident) if ident.as_str() != "rustfmt" => false, + _ => !is_skip_attr(path), } - !is_skip_attr(segments) } fn walk_mod_items(&mut self, items: &[Box]) { From b12956f19705a362f0696eea29ac963dc27fd09f Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 5 Oct 2026 16:15:08 -0700 Subject: [PATCH 27/30] Clean up `rustc_hir_pretty::print_attr_item` using `PathSegment::from_ident` No expected performance impact (just printing code), just a cleanup. --- compiler/rustc_hir_pretty/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 4cfa66c676087..84bf06796d160 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -10,8 +10,8 @@ use std::vec; use rustc_abi::ExternAbi; use rustc_ast as ast; +use rustc_ast::DelimArgs; use rustc_ast::util::parser::{self, ExprPrecedence, Fixity}; -use rustc_ast::{DUMMY_NODE_ID, DelimArgs}; use rustc_ast_pretty::pp::Breaks::{Consistent, Inconsistent}; use rustc_ast_pretty::pp::{self, BoxMarker, Breaks}; use rustc_ast_pretty::pprust::state::MacHeader; @@ -24,7 +24,7 @@ use rustc_hir::{ PreciseCapturingArg, RangeEnd, Term, TyFieldPath, TyPatKind, }; use rustc_span::source_map::SourceMap; -use rustc_span::{DUMMY_SP, FileName, Ident, Span, Spanned, Symbol, kw, sym}; +use rustc_span::{FileName, Ident, Span, Spanned, Symbol, kw, sym}; pub fn id_to_string(cx: &dyn rustc_hir::intravisit::HirTyCtxt<'_>, hir_id: HirId) -> String { to_string(&cx, |s| s.print_node(cx.hir_node(hir_id))) @@ -135,11 +135,7 @@ impl<'a> State<'a> { item.path .segments .iter() - .map(|i| ast::PathSegment { - ident: Ident { name: *i, span: DUMMY_SP }, - args: None, - id: DUMMY_NODE_ID, - }) + .map(|&s| ast::PathSegment::from_ident(Ident::with_dummy_span(s))) .collect(), span, ); From e0c81d434f0f5fae321b4091eb7c09908be25a57 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 6 Oct 2026 00:40:43 -0700 Subject: [PATCH 28/30] rustc_ast: `Path::is_empty`: Add explanations for NoSpan and Ident --- compiler/rustc_ast/src/ast.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 1f4965c292ab0..0c0bbc014b348 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -212,6 +212,7 @@ impl Path { pub fn is_empty(&self) -> bool { match self { Path::General((segments, _)) => segments.is_empty(), + // Path::NoSpan always has non-empty segments. Path::Ident is non-empty by definition. _ => false, } } From 3ef66bda060ff5a4370dfc18b0d3ade6e5d781f0 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 6 Oct 2026 00:43:59 -0700 Subject: [PATCH 29/30] rustc_ast: Update `Path` documentation, discuss non-canonical forms --- compiler/rustc_ast/src/ast.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 0c0bbc014b348..4da3e12c321a1 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -88,9 +88,12 @@ impl fmt::Display for Lifetime { /// A "Path" is essentially Rust's notion of a name. /// -/// We separate the common case 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`, +/// 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::::new`). +/// +/// 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 enum Path { /// The common case of a single identifier (e.g. `x`) From 410f4f52e7cc61815ce54e09e91ca5d3b8e165fa Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 6 Oct 2026 00:45:47 -0700 Subject: [PATCH 30/30] rustc_ast: Add an explanation for why Path's span may differ from its segments --- compiler/rustc_ast/src/ast.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 4da3e12c321a1..29203328c774b 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -105,7 +105,8 @@ pub enum Path { /// `kw::PathRoot`. segments: ThinVec, }, - /// A fully general path where the path's span differs. Boxed to avoid making `Path` larger. + /// A fully general path where the path's span differs (e.g. from recovery). Boxed to avoid + /// making `Path` larger. General(Box<(ThinVec, Span)>), }