Change:
I tried this code: I expected to see this happen: Rustdoc links to Instead, this happened: Rustdoc gets confused that means a function and thinks that the link is a primitive. <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: Relevant code: I think the fix is just to add as a match guard. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
This is blocking and made unnecessarily difficult.
claim
which arm do you mean to add the match guard to?
the one for telling if the resolved item is a primitive:
It looks like is in fact for
Oh hmm - the issue is that for primitives, rustdoc generates the anchor manually: I would try storing the of the associated item in instead, so you know later whether the anchor is a manual link () or whether it was generated by rustdoc.
Adding in here and adding the match guard seems to fix things, but also feels like a hack.
I mean, all of is a hack because other parts of rustdoc break when you store the associated item instead: If you can figure out a way to get rid of it, I'd love to do that, but it will require some pretty major refactors I think.

Discussion:
ns, impl_, ) <del> .map(|item| match item.kind { ty::AssocKind::Fn => "method", ty::AssocKind::Const => "associatedconstant", ty::AssocKind::Type => "associatedtype", </del> <ins> .map(|item| { let kind = item.kind; self.kind_side_channel.set(Some((kind.as_def_kind(), item.def_id))); match kind { ty::AssocKind::Fn => "method", ty::AssocKind::Const => "associatedconstant", ty::AssocKind::Type => "associatedtype", } </ins> }) .map(|out| { (

callback, ); }; <del> match res { Res::Primitive(_) => match disambiguator { Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => { Some(ItemLink { link: ori_link.link, link_text, did: None, fragment }) } Some(other) => { report_mismatch(other, Disambiguator::Primitive); None } }, Res::Def(kind, id) => { debug!("intra-doc link to {} resolved to {:?}", path_str, res); // Disallow e.g. linking to enums with `struct@` debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator); match (self.kind_side_channel.take().map(|(kind, _)| kind).unwrap_or(kind), disambiguator) { | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const))) // NOTE: this allows 'method' to mean both normal functions and associated functions // This can't cause ambiguity because both are in the same namespace. | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn))) // These are namespaces; allow anything in the namespace to match | (_, Some(Disambiguator::Namespace(_))) // If no disambiguator given, allow anything | (_, None) // All of these are valid, so do nothing => {} (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {} (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => { report_mismatch(specified, Disambiguator::Kind(kind)); return None; } </del> <ins> let verify = |kind: DefKind, id: DefId| { debug!("intra-doc link to {} resolved to {:?}", path_str, res); // Disallow e.g. linking to enums with `struct@` debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator); match (self.kind_side_channel.take().map(|(kind, _)| kind).unwrap_or(kind), disambiguator) { | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const))) // NOTE: this allows 'method' to mean both normal functions and associated functions // This can't cause ambiguity because both are in the same namespace. | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn))) // These are namespaces; allow anything in the namespace to match | (_, Some(Disambiguator::Namespace(_))) // If no disambiguator given, allow anything | (_, None) // All of these are valid, so do nothing => {} (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {} (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => { report_mismatch(specified, Disambiguator::Kind(kind)); return None; </ins> } <ins> } // item can be non-local e.g. when using #[doc(primitive = "pointer")] if let Some((src_id, dst_id)) = id .as_local() .and_then(|dst_id| item.def_id.as_local().map(|src_id| (src_id, dst_id))) { use rustc_hir::def_id::LOCAL_CRATE; </ins> <del> // item can be non-local e.g. when using #[doc(primitive = "pointer")] if let Some((src_id, dst_id)) = id .as_local() .and_then(|dst_id| item.def_id.as_local().map(|src_id| (src_id, dst_id))) </del> <ins> let hir_src = self.cx.tcx.hir().local_def_id_to_hir_id(src_id); let hir_dst = self.cx.tcx.hir().local_def_id_to_hir_id(dst_id); if self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_src) && !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_dst) </ins> { <del> use rustc_hir::def_id::LOCAL_CRATE; </del> <ins> privacy_error(cx, &item, &path_str, dox, &ori_link); } } </ins> <del> let hir_src = self.cx.tcx.hir().local_def_id_to_hir_id(src_id); let hir_dst = self.cx.tcx.hir().local_def_id_to_hir_id(dst_id); </del> <ins> Some((kind, id)) }; </ins> <del> if self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_src) && !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_dst) { privacy_error(cx, &item, &path_str, dox, &ori_link); </del> <ins> match res { Res::Primitive(_) => { if let Some((kind, id)) = self.kind_side_channel.take() { // We're actually resolving an associated item of a primitive, so we need to // verify the disambiguator (if any) matches the type of the associated item. // This case should really follow the same flow as the `Res::Def` branch below, // but attempting to add a call to `clean::register_res` causes an ICE. @jyn514 // thinks `register_res` is only needed for cross-crate re-exports, but Rust // doesn't allow statements like `use str::trim;`, making this a (hopefully) // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677 // for discussion on the matter. verify(kind, id)?; } else { match disambiguator { Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {} Some(other) => { report_mismatch(other, Disambiguator::Primitive); return None; } </ins> } } <ins> Some(ItemLink { link: ori_link.link, link_text, did: None, fragment }) } Res::Def(kind, id) => { let (kind, id) = verify(kind, id)?; </ins> let id = clean::register_res(cx, rustc_hir::def::Res::Def(kind, id)); Some(ItemLink { link: ori_link.link, link_text, did: Some(id), fragment }) }

<ins> #![deny(broken_intra_doc_links)] //! [static@u8::MIN] //~^ ERROR incompatible link kind </ins>

<ins> error: incompatible link kind for `u8::MIN` --> $DIR/incompatible-primitive-disambiguator.rs:2:6 | LL | //! [static@u8::MIN] |      ^^^^^^^^^^^^^^ help: to link to the associated constant, prefix with `const@`: `const@u8::MIN` | note: the lint level is defined here --> $DIR/incompatible-primitive-disambiguator.rs:1:9 | LL | #![deny(broken_intra_doc_links)] |         ^^^^^^^^^^^^^^^^^^^^^^ = note: this link resolved to an associated constant, which is not a static error: aborting due to previous error </ins>

<ins> #![deny(broken_intra_doc_links)] // @has primitive_disambiguators/index.html // @has - '//a/@href' 'https://doc.rust-lang.org/nightly/std/primitive.str.html#method.trim' //! [str::trim()] </ins>