From 3052cf5d0bb76ec8ac5b705abe3791f3ed2c47b4 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 21 Apr 2025 07:52:30 +1000 Subject: [PATCH 1/2] Eliminate `word_and_empty` methods. As part of this, streamline a bunch of related stuff: - Introduce `PathParser::word_sym`, as an alternative to `PathParser::word`. - Remove `MetaItemParser::path`, and just get the two parsers one at a time. - Rename `MetaItemParser::path_without_args` as `MetaItemParser::path`, and avoid the clone. - Inline and remove `MetaItemParser::word_without_args`, which has a single use. - Remove `MetaItemParser::word_or_empty_without_args`, which has a single use. - Remove `MetaItemParser::path_is`, which is unused. - Remove `MetaItemListParser::all_{word,path}_list`, which are unused. --- .../src/attributes/allow_unstable.rs | 2 +- .../src/attributes/deprecation.rs | 26 +++--- .../rustc_attr_parsing/src/attributes/repr.rs | 48 ++++++----- .../src/attributes/stability.rs | 31 ++++---- compiler/rustc_attr_parsing/src/context.rs | 3 +- compiler/rustc_attr_parsing/src/parser.rs | 79 +++---------------- 6 files changed, 69 insertions(+), 120 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs index d37ede86cfd2..8460a4a41227 100644 --- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs +++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs @@ -53,7 +53,7 @@ fn parse_unstable<'a>( for param in list.mixed() { let param_span = param.span(); - if let Some(ident) = param.meta_item().and_then(|i| i.word_without_args()) { + if let Some(ident) = param.meta_item().and_then(|i| i.path().word()) { res.push(ident.name); } else { cx.emit_err(session_diagnostics::ExpectsFeatures { diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs index 7d1417446b21..5e94261f1c49 100644 --- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs +++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs @@ -1,5 +1,4 @@ use rustc_attr_data_structures::{AttributeKind, DeprecatedSince, Deprecation}; -use rustc_span::symbol::Ident; use rustc_span::{Span, Symbol, sym}; use super::SingleAttributeParser; @@ -13,16 +12,13 @@ pub(crate) struct DeprecationParser; fn get( cx: &AcceptContext<'_>, - ident: Ident, + name: Symbol, param_span: Span, arg: &ArgParser<'_>, item: &Option, ) -> Option { if item.is_some() { - cx.emit_err(session_diagnostics::MultipleItem { - span: param_span, - item: ident.to_string(), - }); + cx.emit_err(session_diagnostics::MultipleItem { span: param_span, item: name.to_string() }); return None; } if let Some(v) = arg.name_value() { @@ -83,16 +79,16 @@ impl SingleAttributeParser for DeprecationParser { return None; }; - let (ident, arg) = param.word_or_empty(); + let ident_name = param.path().word_sym(); - match ident.name { - sym::since => { - since = Some(get(cx, ident, param_span, arg, &since)?); + match ident_name { + Some(name @ sym::since) => { + since = Some(get(cx, name, param_span, param.args(), &since)?); } - sym::note => { - note = Some(get(cx, ident, param_span, arg, ¬e)?); + Some(name @ sym::note) => { + note = Some(get(cx, name, param_span, param.args(), ¬e)?); } - sym::suggestion => { + Some(name @ sym::suggestion) => { if !features.deprecated_suggestion() { cx.emit_err(session_diagnostics::DeprecatedItemSuggestion { span: param_span, @@ -101,12 +97,12 @@ impl SingleAttributeParser for DeprecationParser { }); } - suggestion = Some(get(cx, ident, param_span, arg, &suggestion)?); + suggestion = Some(get(cx, name, param_span, param.args(), &suggestion)?); } _ => { cx.emit_err(session_diagnostics::UnknownMetaItem { span: param_span, - item: ident.to_string(), + item: param.path().to_string(), expected: if features.deprecated_suggestion() { &["since", "note", "suggestion"] } else { diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs index 26ca637faec6..aac6897119e6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/repr.rs +++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs @@ -96,57 +96,65 @@ fn parse_repr(cx: &AcceptContext<'_>, param: &MetaItemParser<'_>) -> Option { - cx.emit_err(session_diagnostics::InvalidReprAlignNeedArg { span: ident.span }); + let ident = param.path().word(); + let ident_span = ident.map_or(rustc_span::DUMMY_SP, |ident| ident.span); + let name = ident.map(|ident| ident.name); + let args = param.args(); + + match (name, args) { + (Some(sym::align), ArgParser::NoArgs) => { + cx.emit_err(session_diagnostics::InvalidReprAlignNeedArg { span: ident_span }); None } - (sym::align, ArgParser::List(l)) => parse_repr_align(cx, l, param.span(), AlignKind::Align), + (Some(sym::align), ArgParser::List(l)) => { + parse_repr_align(cx, l, param.span(), AlignKind::Align) + } - (sym::packed, ArgParser::NoArgs) => Some(ReprPacked(Align::ONE)), - (sym::packed, ArgParser::List(l)) => { + (Some(sym::packed), ArgParser::NoArgs) => Some(ReprPacked(Align::ONE)), + (Some(sym::packed), ArgParser::List(l)) => { parse_repr_align(cx, l, param.span(), AlignKind::Packed) } - (sym::align | sym::packed, ArgParser::NameValue(l)) => { + (Some(sym::align | sym::packed), ArgParser::NameValue(l)) => { cx.emit_err(session_diagnostics::IncorrectReprFormatGeneric { span: param.span(), // FIXME(jdonszelmann) can just be a string in the diag type - repr_arg: &ident.to_string(), + repr_arg: &ident.unwrap().to_string(), cause: IncorrectReprFormatGenericCause::from_lit_kind( param.span(), &l.value_as_lit().kind, - ident.name.as_str(), + ident.unwrap().as_str(), ), }); None } - (sym::Rust, ArgParser::NoArgs) => Some(ReprRust), - (sym::C, ArgParser::NoArgs) => Some(ReprC), - (sym::simd, ArgParser::NoArgs) => Some(ReprSimd), - (sym::transparent, ArgParser::NoArgs) => Some(ReprTransparent), - (i @ int_pat!(), ArgParser::NoArgs) => { + (Some(sym::Rust), ArgParser::NoArgs) => Some(ReprRust), + (Some(sym::C), ArgParser::NoArgs) => Some(ReprC), + (Some(sym::simd), ArgParser::NoArgs) => Some(ReprSimd), + (Some(sym::transparent), ArgParser::NoArgs) => Some(ReprTransparent), + (Some(i @ int_pat!()), ArgParser::NoArgs) => { // int_pat!() should make sure it always parses Some(ReprInt(int_type_of_word(i).unwrap())) } ( - sym::Rust | sym::C | sym::simd | sym::transparent | int_pat!(), + Some(sym::Rust | sym::C | sym::simd | sym::transparent | int_pat!()), ArgParser::NameValue(_), ) => { cx.emit_err(session_diagnostics::InvalidReprHintNoValue { span: param.span(), - name: ident.to_string(), + name: ident.unwrap().to_string(), }); None } - (sym::Rust | sym::C | sym::simd | sym::transparent | int_pat!(), ArgParser::List(_)) => { + ( + Some(sym::Rust | sym::C | sym::simd | sym::transparent | int_pat!()), + ArgParser::List(_), + ) => { cx.emit_err(session_diagnostics::InvalidReprHintNoParen { span: param.span(), - name: ident.to_string(), + name: ident.unwrap().to_string(), }); None } diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index bdad6b50186d..cea16a9e45f6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -204,7 +204,7 @@ fn insert_value_into_option_or_error( if item.is_some() { cx.emit_err(session_diagnostics::MultipleItem { span: param.span(), - item: param.path_without_args().to_string(), + item: param.path().to_string(), }); None } else if let Some(v) = param.args().name_value() @@ -242,13 +242,13 @@ pub(crate) fn parse_stability( return None; }; - match param.word_or_empty_without_args().name { - sym::feature => insert_value_into_option_or_error(cx, ¶m, &mut feature)?, - sym::since => insert_value_into_option_or_error(cx, ¶m, &mut since)?, + match param.path().word_sym() { + Some(sym::feature) => insert_value_into_option_or_error(cx, ¶m, &mut feature)?, + Some(sym::since) => insert_value_into_option_or_error(cx, ¶m, &mut since)?, _ => { cx.emit_err(session_diagnostics::UnknownMetaItem { span: param_span, - item: param.path_without_args().to_string(), + item: param.path().to_string(), expected: &["feature", "since"], }); return None; @@ -310,11 +310,10 @@ pub(crate) fn parse_unstability( return None; }; - let (word, args) = param.word_or_empty(); - match word.name { - sym::feature => insert_value_into_option_or_error(cx, ¶m, &mut feature)?, - sym::reason => insert_value_into_option_or_error(cx, ¶m, &mut reason)?, - sym::issue => { + match param.path().word_sym() { + Some(sym::feature) => insert_value_into_option_or_error(cx, ¶m, &mut feature)?, + Some(sym::reason) => insert_value_into_option_or_error(cx, ¶m, &mut reason)?, + Some(sym::issue) => { insert_value_into_option_or_error(cx, ¶m, &mut issue)?; // These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item @@ -328,7 +327,7 @@ pub(crate) fn parse_unstability( session_diagnostics::InvalidIssueString { span: param.span(), cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind( - args.name_value().unwrap().value_span, + param.args().name_value().unwrap().value_span, err.kind(), ), }, @@ -338,17 +337,19 @@ pub(crate) fn parse_unstability( }, }; } - sym::soft => { - if !args.no_args() { + Some(sym::soft) => { + if !param.args().no_args() { cx.emit_err(session_diagnostics::SoftNoArgs { span: param.span() }); } is_soft = true; } - sym::implied_by => insert_value_into_option_or_error(cx, ¶m, &mut implied_by)?, + Some(sym::implied_by) => { + insert_value_into_option_or_error(cx, ¶m, &mut implied_by)? + } _ => { cx.emit_err(session_diagnostics::UnknownMetaItem { span: param.span(), - item: param.path_without_args().to_string(), + item: param.path().to_string(), expected: &["feature", "reason", "issue", "soft", "implied_by"], }); return None; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 55c3df003fe1..c10178549166 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -260,7 +260,8 @@ impl<'sess> AttributeParser<'sess> { // } ast::AttrKind::Normal(n) => { let parser = MetaItemParser::from_attr(n, self.dcx()); - let (path, args) = parser.deconstruct(); + let path = parser.path(); + let args = parser.args(); let parts = path.segments().map(|i| i.name).collect::>(); if let Some(accepts) = ATTRIBUTE_MAPPING.0.get(parts.as_slice()) { diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 40aa39711d3e..6b20108a6e47 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -78,8 +78,8 @@ impl<'a> PathParser<'a> { (self.len() == 1).then(|| **self.segments().next().as_ref().unwrap()) } - pub fn word_or_empty(&self) -> Ident { - self.word().unwrap_or_else(Ident::empty) + pub fn word_sym(&self) -> Option { + self.word().map(|ident| ident.name) } /// Asserts that this MetaItem is some specific word. @@ -253,71 +253,28 @@ impl<'a> MetaItemParser<'a> { } } - /// Gets just the path, without the args. - pub fn path_without_args(&self) -> PathParser<'a> { - self.path.clone() - } - - /// Gets just the args parser, without caring about the path. - pub fn args(&self) -> &ArgParser<'a> { - &self.args - } - - pub fn deconstruct(&self) -> (PathParser<'a>, &ArgParser<'a>) { - (self.path_without_args(), self.args()) - } - - /// Asserts that this MetaItem starts with a path. Some examples: + /// Gets a `PathParser` for a path. Some examples: /// /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path /// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path /// - `#[inline]`: `inline` is a single segment path - pub fn path(&self) -> (PathParser<'a>, &ArgParser<'a>) { - self.deconstruct() - } - - /// Asserts that this MetaItem starts with a word, or single segment path. - /// Doesn't return the args parser. - /// - /// For examples. see [`Self::word`] - pub fn word_without_args(&self) -> Option { - Some(self.word()?.0) + pub fn path(&self) -> &PathParser<'a> { + &self.path } - /// Like [`word`](Self::word), but returns an empty symbol instead of None - pub fn word_or_empty_without_args(&self) -> Ident { - self.word_or_empty().0 + /// Gets just the args parser, without caring about the path. + pub fn args(&self) -> &ArgParser<'a> { + &self.args } - /// Asserts that this MetaItem starts with a word, or single segment path. + /// Asserts that this MetaItem starts with some specific word. /// /// Some examples: /// - `#[inline]`: `inline` is a word /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path, /// and not a word and should instead be parsed using [`path`](Self::path) - pub fn word(&self) -> Option<(Ident, &ArgParser<'a>)> { - let (path, args) = self.deconstruct(); - Some((path.word()?, args)) - } - - /// Like [`word`](Self::word), but returns an empty symbol instead of None - pub fn word_or_empty(&self) -> (Ident, &ArgParser<'a>) { - let (path, args) = self.deconstruct(); - (path.word().unwrap_or(Ident::empty()), args) - } - - /// Asserts that this MetaItem starts with some specific word. - /// - /// See [`word`](Self::word) for examples of what a word is. pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> { - self.path_without_args().word_is(sym).then(|| self.args()) - } - - /// Asserts that this MetaItem starts with some specific path. - /// - /// See [`word`](Self::path) for examples of what a word is. - pub fn path_is(&self, segments: &[Symbol]) -> Option<&ArgParser<'a>> { - self.path_without_args().segments_is(segments).then(|| self.args()) + self.path().word_is(sym).then(|| self.args()) } } @@ -560,7 +517,7 @@ impl<'a> MetaItemListParser<'a> { } /// Lets you pick and choose as what you want to parse each element in the list - pub fn mixed<'s>(&'s self) -> impl Iterator> + 's { + pub fn mixed(&self) -> impl Iterator> + '_ { self.sub_parsers.iter() } @@ -572,20 +529,6 @@ impl<'a> MetaItemListParser<'a> { self.len() == 0 } - /// Asserts that every item in the list is another list starting with a word. - /// - /// See [`MetaItemParser::word`] for examples of words. - pub fn all_word_list<'s>(&'s self) -> Option)>> { - self.mixed().map(|i| i.meta_item()?.word()).collect() - } - - /// Asserts that every item in the list is another list starting with a full path. - /// - /// See [`MetaItemParser::path`] for examples of paths. - pub fn all_path_list<'s>(&'s self) -> Option, &'s ArgParser<'a>)>> { - self.mixed().map(|i| Some(i.meta_item()?.path())).collect() - } - /// Returns Some if the list contains only a single element. /// /// Inside the Some is the parser to parse this single element. From 40952d76bfd110289067b45cccb6b68b0ac7abcc Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 24 Apr 2025 11:56:44 +1000 Subject: [PATCH 2/2] Remove `Ident::empty`. All uses have been removed. And it's nonsensical: an identifier by definition has at least one char. The commits adds an is-non-empty assertion to `Ident::new` to enforce this, and converts some `Ident` constructions to use `Ident::new`. Adding the assertion requires making `Ident::new` and `Ident::with_dummy_span` non-const, which is no great loss. The commit amends a couple of places that do path splitting to ensure no empty identifiers are created. --- compiler/rustc_hir/src/hir/tests.rs | 23 ++++++----------- compiler/rustc_parse/src/parser/expr.rs | 2 +- .../rustc_resolve/src/build_reduced_graph.rs | 4 +-- compiler/rustc_resolve/src/lib.rs | 25 +++++++++++++------ compiler/rustc_span/src/symbol.rs | 22 +++++++--------- src/librustdoc/clean/render_macro_matchers.rs | 2 +- .../passes/collect_intra_doc_links.rs | 21 +++++++++++----- 7 files changed, 54 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_hir/src/hir/tests.rs b/compiler/rustc_hir/src/hir/tests.rs index 18f8c523f9d3..8684adee29c9 100644 --- a/compiler/rustc_hir/src/hir/tests.rs +++ b/compiler/rustc_hir/src/hir/tests.rs @@ -50,21 +50,14 @@ fn trait_object_roundtrips() { } fn trait_object_roundtrips_impl(syntax: TraitObjectSyntax) { - let unambig = TyKind::TraitObject::<'_, ()>( - &[], - TaggedRef::new( - &const { - Lifetime { - hir_id: HirId::INVALID, - ident: Ident::new(sym::name, DUMMY_SP), - kind: LifetimeKind::Static, - source: LifetimeSource::Other, - syntax: LifetimeSyntax::Hidden, - } - }, - syntax, - ), - ); + let lt = Lifetime { + hir_id: HirId::INVALID, + ident: Ident::new(sym::name, DUMMY_SP), + kind: LifetimeKind::Static, + source: LifetimeSource::Other, + syntax: LifetimeSyntax::Hidden, + }; + let unambig = TyKind::TraitObject::<'_, ()>(&[], TaggedRef::new(<, syntax)); let unambig_to_ambig = unsafe { std::mem::transmute::<_, TyKind<'_, AmbigArg>>(unambig) }; match unambig_to_ambig { diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index f3b53971b295..2a7910a6af4d 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3828,7 +3828,7 @@ impl<'a> Parser<'a> { // Convert `label` -> `'label`, // so that nameres doesn't complain about non-existing label let label = format!("'{}", ident.name); - let ident = Ident { name: Symbol::intern(&label), span: ident.span }; + let ident = Ident::new(Symbol::intern(&label), ident.span); self.dcx().emit_err(errors::ExpectedLabelFoundIdent { span: ident.span, diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index cb328022c76d..3460c53782f3 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -549,7 +549,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { source = module_path.pop().unwrap(); if rename.is_none() { // Keep the span of `self`, but the name of `foo` - ident = Ident { name: source.ident.name, span: self_span }; + ident = Ident::new(source.ident.name, self_span); } } } else { @@ -597,7 +597,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { if let Some(crate_name) = crate_name { // `crate_name` should not be interpreted as relative. module_path.push(Segment::from_ident_and_id( - Ident { name: kw::PathRoot, span: source.ident.span }, + Ident::new(kw::PathRoot, source.ident.span), self.r.next_node_id(), )); source.ident.name = crate_name; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 4a252a7b5281..74f17c2924ea 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -2157,13 +2157,24 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ns: Namespace, parent_scope: ParentScope<'ra>, ) -> Option { - let mut segments = - Vec::from_iter(path_str.split("::").map(Ident::from_str).map(Segment::from_ident)); - if let Some(segment) = segments.first_mut() { - if segment.ident.name == kw::Empty { - segment.ident.name = kw::PathRoot; - } - } + let segments: Result, ()> = path_str + .split("::") + .enumerate() + .map(|(i, s)| { + let sym = if s.is_empty() { + if i == 0 { + // For a path like `::a::b`, use `kw::PathRoot` as the leading segment. + kw::PathRoot + } else { + return Err(()); // occurs in cases like `String::` + } + } else { + Symbol::intern(s) + }; + Ok(Segment::from_ident(Ident::with_dummy_span(sym))) + }) + .collect(); + let Ok(segments) = segments else { return None }; match self.maybe_resolve_path(&segments, Some(ns), &parent_scope, None) { PathResult::Module(ModuleOrUniformRoot::Module(module)) => Some(module.res().unwrap()), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 040ea7d8bc21..f6fac1b09a98 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2332,6 +2332,9 @@ pub const STDLIB_STABLE_CRATES: &[Symbol] = &[sym::std, sym::core, sym::alloc, s #[derive(Copy, Clone, Eq, HashStable_Generic, Encodable, Decodable)] pub struct Ident { + // `name` should never be the empty symbol. If you are considering that, + // you are probably conflating "empty identifer with "no identifier" and + // you should use `Option` instead. pub name: Symbol, pub span: Span, } @@ -2339,28 +2342,21 @@ pub struct Ident { impl Ident { #[inline] /// Constructs a new identifier from a symbol and a span. - pub const fn new(name: Symbol, span: Span) -> Ident { + pub fn new(name: Symbol, span: Span) -> Ident { + assert_ne!(name, kw::Empty); Ident { name, span } } /// Constructs a new identifier with a dummy span. #[inline] - pub const fn with_dummy_span(name: Symbol) -> Ident { + pub fn with_dummy_span(name: Symbol) -> Ident { Ident::new(name, DUMMY_SP) } - /// This is best avoided, because it blurs the lines between "empty - /// identifier" and "no identifier". Using `Option` is preferable, - /// where possible, because that is unambiguous. - #[inline] - pub fn empty() -> Ident { - Ident::with_dummy_span(kw::Empty) - } - // For dummy identifiers that are never used and absolutely must be - // present, it's better to use `Ident::dummy` than `Ident::Empty`, because - // it's clearer that it's intended as a dummy value, and more likely to be - // detected if it accidentally does get used. + // present. Note that this does *not* use the empty symbol; `sym::dummy` + // makes it clear that it's intended as a dummy value, and is more likely + // to be detected if it accidentally does get used. #[inline] pub fn dummy() -> Ident { Ident::with_dummy_span(sym::dummy) diff --git a/src/librustdoc/clean/render_macro_matchers.rs b/src/librustdoc/clean/render_macro_matchers.rs index e967fd406093..d684e6f8650f 100644 --- a/src/librustdoc/clean/render_macro_matchers.rs +++ b/src/librustdoc/clean/render_macro_matchers.rs @@ -167,7 +167,7 @@ fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) { } fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol, span: Span) -> bool { - let ident = Ident { name: symbol, span }; + let ident = Ident::new(symbol, span); let is_keyword = ident.is_used_keyword() || ident.is_unused_keyword(); if !is_keyword { // An identifier that is not a keyword usually does not need a space diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 36f5889dcf4e..f3e2138d1a57 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -496,12 +496,21 @@ impl<'tcx> LinkCollector<'_, 'tcx> { // Try looking for methods and associated items. // NB: `path_root` could be empty when resolving in the root namespace (e.g. `::std`). - let (path_root, item_str) = path_str.rsplit_once("::").ok_or_else(|| { - // If there's no `::`, it's not an associated item. - // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved. - debug!("found no `::`, assuming {path_str} was correctly not in scope"); - UnresolvedPath { item_id, module_id, partial_res: None, unresolved: path_str.into() } - })?; + let (path_root, item_str) = match path_str.rsplit_once("::") { + Some(res @ (_path_root, item_str)) if !item_str.is_empty() => res, + _ => { + // If there's no `::`, or the `::` is at the end (e.g. `String::`) it's not an + // associated item. So we can be sure that `rustc_resolve` was accurate when it + // said it wasn't resolved. + debug!("`::` missing or at end, assuming {path_str} was not in scope"); + return Err(UnresolvedPath { + item_id, + module_id, + partial_res: None, + unresolved: path_str.into(), + }); + } + }; let item_name = Symbol::intern(item_str); // FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support