提交 0273e3bc 编写于 作者: B bors

Auto merge of #87073 - jyn514:primitive-docs, r=GuillaumeGomez,jyn514

Fix rustdoc handling of primitive items

This is a complicated PR and does a lot of things. I'm willing to split it up a little more if it would help reviewing, but it would be tricky and I'd rather not unless it's necessary.

 ## What does this do?

- Fixes https://github.com/rust-lang/rust/issues/73423.
- Fixes https://github.com/rust-lang/rust/issues/79630. I'm not sure how to test this for the standard library explicitly, but you can see from some of the diffs from the `no_std` tests. I also tested it locally and it works correctly: ![image](https://user-images.githubusercontent.com/23638587/125214383-e1fdd000-e284-11eb-8048-76b5df958aad.png)
- Fixes https://github.com/rust-lang/rust/issues/83083.

## Why are these changes interconnected?

- Allowing anchors (https://github.com/rust-lang/rust/issues/83083) without fixing the online/offline problem (https://github.com/rust-lang/rust/issues/79630) will actually just silently discard the anchors, that's not a fix. The online/offline problem is directly related to the fragment hack; links need to go through `fn href()` to be fixed.
- Technically I could fix the online/offline problem without removing the error on anchors; I am willing to separate that out if it would be helpful for reviewing. However I can't fix the anchor problem without adding docs to core, since rustdoc needs all those primitives to have docs to avoid a fallback, and currently `#![no_std]` crates don't have docs for primitives. I also can't fix the online/offline problem without removing the fragment hack, since otherwise diffs like this will be wrong for some primitives but not others:
```diff
`@@` -385,7 +381,7 `@@` fn resolve_primitive_associated_item(
                         ty::AssocKind::Const => "associatedconstant",
                         ty::AssocKind::Type => "associatedtype",
                     };
-                    let fragment = format!("{}#{}.{}", prim_ty.as_sym(), out, item_name);
+                    let fragment = format!("{}.{}", out, item_name);
                     (Res::Primitive(prim_ty), fragment, Some((kind.as_def_kind(), item.def_id)))
                 })
         })
```
- Adding primitive docs to core without making any other change will cause links to go to `core` instead of `std`, even for crates with `extern crate std`. See "Breaking changes to doc(primitive)" below for why this is the case. That said, I could add some special casing to rustdoc at the same time that would let me separate this change from the others (it would fix https://github.com/rust-lang/rust/issues/73423 but still special-case intra-doc links). I'm willing to separate that out if helpful for reviewing.

### Add primitive documentation to libcore

This works by reusing the same `include!("primitive_docs.rs")` file in both core and std, and then special-casing links in core to use relative links instead of intra-doc links. This doesn't use purely intra-doc links because some of the primitive docs links to items only in std; this doesn't use purely relative links because that introduces new broken links when the docs are re-exported (e.g. String's `&str` deref impl, or Vec's slice deref impl).

Note that this copies the whole file to core, to avoid anyone compiling core to have to set `CARGO_PKG_NAME`. See https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/Who.20should.20review.20changes.20to.20linkchecker.3F/near/249939598 for more context. It also adds a tidy check to make sure the two files are kept in sync.

### Fix inconsistent online/offline primitive docs

This does four things:
- Records modules with `doc(primitive)` in `cache.external_paths`. This is necessary for `href()` to find them later.
- Makes `cache.primitive_locations` available to the intra-doc link pass, by refactoring out a `PrimitiveType::primitive_locations` function that only uses `TyCtxt`.
- Special cases modules with `doc(primitive)` to be treated as always public for the purpose of links.
- Removes the fragment hack. cc `@notriddle,` I know you added some comments about this in the code (thank you for that!)

### Breaking changes to `doc(primitive)`

"Breaking" is a little misleading here - these are changes in behavior, none of them will cause code to fail to compile.

Let me preface this by saying I think stabilizing `doc(primitive)` was a uniquely terrible idea. As far as I can tell, it was stabilized by oversight; it's been stable since 1.0. No one should have need to use it except the standard library, and a crater run shows that in fact no one is using it: https://github.com/rust-lang/rust/pull/87050#issuecomment-886166706. I hope to actually make `doc(primitive)` a no-op unless you opt-in with a nightly feature, which will keep crates compiling without forcing rustdoc into trying to keep somewhat arbitrary behavior guarantees; but for now, this just subtly changes some of the behavior if you use `doc(primitive)` in a dependency.

That said, here are the changes:
-  Refactoring out `primitive_locations()` is technically a change in behavior, since it no longer looks for primitives in crates that were passed through `--extern`, but not used by the crate; however, that seems like such an unlikely edge case it's not worth dealing with.
- The precedence given to primitive locations is no longer just arbitrary, it can also be inconsistent from run to run. Let me explain that more: previously, primitive locations were sorted by the `CrateNum`; the comment on that sort said "Favor linking to as local extern as possible, so iterate all crates in reverse topological order." Unfortunately, that's not actually what CrateNum tracks: it measures the order crates are loaded, not the number of intermediate crates between that dependency and the root crate. It happened to work as intended before because the compiler injects `extern crate std;` at the top of every crate, which ensured it would have the first CrateNum other than the current, but every other CrateNum was completely arbitrary (for example, `core` often had a later CrateNum than `std`). This now removes the sort on CrateNum completely and special-cases core instead. In particular, if you depend on both `std` and a crate which defines a `doc(primitive)` module, it's arbitrary whether rustdoc will use the docs from std or the ones from the other crate. cc `@alexcrichton,` you wrote this originally.

cc `@rust-lang/rustdoc`
cc `@rust-lang/libs` for the addition to `core` (the commit you're interested in is https://github.com/rust-lang/rust/pull/87073/commits/91346c8293bb5f41d8e1d2ec9336433664652c53)
../std/boxed/struct.Box.html#method.into_raw
......@@ -2,7 +2,8 @@
#[lang = "bool"]
impl bool {
/// Returns `Some(t)` if the `bool` is [`true`](keyword.true.html), or `None` otherwise.
/// Returns `Some(t)` if the `bool` is [`true`](../std/keyword.true.html),
/// or `None` otherwise.
///
/// # Examples
///
......@@ -18,7 +19,8 @@ pub fn then_some<T>(self, t: T) -> Option<T> {
if self { Some(t) } else { None }
}
/// Returns `Some(f())` if the `bool` is [`true`](keyword.true.html), or `None` otherwise.
/// Returns `Some(f())` if the `bool` is [`true`](../std/keyword.true.html),
/// or `None` otherwise.
///
/// # Examples
///
......
......@@ -24,7 +24,7 @@ impl char {
/// decoding error.
///
/// It can occur, for example, when giving ill-formed UTF-8 bytes to
/// [`String::from_utf8_lossy`](string/struct.String.html#method.from_utf8_lossy).
/// [`String::from_utf8_lossy`](../std/string/struct.String.html#method.from_utf8_lossy).
#[stable(feature = "assoc_char_consts", since = "1.52.0")]
pub const REPLACEMENT_CHARACTER: char = '\u{FFFD}';
......@@ -96,7 +96,7 @@ pub fn decode_utf16<I: IntoIterator<Item = u16>>(iter: I) -> DecodeUtf16<I::Into
/// Converts a `u32` to a `char`.
///
/// Note that all `char`s are valid [`u32`]s, and can be cast to one with
/// [`as`](keyword.as.html):
/// [`as`](../std/keyword.as.html):
///
/// ```
/// let c = '💯';
......@@ -372,7 +372,7 @@ pub fn to_digit(self, radix: u32) -> Option<u32> {
/// println!("\\u{{2764}}");
/// ```
///
/// Using [`to_string`](string/trait.ToString.html#tymethod.to_string):
/// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string):
///
/// ```
/// assert_eq!('❤'.escape_unicode().to_string(), "\\u{2764}");
......@@ -448,7 +448,7 @@ pub(crate) fn escape_debug_ext(self, args: EscapeDebugExtArgs) -> EscapeDebug {
/// println!("\\n");
/// ```
///
/// Using [`to_string`](string/trait.ToString.html#tymethod.to_string):
/// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string):
///
/// ```
/// assert_eq!('\n'.escape_debug().to_string(), "\\n");
......@@ -502,7 +502,7 @@ pub fn escape_debug(self) -> EscapeDebug {
/// println!("\\\"");
/// ```
///
/// Using [`to_string`](string/trait.ToString.html#tymethod.to_string):
/// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string):
///
/// ```
/// assert_eq!('"'.escape_default().to_string(), "\\\"");
......@@ -937,7 +937,7 @@ pub fn is_numeric(self) -> bool {
/// println!("i\u{307}");
/// ```
///
/// Using [`to_string`](string/trait.ToString.html#tymethod.to_string):
/// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string):
///
/// ```
/// assert_eq!('C'.to_lowercase().to_string(), "c");
......@@ -1002,7 +1002,7 @@ pub fn to_lowercase(self) -> ToLowercase {
/// println!("SS");
/// ```
///
/// Using [`to_string`](string/trait.ToString.html#tymethod.to_string):
/// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string):
///
/// ```
/// assert_eq!('c'.to_uppercase().to_string(), "C");
......
......@@ -130,6 +130,7 @@
#![feature(decl_macro)]
#![feature(doc_cfg)]
#![feature(doc_notable_trait)]
#![feature(doc_primitive)]
#![feature(exhaustive_patterns)]
#![feature(extern_types)]
#![feature(fundamental)]
......@@ -355,3 +356,5 @@ pub mod arch {
/* compiler built-in */
}
}
include!("primitive_docs.rs");
此差异已折叠。
......@@ -2257,9 +2257,9 @@ pub fn binary_search_by<'a, F>(&'a self, mut f: F) -> Result<usize, usize>
/// assert!(match r { Ok(1..=4) => true, _ => false, });
/// ```
// Lint rustdoc::broken_intra_doc_links is allowed as `slice::sort_by_key` is
// in crate `alloc`, and as such doesn't exists yet when building `core`.
// links to downstream crate: #74481. Since primitives are only documented in
// libstd (#73423), this never leads to broken links in practice.
// in crate `alloc`, and as such doesn't exists yet when building `core`: #74481.
// This breaks links when slice is displayed in core, but changing it to use relative links
// would break when the item is re-exported. So allow the core links to be broken for now.
#[allow(rustdoc::broken_intra_doc_links)]
#[stable(feature = "slice_binary_search_by_key", since = "1.10.0")]
#[inline]
......
// `library/{std,core}/src/primitive_docs.rs` should have the same contents.
// These are different files so that relative links work properly without
// having to have `CARGO_PKG_NAME` set, but conceptually they should always be the same.
#[doc(primitive = "bool")]
#[doc(alias = "true")]
#[doc(alias = "false")]
......@@ -20,12 +23,12 @@
/// assert!(!bool_val);
/// ```
///
/// [`true`]: keyword.true.html
/// [`false`]: keyword.false.html
/// [`true`]: ../std/keyword.true.html
/// [`false`]: ../std/keyword.false.html
/// [`BitAnd`]: ops::BitAnd
/// [`BitOr`]: ops::BitOr
/// [`Not`]: ops::Not
/// [`if`]: keyword.if.html
/// [`if`]: ../std/keyword.if.html
///
/// # Examples
///
......@@ -103,7 +106,7 @@ mod prim_bool {}
/// behaviour of the `!` type - expressions with type `!` will coerce into any other type.
///
/// [`u32`]: prim@u32
/// [`exit`]: process::exit
#[doc = concat!("[`exit`]: ", include_str!("../primitive_docs/process_exit.md"))]
///
/// # `!` and generics
///
......@@ -188,7 +191,7 @@ mod prim_bool {}
/// because `!` coerces to `Result<!, ConnectionError>` automatically.
///
/// [`String::from_str`]: str::FromStr::from_str
/// [`String`]: string::String
#[doc = concat!("[`String`]: ", include_str!("../primitive_docs/string_string.md"))]
/// [`FromStr`]: str::FromStr
///
/// # `!` and traits
......@@ -264,7 +267,7 @@ mod prim_bool {}
/// `impl` for this which simply panics, but the same is true for any type (we could `impl
/// Default` for (eg.) [`File`] by just making [`default()`] panic.)
///
/// [`File`]: fs::File
#[doc = concat!("[`File`]: ", include_str!("../primitive_docs/fs_file.md"))]
/// [`Debug`]: fmt::Debug
/// [`default()`]: Default::default
///
......@@ -272,7 +275,6 @@ mod prim_bool {}
mod prim_never {}
#[doc(primitive = "char")]
//
/// A character type.
///
/// The `char` type represents a single character. More specifically, since
......@@ -304,7 +306,7 @@ mod prim_never {}
/// assert_eq!(5, s.len() * std::mem::size_of::<u8>());
/// ```
///
/// [`String`]: string/struct.String.html
#[doc = concat!("[`String`]: ", include_str!("../primitive_docs/string_string.md"))]
///
/// As always, remember that a human intuition for 'character' might not map to
/// Unicode's definitions. For example, despite looking similar, the 'é'
......@@ -499,7 +501,7 @@ mod prim_unit {}
/// [`null_mut`]: ptr::null_mut
/// [`is_null`]: pointer::is_null
/// [`offset`]: pointer::offset
/// [`into_raw`]: Box::into_raw
#[doc = concat!("[`into_raw`]: ", include_str!("../primitive_docs/box_into_raw.md"))]
/// [`drop`]: mem::drop
/// [`write`]: ptr::write
#[stable(feature = "rust1", since = "1.0.0")]
......@@ -581,9 +583,9 @@ mod prim_pointer {}
/// # Editions
///
/// Prior to Rust 1.53, arrays did not implement [`IntoIterator`] by value, so the method call
/// `array.into_iter()` auto-referenced into a [slice iterator](slice::iter). Right now, the old behavior
/// is preserved in the 2015 and 2018 editions of Rust for compatibility, ignoring
/// `IntoIterator` by value. In the future, the behavior on the 2015 and 2018 edition
/// `array.into_iter()` auto-referenced into a [slice iterator](slice::iter). Right now, the old
/// behavior is preserved in the 2015 and 2018 editions of Rust for compatibility, ignoring
/// [`IntoIterator`] by value. In the future, the behavior on the 2015 and 2018 edition
/// might be made consistent to the behavior of later editions.
///
/// ```rust,edition2018
......@@ -1042,15 +1044,15 @@ mod prim_usize {}
/// References, both shared and mutable.
///
/// A reference represents a borrow of some owned value. You can get one by using the `&` or `&mut`
/// operators on a value, or by using a [`ref`](keyword.ref.html) or
/// <code>[ref](keyword.ref.html) [mut](keyword.mut.html)</code> pattern.
/// operators on a value, or by using a [`ref`](../std/keyword.ref.html) or
/// <code>[ref](../std/keyword.ref.html) [mut](../std/keyword.mut.html)</code> pattern.
///
/// For those familiar with pointers, a reference is just a pointer that is assumed to be
/// aligned, not null, and pointing to memory containing a valid value of `T` - for example,
/// <code>&[bool]</code> can only point to an allocation containing the integer values `1`
/// ([`true`](keyword.true.html)) or `0` ([`false`](keyword.false.html)), but creating a
/// <code>&[bool]</code> that points to an allocation containing the value `3` causes
/// undefined behaviour.
/// ([`true`](../std/keyword.true.html)) or `0` ([`false`](../std/keyword.false.html)), but
/// creating a <code>&[bool]</code> that points to an allocation containing
/// the value `3` causes undefined behaviour.
/// In fact, <code>[Option]\<&T></code> has the same memory representation as a
/// nullable but aligned pointer, and can be passed across FFI boundaries as such.
///
......@@ -1117,6 +1119,7 @@ mod prim_usize {}
///
/// [`DerefMut`]: ops::DerefMut
/// [`BorrowMut`]: borrow::BorrowMut
/// [bool]: prim@bool
///
/// The following traits are implemented on `&T` references if the underlying `T` also implements
/// that trait:
......@@ -1134,7 +1137,7 @@ mod prim_usize {}
/// [`std::fmt`]: fmt
/// ['Pointer`]: fmt::Pointer
/// [`Hash`]: hash::Hash
/// [`ToSocketAddrs`]: net::ToSocketAddrs
#[doc = concat!("[`ToSocketAddrs`]: ", include_str!("../primitive_docs/net_tosocketaddrs.md"))]
///
/// `&mut T` references get all of the above except `ToSocketAddrs`, plus the following, if `T`
/// implements that trait:
......@@ -1155,9 +1158,10 @@ mod prim_usize {}
///
/// [`FusedIterator`]: iter::FusedIterator
/// [`TrustedLen`]: iter::TrustedLen
/// [`Seek`]: io::Seek
/// [`BufRead`]: io::BufRead
/// [`Read`]: io::Read
#[doc = concat!("[`Seek`]: ", include_str!("../primitive_docs/io_seek.md"))]
#[doc = concat!("[`BufRead`]: ", include_str!("../primitive_docs/io_bufread.md"))]
#[doc = concat!("[`Read`]: ", include_str!("../primitive_docs/io_read.md"))]
#[doc = concat!("[`io::Write`]: ", include_str!("../primitive_docs/io_write.md"))]
///
/// Note that due to method call deref coercion, simply calling a trait method will act like they
/// work on references as well as they do on owned values! The implementations described here are
......
......@@ -17,7 +17,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
let param_env = self.cx.tcx.param_env(item_def_id);
let ty = self.cx.tcx.type_of(item_def_id);
debug!("get_blanket_impls({:?})", ty);
trace!("get_blanket_impls({:?})", ty);
let mut impls = Vec::new();
for &trait_def_id in self.cx.tcx.all_traits(()).iter() {
if !self.cx.cache.access_levels.is_public(trait_def_id)
......@@ -28,9 +28,10 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
// NOTE: doesn't use `for_each_relevant_impl` to avoid looking at anything besides blanket impls
let trait_impls = self.cx.tcx.trait_impls_of(trait_def_id);
for &impl_def_id in trait_impls.blanket_impls() {
debug!(
trace!(
"get_blanket_impls: Considering impl for trait '{:?}' {:?}",
trait_def_id, impl_def_id
trait_def_id,
impl_def_id
);
let trait_ref = self.cx.tcx.impl_trait_ref(impl_def_id).unwrap();
let is_param = matches!(trait_ref.self_ty().kind(), ty::Param(_));
......@@ -50,9 +51,11 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
// FIXME(eddyb) ignoring `obligations` might cause false positives.
drop(obligations);
debug!(
trace!(
"invoking predicate_may_hold: param_env={:?}, trait_ref={:?}, ty={:?}",
param_env, trait_ref, ty
param_env,
trait_ref,
ty
);
let predicates = self
.cx
......
......@@ -447,9 +447,9 @@ fn merge_attrs(
}
let (merged_attrs, cfg) = merge_attrs(cx, parent_module.into(), load_attrs(cx, did), attrs);
debug!("merged_attrs={:?}", merged_attrs);
trace!("merged_attrs={:?}", merged_attrs);
debug!("build_impl: impl {:?} for {:?}", trait_.def_id(), for_.def_id());
trace!("build_impl: impl {:?} for {:?}", trait_.def_id(), for_.def_id());
ret.push(clean::Item::from_def_id_and_attrs_and_parts(
did,
None,
......
......@@ -1406,7 +1406,7 @@ fn normalize(cx: &mut DocContext<'tcx>, ty: Ty<'_>) -> Option<Ty<'tcx>> {
impl<'tcx> Clean<Type> for Ty<'tcx> {
fn clean(&self, cx: &mut DocContext<'_>) -> Type {
debug!("cleaning type: {:?}", self);
trace!("cleaning type: {:?}", self);
let ty = normalize(cx, self).unwrap_or(self);
match *ty.kind() {
ty::Never => Never,
......
......@@ -461,60 +461,20 @@ pub fn from_def_id_and_attrs_and_parts(
.map_or(&[][..], |v| v.as_slice())
.iter()
.filter_map(|ItemLink { link: s, link_text, did, ref fragment }| {
match did {
Some(did) => {
if let Ok((mut href, ..)) = href(*did, cx) {
if let Some(ref fragment) = *fragment {
href.push('#');
href.push_str(fragment);
}
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href,
})
} else {
None
}
}
// FIXME(83083): using fragments as a side-channel for
// primitive names is very unfortunate
None => {
let relative_to = &cx.current;
if let Some(ref fragment) = *fragment {
let url = match cx.cache().extern_locations.get(&self.def_id.krate()) {
Some(&ExternalLocation::Local) => {
if relative_to[0] == "std" {
let depth = relative_to.len() - 1;
"../".repeat(depth)
} else {
let depth = relative_to.len();
format!("{}std/", "../".repeat(depth))
}
}
Some(ExternalLocation::Remote(ref s)) => {
format!("{}/std/", s.trim_end_matches('/'))
}
Some(ExternalLocation::Unknown) | None => {
format!("{}/std/", crate::DOC_RUST_LANG_ORG_CHANNEL)
}
};
// This is a primitive so the url is done "by hand".
let tail = fragment.find('#').unwrap_or_else(|| fragment.len());
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: format!(
"{}primitive.{}.html{}",
url,
&fragment[..tail],
&fragment[tail..]
),
})
} else {
panic!("This isn't a primitive?!");
}
debug!(?did);
if let Ok((mut href, ..)) = href(*did, cx) {
debug!(?href);
if let Some(ref fragment) = *fragment {
href.push('#');
href.push_str(fragment);
}
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href,
})
} else {
None
}
})
.collect()
......@@ -531,18 +491,10 @@ pub fn from_def_id_and_attrs_and_parts(
.get(&self.def_id)
.map_or(&[][..], |v| v.as_slice())
.iter()
.filter_map(|ItemLink { link: s, link_text, did, fragment }| {
// FIXME(83083): using fragments as a side-channel for
// primitive names is very unfortunate
if did.is_some() || fragment.is_some() {
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: String::new(),
})
} else {
None
}
.map(|ItemLink { link: s, link_text, .. }| RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: String::new(),
})
.collect()
}
......@@ -963,7 +915,7 @@ fn from_iter<T>(iter: T) -> Self
crate other_attrs: Vec<ast::Attribute>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
/// A link that has not yet been rendered.
///
/// This link will be turned into a rendered link by [`Item::links`].
......@@ -975,7 +927,7 @@ fn from_iter<T>(iter: T) -> Self
/// This may not be the same as `link` if there was a disambiguator
/// in an intra-doc link (e.g. \[`fn@f`\])
pub(crate) link_text: String,
pub(crate) did: Option<DefId>,
pub(crate) did: DefId,
/// The url fragment to append to the link
pub(crate) fragment: Option<String>,
}
......@@ -1802,6 +1754,39 @@ impl PrimitiveType {
Never => sym::never,
}
}
/// Returns the DefId of the module with `doc(primitive)` for this primitive type.
/// Panics if there is no such module.
///
/// This gives precedence to primitives defined in the current crate, and deprioritizes primitives defined in `core`,
/// but otherwise, if multiple crates define the same primitive, there is no guarantee of which will be picked.
/// In particular, if a crate depends on both `std` and another crate that also defines `doc(primitive)`, then
/// it's entirely random whether `std` or the other crate is picked. (no_std crates are usually fine unless multiple dependencies define a primitive.)
crate fn primitive_locations(tcx: TyCtxt<'_>) -> &FxHashMap<PrimitiveType, DefId> {
static PRIMITIVE_LOCATIONS: OnceCell<FxHashMap<PrimitiveType, DefId>> = OnceCell::new();
PRIMITIVE_LOCATIONS.get_or_init(|| {
let mut primitive_locations = FxHashMap::default();
// NOTE: technically this misses crates that are only passed with `--extern` and not loaded when checking the crate.
// This is a degenerate case that I don't plan to support.
for &crate_num in tcx.crates(()) {
let e = ExternalCrate { crate_num };
let crate_name = e.name(tcx);
debug!(?crate_num, ?crate_name);
for &(def_id, prim) in &e.primitives(tcx) {
// HACK: try to link to std instead where possible
if crate_name == sym::core && primitive_locations.contains_key(&prim) {
continue;
}
primitive_locations.insert(prim, def_id);
}
}
let local_primitives = ExternalCrate { crate_num: LOCAL_CRATE }.primitives(tcx);
for (def_id, prim) in local_primitives {
primitive_locations.insert(prim, def_id);
}
primitive_locations
})
}
}
impl From<ast::IntTy> for PrimitiveType {
......
......@@ -6,7 +6,7 @@
use rustc_middle::ty::TyCtxt;
use rustc_span::symbol::sym;
use crate::clean::{self, GetDefId, ItemId};
use crate::clean::{self, GetDefId, ItemId, PrimitiveType};
use crate::config::RenderOptions;
use crate::fold::DocFolder;
use crate::formats::item_type::ItemType;
......@@ -159,17 +159,16 @@ impl Cache {
self.external_paths.insert(e.def_id(), (vec![name.to_string()], ItemType::Module));
}
// Cache where all known primitives have their documentation located.
//
// Favor linking to as local extern as possible, so iterate all crates in
// reverse topological order.
for &e in krate.externs.iter().rev() {
for &(def_id, prim) in &e.primitives(tcx) {
self.primitive_locations.insert(prim, def_id);
}
}
for &(def_id, prim) in &krate.primitives {
self.primitive_locations.insert(prim, def_id);
// FIXME: avoid this clone (requires implementing Default manually)
self.primitive_locations = PrimitiveType::primitive_locations(tcx).clone();
for (prim, &def_id) in &self.primitive_locations {
let crate_name = tcx.crate_name(def_id.krate);
// Recall that we only allow primitive modules to be at the root-level of the crate.
// If that restriction is ever lifted, this will have to include the relative paths instead.
self.external_paths.insert(
def_id,
(vec![crate_name.to_string(), prim.as_sym().to_string()], ItemType::Primitive),
);
}
krate = CacheBuilder { tcx, cache: self }.fold_crate(krate);
......
......@@ -509,7 +509,11 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
}
if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private {
if !did.is_local()
&& !cache.access_levels.is_public(did)
&& !cache.document_private
&& !cache.primitive_locations.values().any(|&id| id == did)
{
return Err(HrefError::Private);
}
......@@ -517,6 +521,7 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
Some(&(ref fqp, shortty)) => (fqp, shortty, {
let module_fqp = to_module_fqp(shortty, fqp);
debug!(?fqp, ?shortty, ?module_fqp);
href_relative_parts(module_fqp, relative_to)
}),
None => {
......@@ -548,6 +553,7 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
url_parts.insert(0, root);
}
}
debug!(?url_parts);
let last = &fqp.last().unwrap()[..];
let filename;
match shortty {
......@@ -742,7 +748,7 @@ fn fmt_type<'cx>(
use_absolute: bool,
cx: &'cx Context<'_>,
) -> fmt::Result {
debug!("fmt_type(t = {:?})", t);
trace!("fmt_type(t = {:?})", t);
match *t {
clean::Generic(name) => write!(f, "{}", name),
......
......@@ -30,9 +30,7 @@ pub(super) fn convert_item(&self, item: clean::Item) -> Option<Item> {
.get(&item.def_id)
.into_iter()
.flatten()
.filter_map(|clean::ItemLink { link, did, .. }| {
did.map(|did| (link.clone(), from_item_id(did.into())))
})
.map(|clean::ItemLink { link, did, .. }| (link.clone(), from_item_id((*did).into())))
.collect();
let docs = item.attrs.collapsed_doc_value();
let attrs = item
......
......@@ -14,7 +14,7 @@
};
use rustc_hir::def_id::{CrateNum, DefId};
use rustc_middle::ty::TyCtxt;
use rustc_middle::{bug, ty};
use rustc_middle::{bug, span_bug, ty};
use rustc_resolve::ParentScope;
use rustc_session::lint::Lint;
use rustc_span::hygiene::{MacroKind, SyntaxContext};
......@@ -98,14 +98,10 @@ fn name(self, tcx: TyCtxt<'_>) -> Symbol {
}
}
fn def_id(self) -> DefId {
self.opt_def_id().expect("called def_id() on a primitive")
}
fn opt_def_id(self) -> Option<DefId> {
fn def_id(self, tcx: TyCtxt<'_>) -> DefId {
match self {
Res::Def(_, id) => Some(id),
Res::Primitive(_) => None,
Res::Def(_, id) => id,
Res::Primitive(prim) => *PrimitiveType::primitive_locations(tcx).get(&prim).unwrap(),
}
}
......@@ -237,10 +233,7 @@ enum AnchorFailure {
/// link, Rustdoc disallows having a user-specified anchor.
///
/// Most of the time this is fine, because you can just link to the page of
/// the item if you want to provide your own anchor. For primitives, though,
/// rustdoc uses the anchor as a side channel to know which page to link to;
/// it doesn't show up in the generated link. Ideally, rustdoc would remove
/// this limitation, allowing you to link to subheaders on primitives.
/// the item if you want to provide your own anchor.
RustdocAnchorConflict(Res),
}
......@@ -388,7 +381,7 @@ fn resolve_primitive_associated_item(
ty::AssocKind::Const => "associatedconstant",
ty::AssocKind::Type => "associatedtype",
};
let fragment = format!("{}#{}.{}", prim_ty.as_sym(), out, item_name);
let fragment = format!("{}.{}", out, item_name);
(Res::Primitive(prim_ty), fragment, Some((kind.as_def_kind(), item.def_id)))
})
})
......@@ -475,14 +468,6 @@ fn resolve<'path>(
return handle_variant(self.cx, res, extra_fragment);
}
// Not a trait item; just return what we found.
Res::Primitive(ty) => {
if extra_fragment.is_some() {
return Err(ErrorKind::AnchorFailure(
AnchorFailure::RustdocAnchorConflict(res),
));
}
return Ok((res, Some(ty.as_sym().to_string())));
}
_ => return Ok((res, extra_fragment.clone())),
}
}
......@@ -517,6 +502,8 @@ fn resolve<'path>(
let (res, fragment, side_channel) =
self.resolve_associated_item(ty_res, item_name, ns, module_id)?;
let result = if extra_fragment.is_some() {
// NOTE: can never be a primitive since `side_channel.is_none()` only when `res`
// is a trait (and the side channel DefId is always an associated item).
let diag_res = side_channel.map_or(res, |(k, r)| Res::Def(k, r));
Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(diag_res)))
} else {
......@@ -1152,7 +1139,7 @@ fn resolve_link(
module_id = DefId { krate, index: CRATE_DEF_INDEX };
}
let (mut res, mut fragment) = self.resolve_with_disambiguator_cached(
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
ResolutionInfo {
module_id,
dis: disambiguator,
......@@ -1174,16 +1161,7 @@ fn resolve_link(
if let Some(prim) = resolve_primitive(path_str, TypeNS) {
// `prim@char`
if matches!(disambiguator, Some(Disambiguator::Primitive)) {
if fragment.is_some() {
anchor_failure(
self.cx,
diag_info,
AnchorFailure::RustdocAnchorConflict(prim),
);
return None;
}
res = prim;
fragment = Some(prim.name(self.cx.tcx).to_string());
} else {
// `[char]` when a `char` module is in scope
let candidates = vec![res, prim];
......@@ -1303,12 +1281,17 @@ fn resolve_link(
}
}
Some(ItemLink { link: ori_link.link, link_text, did: None, fragment })
Some(ItemLink {
link: ori_link.link,
link_text,
did: res.def_id(self.cx.tcx),
fragment,
})
}
Res::Def(kind, id) => {
verify(kind, id)?;
let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
Some(ItemLink { link: ori_link.link, link_text, did: Some(id), fragment })
Some(ItemLink { link: ori_link.link, link_text, did: id, fragment })
}
}
}
......@@ -2069,8 +2052,11 @@ fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: A
diag.span_label(sp, "invalid anchor");
}
if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure {
diag.note("this restriction may be lifted in a future release");
diag.note("see https://github.com/rust-lang/rust/issues/83083 for more information");
if let Some(sp) = sp {
span_bug!(sp, "anchors should be allowed now");
} else {
bug!("anchors should be allowed now");
}
}
});
}
......@@ -2198,10 +2184,11 @@ fn handle_variant(
use rustc_middle::ty::DefIdTree;
if extra_fragment.is_some() {
// NOTE: `res` can never be a primitive since this function is only called when `tcx.def_kind(res) == DefKind::Variant`.
return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res)));
}
cx.tcx
.parent(res.def_id())
.parent(res.def_id(cx.tcx))
.map(|parent| {
let parent_def = Res::Def(DefKind::Enum, parent);
let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap());
......
......@@ -2,8 +2,9 @@ const QUERY = 'str,u8';
const EXPECTED = {
'others': [
{ 'path': 'std', 'name': 'str' },
{ 'path': 'std', 'name': 'u8' },
{ 'path': 'std::ffi', 'name': 'CStr' },
{ 'path': 'std', 'name': 'str', 'href': '../std/primitive.str.html' },
{ 'path': 'std', 'name': 'u8', 'href': '../std/primitive.u8.html' },
{ 'path': 'std', 'name': 'str', 'href': '../std/str/index.html' },
{ 'path': 'std', 'name': 'u8', 'href': '../std/u8/index.html' },
],
};
......@@ -37,13 +37,3 @@ pub fn bar() {}
/// Damn enum's variants: [Enum::A#whatever].
//~^ ERROR `Enum::A#whatever` contains an anchor
pub fn enum_link() {}
/// Primitives?
///
/// [u32#hello]
//~^ ERROR `u32#hello` contains an anchor
pub fn x() {}
/// [prim@usize#x]
//~^ ERROR `prim@usize#x` contains an anchor
pub mod usize {}
error: `prim@usize#x` contains an anchor, but links to builtin types are already anchored
--> $DIR/anchors.rs:47:6
|
LL | /// [prim@usize#x]
| ^^^^^^^^^^--
| |
| invalid anchor
|
note: the lint level is defined here
--> $DIR/anchors.rs:1:9
|
LL | #![deny(rustdoc::broken_intra_doc_links)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this restriction may be lifted in a future release
= note: see https://github.com/rust-lang/rust/issues/83083 for more information
error: `Foo::f#hola` contains an anchor, but links to fields are already anchored
--> $DIR/anchors.rs:25:15
|
......@@ -21,6 +5,12 @@ LL | /// Or maybe [Foo::f#hola].
| ^^^^^^-----
| |
| invalid anchor
|
note: the lint level is defined here
--> $DIR/anchors.rs:1:9
|
LL | #![deny(rustdoc::broken_intra_doc_links)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `hello#people#!` contains multiple anchors
--> $DIR/anchors.rs:31:28
......@@ -38,16 +28,5 @@ LL | /// Damn enum's variants: [Enum::A#whatever].
| |
| invalid anchor
error: `u32#hello` contains an anchor, but links to builtin types are already anchored
--> $DIR/anchors.rs:43:6
|
LL | /// [u32#hello]
| ^^^------
| |
| invalid anchor
|
= note: this restriction may be lifted in a future release
= note: see https://github.com/rust-lang/rust/issues/83083 for more information
error: aborting due to 5 previous errors
error: aborting due to 3 previous errors
// no-prefer-dynamic
// compile-flags: -Cmetadata=aux
#![crate_type = "rlib"]
#![doc(html_root_url = "http://example.com/")]
#![feature(lang_items)]
#![no_std]
#[lang = "eh_personality"]
fn foo() {}
#[panic_handler]
fn bar(_: &core::panic::PanicInfo) -> ! { loop {} }
/// dox
#[doc(primitive = "pointer")]
......
// compile-flags: --crate-type lib --edition 2018
#![feature(no_core)]
#![no_core]
#[doc(primitive = "usize")]
/// This is the built-in type `usize`.
mod usize {
......
// aux-build:primitive-doc.rs
// compile-flags: --extern-html-root-url=primitive_doc=../ -Z unstable-options
// ignore-windows
#![no_std]
#![feature(no_core)]
#![no_core]
extern crate primitive_doc;
// @has 'cross_crate_primitive_doc/fn.foo.html' '//a[@href="../primitive_doc/primitive.usize.html"]' 'usize'
// @has 'cross_crate_primitive_doc/fn.foo.html' '//a[@href="../primitive_doc/primitive.usize.html"]' 'link'
/// [link](usize)
pub fn foo() -> usize { 0 }
......@@ -10,3 +10,15 @@
///
/// To link to [Something#Anchor!]
pub struct SomeOtherType;
/// Primitives?
///
/// [u32#hello]
// @has anchors/fn.x.html
// @has - '//a/@href' '{{channel}}/std/primitive.u32.html#hello'
pub fn x() {}
/// [prim@usize#x]
// @has anchors/usize/index.html
// @has - '//a/@href' '{{channel}}/std/primitive.usize.html#x'
pub mod usize {}
......@@ -2,6 +2,10 @@
#![no_core]
#![crate_type="rlib"]
#[doc(primitive = "char")]
/// Some char docs
mod char {}
#[lang = "char"]
impl char {
pub fn len_utf8(self) -> usize {
......
......@@ -9,8 +9,8 @@
#![crate_type = "rlib"]
// @has prim_methods_external_core/index.html
// @has - '//*[@id="main"]//a[@href="{{channel}}/std/primitive.char.html"]' 'char'
// @has - '//*[@id="main"]//a[@href="{{channel}}/std/primitive.char.html#method.len_utf8"]' 'char::len_utf8'
// @has - '//*[@id="main"]//a[@href="../my_core/primitive.char.html"]' 'char'
// @has - '//*[@id="main"]//a[@href="../my_core/primitive.char.html#method.len_utf8"]' 'char::len_utf8'
//! A [`char`] and its [`char::len_utf8`].
......
......@@ -5,10 +5,13 @@
// @has prim_methods_local/index.html
// @has - '//*[@id="main"]//a[@href="{{channel}}/std/primitive.char.html"]' 'char'
// @has - '//*[@id="main"]//a[@href="{{channel}}/std/primitive.char.html#method.len_utf8"]' 'char::len_utf8'
// @has - '//*[@id="main"]//a[@href="primitive.char.html"]' 'char'
// @has - '//*[@id="main"]//a[@href="primitive.char.html#method.len_utf8"]' 'char::len_utf8'
//! A [`char`] and its [`char::len_utf8`].
//! A [prim@`char`] and its [`char::len_utf8`].
#[doc(primitive = "char")]
mod char {}
#[lang = "char"]
impl char {
......
......@@ -7,8 +7,8 @@
/// [Self::f]
/// [Self::MAX]
// @has intra_link_prim_self/primitive.usize.html
// @has - '//a[@href="{{channel}}/std/primitive.usize.html#method.f"]' 'Self::f'
// @has - '//a[@href="{{channel}}/std/primitive.usize.html#associatedconstant.MAX"]' 'Self::MAX'
// @has - '//a[@href="primitive.usize.html#method.f"]' 'Self::f'
// @has - '//a[@href="primitive.usize.html#associatedconstant.MAX"]' 'Self::MAX'
impl usize {
/// Some docs
pub fn f() {}
......
// aux-build:issue-15318.rs
// ignore-cross-compile
#![no_std]
extern crate issue_15318;
......
#![no_std]
/// Link to [intra-doc link][u8]
// @has 'no_std_primitive/fn.foo.html' '//a[@href="{{channel}}/core/primitive.u8.html"]' 'intra-doc link'
// @has - '//a[@href="{{channel}}/core/primitive.u8.html"]' 'u8'
pub fn foo() -> u8 {}
#![no_std]
#![deny(warnings)]
#![deny(rustdoc::broken_intra_doc_links)]
// @has no_std/fn.foo.html '//a/[@href="{{channel}}/core/primitive.u8.html"]' 'u8'
// @has no_std/fn.foo.html '//a/[@href="{{channel}}/core/primitive.u8.html"]' 'primitive link'
/// Link to [primitive link][u8]
pub fn foo() -> u8 {}
// Test that all primitives can be linked to.
/// [isize] [i8] [i16] [i32] [i64] [i128]
/// [usize] [u8] [u16] [u32] [u64] [u128]
/// [f32] [f64]
/// [char] [bool] [str] [slice] [array] [tuple] [unit]
/// [pointer] [reference] [fn] [never]
pub fn bar() {}
......@@ -30,6 +30,7 @@
// If at all possible you should use intra-doc links to avoid linkcheck issues. These
// are cases where that does not work
// [(generated_documentation_page, &[broken_links])]
#[rustfmt::skip]
const LINKCHECK_EXCEPTIONS: &[(&str, &[&str])] = &[
// These try to link to std::collections, but are defined in alloc
// https://github.com/rust-lang/rust/issues/74481
......@@ -37,6 +38,19 @@
("std/collections/btree_set/struct.BTreeSet.html", &["#insert-and-complex-keys"]),
("alloc/collections/btree_map/struct.BTreeMap.html", &["#insert-and-complex-keys"]),
("alloc/collections/btree_set/struct.BTreeSet.html", &["#insert-and-complex-keys"]),
// These try to link to various things in std, but are defined in core.
// The docs in std::primitive use proper intra-doc links, so these seem fine to special-case.
// Most these are broken because liballoc uses `#[lang_item]` magic to define things on
// primitives that aren't available in core.
("alloc/slice/trait.Join.html", &["#method.join"]),
("alloc/slice/trait.Concat.html", &["#method.concat"]),
("alloc/slice/index.html", &["#method.concat", "#method.join"]),
("alloc/vec/struct.Vec.html", &["#method.sort_by_key", "#method.sort_by_cached_key"]),
("core/primitive.str.html", &["#method.to_ascii_uppercase", "#method.to_ascii_lowercase"]),
("core/primitive.slice.html", &["#method.to_ascii_uppercase", "#method.to_ascii_lowercase",
"core/slice::sort_by_key", "core\\slice::sort_by_key",
"#method.sort_by_cached_key"]),
];
#[rustfmt::skip]
......@@ -376,6 +390,10 @@ fn check(&mut self, file: &Path, report: &mut Report) {
/// Load a file from disk, or from the cache if available.
fn load_file(&mut self, file: &Path, report: &mut Report) -> (String, &FileEntry) {
// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
#[cfg(windows)]
const ERROR_INVALID_NAME: i32 = 123;
let pretty_path =
file.strip_prefix(&self.root).unwrap_or(&file).to_str().unwrap().to_string();
......@@ -392,6 +410,14 @@ fn load_file(&mut self, file: &Path, report: &mut Report) -> (String, &FileEntry
}
Err(e) if e.kind() == ErrorKind::NotFound => FileEntry::Missing,
Err(e) => {
// If a broken intra-doc link contains `::`, on windows, it will cause `ERROR_INVALID_NAME` rather than `NotFound`.
// Explicitly check for that so that the broken link can be allowed in `LINKCHECK_EXCEPTIONS`.
#[cfg(windows)]
if e.raw_os_error() == Some(ERROR_INVALID_NAME)
&& file.as_os_str().to_str().map_or(false, |s| s.contains("::"))
{
return FileEntry::Missing;
}
panic!("unexpected read error for {}: {}", file.display(), e);
}
});
......
......@@ -46,6 +46,7 @@
pub mod extdeps;
pub mod features;
pub mod pal;
pub mod primitive_docs;
pub mod style;
pub mod target_specific_tests;
pub mod ui_tests;
......
......@@ -71,6 +71,7 @@ fn main() {
// Checks that only make sense for the std libs.
check!(pal, &library_path);
check!(primitive_docs, &library_path);
// Checks that need to be done for both the compiler and std libraries.
check!(unit_tests, &src_path);
......
//! Tidy check to make sure `library/{std,core}/src/primitive_docs.rs` are the same file. These are
//! different files so that relative links work properly without having to have `CARGO_PKG_NAME`
//! set, but conceptually they should always be the same.
use std::path::Path;
pub fn check(library_path: &Path, bad: &mut bool) {
let std_name = "std/src/primitive_docs.rs";
let core_name = "core/src/primitive_docs.rs";
let std_contents = std::fs::read_to_string(library_path.join(std_name))
.unwrap_or_else(|e| panic!("failed to read library/{}: {}", std_name, e));
let core_contents = std::fs::read_to_string(library_path.join(core_name))
.unwrap_or_else(|e| panic!("failed to read library/{}: {}", core_name, e));
if std_contents != core_contents {
tidy_error!(bad, "library/{} and library/{} have different contents", core_name, std_name);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册