Improve path segment joining.

There are many places that join path segments with `::` to produce a
string. A lot of these use `join("::")`. Many in rustdoc use
`join_with_double_colon`, and a few use `.joined("..")`. One in Clippy
uses `itertools::join`. A couple of them look for `kw::PathRoot` in the
first segment, which can be important.

This commit introduces `rustc_ast::join_path_{syms,ident}` to do the
joining for everyone. `rustc_ast` is as good a location for these as
any, being the earliest-running of the several crates with a `Path`
type. Two functions are needed because `Ident` printing is more complex
than simple `Symbol` printing.

The commit also removes `join_with_double_colon`, and
`estimate_item_path_byte_length` with it.

There are still a handful of places that join strings with "::" that are
unchanged. They are not that important: some of them are in tests, and
some of them first split a path around "::" and then rejoin with "::".

This fixes one test case where `{{root}}` shows up in an error message.
This commit is contained in:
Nicholas Nethercote
2025-05-27 15:51:47 +10:00
parent 5795086bdf
commit fb7aa9e4fd
20 changed files with 130 additions and 110 deletions

View File

@@ -18,7 +18,7 @@
//! - [`Attribute`]: Metadata associated with item.
//! - [`UnOp`], [`BinOp`], and [`BinOpKind`]: Unary and binary operators.
use std::borrow::Cow;
use std::borrow::{Borrow, Cow};
use std::{cmp, fmt};
pub use GenericArgs::*;
@@ -155,6 +155,59 @@ impl Path {
}
}
/// 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".
///
/// The generics on the `path` argument mean it can accept many forms, such as:
/// - `&[Symbol]`
/// - `Vec<Symbol>`
/// - `Vec<&Symbol>`
/// - `impl Iterator<Item = Symbol>`
/// - `impl Iterator<Item = &Symbol>`
///
/// Panics if `path` is empty or a segment after the first is `kw::PathRoot`.
pub fn join_path_syms(path: impl IntoIterator<Item = impl Borrow<Symbol>>) -> String {
// This is a guess at the needed capacity that works well in practice. It is slightly faster
// than (a) starting with an empty string, or (b) computing the exact capacity required.
// `8` works well because it's about the right size and jemalloc's size classes are all
// multiples of 8.
let mut iter = path.into_iter();
let len_hint = iter.size_hint().1.unwrap_or(1);
let mut s = String::with_capacity(len_hint * 8);
let first_sym = *iter.next().unwrap().borrow();
if first_sym != kw::PathRoot {
s.push_str(first_sym.as_str());
}
for sym in iter {
let sym = *sym.borrow();
debug_assert_ne!(sym, kw::PathRoot);
s.push_str("::");
s.push_str(sym.as_str());
}
s
}
/// Like `join_path_syms`, but for `Ident`s. This function is necessary because
/// `Ident::to_string` does more than just print the symbol in the `name` field.
pub fn join_path_idents(path: impl IntoIterator<Item = impl Borrow<Ident>>) -> String {
let mut iter = path.into_iter();
let len_hint = iter.size_hint().1.unwrap_or(1);
let mut s = String::with_capacity(len_hint * 8);
let first_ident = *iter.next().unwrap().borrow();
if first_ident.name != kw::PathRoot {
s.push_str(&first_ident.to_string());
}
for ident in iter {
let ident = *ident.borrow();
debug_assert_ne!(ident.name, kw::PathRoot);
s.push_str("::");
s.push_str(&ident.to_string());
}
s
}
/// A segment of a path: an identifier, an optional lifetime, and a set of types.
///
/// E.g., `std`, `String` or `Box<T>`.