diff options
| author | bors <bors@rust-lang.org> | 2025-09-25 20:35:49 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2025-09-25 20:35:49 +0000 |
| commit | 7ac0330c6d684d86d6f86fabe601a3defdc3b234 (patch) | |
| tree | 5c1a901b7316ab94dc6ad125c5524a23f86bd101 /src | |
| parent | eabf390b4ceeb34db9f37e97f435134abbcdea92 (diff) | |
| parent | c6d0059fb887c172a9325adae78548c975653572 (diff) | |
| download | rust-7ac0330c6d684d86d6f86fabe601a3defdc3b234.tar.gz rust-7ac0330c6d684d86d6f86fabe601a3defdc3b234.zip | |
Auto merge of #147037 - matthiaskrgr:rollup-xtgqzuu, r=matthiaskrgr
Rollup of 8 pull requests Successful merges: - rust-lang/rust#116882 (rustdoc: hide `#[repr]` if it isn't part of the public ABI) - rust-lang/rust#135771 ([rustdoc] Add support for associated items in "jump to def" feature) - rust-lang/rust#141032 (avoid violating `slice::from_raw_parts` safety contract in `Vec::extract_if`) - rust-lang/rust#142401 (Add proper name mangling for pattern types) - rust-lang/rust#146293 (feat: non-panicking `Vec::try_remove`) - rust-lang/rust#146859 (BTreeMap: Don't leak allocators when initializing nodes) - rust-lang/rust#146924 (Add doc for `NonZero*` const creation) - rust-lang/rust#146933 (Make `render_example_with_highlighting` return an `impl fmt::Display`) r? `@ghost` `@rustbot` modify labels: rollup
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc/rustc/src/symbol-mangling/v0.md | 28 | ||||
| -rw-r--r-- | src/doc/rustdoc/src/advanced-features.md | 20 | ||||
| -rw-r--r-- | src/librustdoc/clean/types.rs | 112 | ||||
| -rw-r--r-- | src/librustdoc/html/highlight.rs | 250 | ||||
| -rw-r--r-- | src/librustdoc/html/markdown.rs | 45 | ||||
| -rw-r--r-- | src/librustdoc/html/render/mod.rs | 180 | ||||
| -rw-r--r-- | src/librustdoc/html/render/print_item.rs | 8 | ||||
| -rw-r--r-- | src/librustdoc/html/render/span_map.rs | 124 | ||||
| -rw-r--r-- | src/tools/miri/tests/pass/vec.rs | 10 |
9 files changed, 431 insertions, 346 deletions
diff --git a/src/doc/rustc/src/symbol-mangling/v0.md b/src/doc/rustc/src/symbol-mangling/v0.md index 109942518fc..2bcc453a532 100644 --- a/src/doc/rustc/src/symbol-mangling/v0.md +++ b/src/doc/rustc/src/symbol-mangling/v0.md @@ -710,6 +710,7 @@ A *placeholder* may occur in circumstances where a type or const value is not re [mut-ptr-type]: #mut-ptr-type [fn-type]: #fn-type [dyn-trait-type]: #dyn-trait-type +[pattern-type]: #pattern-type > type → \ > *[basic-type]* \ @@ -722,6 +723,7 @@ A *placeholder* may occur in circumstances where a type or const value is not re > | *[mut-ptr-type]* \ > | *[fn-type]* \ > | *[dyn-trait-type]* \ +> | *[pattern-type]* \ > | *[path]* \ > | *[backref]* @@ -830,6 +832,23 @@ Remaining primitives are encoded as a crate production, e.g. `C4f128`. [fn-sig]: #fn-sig [abi]: #abi +* `W` — A [pattern-type][pattern-tpye] `u32 is 0..100`. + > <span id="pattern-type">pattern-type</span> → `W` *[pattern-kind]* + > + > <span id="pattern-kind">pattern-kind</span> → \ + > *[range-pattern-kind]* \ + > *[or-pattern-kind]* + > + > <span id="range-pattern-kind">range-pattern-kind</span> → `R` *[const]* *[const]* + > + > <span id="or-pattern-kind">or-pattern-kind</span> → `O` *[pattern-kind]* `E` + + While or patterns can be nested in theory, in practice this does not happen and they are instead flattened. + + Range patterns have a start and end constant that are both included in the range. + The end must be larger than the start (there can be no wraparound). To emulate wraparound, + you need to use an or pattern of the two ranges to the upper limit and from the lower limit. + * `D` — A [trait object][reference-trait-object] `dyn Trait<Assoc=X> + Send + 'a`. > <span id="dyn-trait-type">dyn-trait-type</span> → `D` *[dyn-bounds]* *[lifetime]* @@ -1139,6 +1158,7 @@ The following is a summary of all of the productions of the symbol grammar. > | *[mut-ptr-type]* \ > | *[fn-type]* \ > | *[dyn-trait-type]* \ +> | *[pattern-type]* \ > | *[path]* \ > | *[backref]* > @@ -1152,6 +1172,14 @@ The following is a summary of all of the productions of the symbol grammar. > [mut-ptr-type] → `O` *[type]* \ > [fn-type] → `F` *[fn-sig]* \ > [dyn-trait-type] → `D` *[dyn-bounds]* *[lifetime]* +> [pattern-type] → `W` *[pattern-kind]* +> +> [pattern-kind] → \ +> *[range-pattern-kind]* \ +> *[or-pattern-kind]* +> +> [range-pattern-kind] -> `R` *[const]* *[const]* \ +> [or-pattern-kind] -> `O` *[pattern-kind]* `E` \ > > [namespace] → *[lower]* | *[upper]* > diff --git a/src/doc/rustdoc/src/advanced-features.md b/src/doc/rustdoc/src/advanced-features.md index c02c9aebe7e..f49edb2ac78 100644 --- a/src/doc/rustdoc/src/advanced-features.md +++ b/src/doc/rustdoc/src/advanced-features.md @@ -89,20 +89,30 @@ https://doc.rust-lang.org/stable/std/?search=%s&go_to_first=true This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL to automatically go to the first result. -## `#[repr(transparent)]`: Documenting the transparent representation +## `#[repr(...)]`: Documenting the representation of a type + +Generally, rustdoc only displays the representation of a given type if none of its variants are +`#[doc(hidden)]` and if all of its fields are public and not `#[doc(hidden)]` since it's likely +not meant to be considered part of the public ABI otherwise. + +Note that there's no way to overwrite that heuristic and force rustdoc to show the representation +regardless. + +### `#[repr(transparent)]` You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and in the [Rustonomicon][repr-trans-nomicon]. Since this representation is only considered part of the public ABI if the single field with non-trivial -size or alignment is public and if the documentation does not state otherwise, Rustdoc helpfully displays -the attribute if and only if the non-1-ZST field is public or at least one field is public in case all -fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned and zero-sized. +size or alignment is public and if the documentation does not state otherwise, rustdoc helpfully displays +the attribute if and only if the non-1-ZST field is public and not `#[doc(hidden)]` or +– in case all fields are 1-ZST fields — at least one field is public and not `#[doc(hidden)]`. +The term *1-ZST* refers to types that are one-aligned and zero-sized. It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]` if one wishes to declare the representation as private even if the non-1-ZST field is public. However, due to [current limitations][cross-crate-cfg-doc], this method is not always guaranteed to work. -Therefore, if you would like to do so, you should always write it down in prose independently of whether +Therefore, if you would like to do so, you should always write that down in prose independently of whether you use `cfg_attr` or not. [repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index bd3f4e9a6f2..ff513c71035 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -794,50 +794,6 @@ impl Item { Some(tcx.visibility(def_id)) } - /// Get a list of attributes excluding `#[repr]` to display. - /// - /// Only used by the HTML output-format. - fn attributes_without_repr(&self) -> Vec<String> { - self.attrs - .other_attrs - .iter() - .filter_map(|attr| match attr { - hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => { - Some(format!("#[unsafe(link_section = \"{name}\")]")) - } - hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => { - Some("#[unsafe(no_mangle)]".to_string()) - } - hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => { - Some(format!("#[unsafe(export_name = \"{name}\")]")) - } - hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => { - Some("#[non_exhaustive]".to_string()) - } - _ => None, - }) - .collect() - } - - /// Get a list of attributes to display on this item. - /// - /// Only used by the HTML output-format. - pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> { - let mut attrs = self.attributes_without_repr(); - - if let Some(repr_attr) = self.repr(tcx, cache) { - attrs.push(repr_attr); - } - attrs - } - - /// Returns a stringified `#[repr(...)]` attribute. - /// - /// Only used by the HTML output-format. - pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> { - repr_attributes(tcx, cache, self.def_id()?, self.type_()) - } - pub fn is_doc_hidden(&self) -> bool { self.attrs.is_doc_hidden() } @@ -847,74 +803,6 @@ impl Item { } } -/// Return a string representing the `#[repr]` attribute if present. -/// -/// Only used by the HTML output-format. -pub(crate) fn repr_attributes( - tcx: TyCtxt<'_>, - cache: &Cache, - def_id: DefId, - item_type: ItemType, -) -> Option<String> { - use rustc_abi::IntegerType; - - if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) { - return None; - } - let adt = tcx.adt_def(def_id); - let repr = adt.repr(); - let mut out = Vec::new(); - if repr.c() { - out.push("C"); - } - if repr.transparent() { - // Render `repr(transparent)` iff the non-1-ZST field is public or at least one - // field is public in case all fields are 1-ZST fields. - let render_transparent = cache.document_private - || adt - .all_fields() - .find(|field| { - let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did)); - tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty)) - .is_ok_and(|layout| !layout.is_1zst()) - }) - .map_or_else( - || adt.all_fields().any(|field| field.vis.is_public()), - |field| field.vis.is_public(), - ); - - if render_transparent { - out.push("transparent"); - } - } - if repr.simd() { - out.push("simd"); - } - let pack_s; - if let Some(pack) = repr.pack { - pack_s = format!("packed({})", pack.bytes()); - out.push(&pack_s); - } - let align_s; - if let Some(align) = repr.align { - align_s = format!("align({})", align.bytes()); - out.push(&align_s); - } - let int_s; - if let Some(int) = repr.int { - int_s = match int { - IntegerType::Pointer(is_signed) => { - format!("{}size", if is_signed { 'i' } else { 'u' }) - } - IntegerType::Fixed(size, is_signed) => { - format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8) - } - }; - out.push(&int_s); - } - if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None } -} - #[derive(Clone, Debug)] pub(crate) enum ItemKind { ExternCrateItem { diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 0e06361024b..fad15573cde 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -8,6 +8,7 @@ use std::borrow::Cow; use std::collections::VecDeque; use std::fmt::{self, Display, Write}; +use std::iter; use rustc_data_structures::fx::FxIndexMap; use rustc_lexer::{Cursor, FrontmatterAllowed, LiteralKind, TokenKind}; @@ -15,8 +16,9 @@ use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; use rustc_span::{BytePos, DUMMY_SP, Span}; -use super::format::{self, write_str}; +use super::format; use crate::clean::PrimitiveType; +use crate::display::Joined as _; use crate::html::escape::EscapeBodyText; use crate::html::macro_expansion::ExpandedCode; use crate::html::render::{Context, LinkFromSrc}; @@ -45,92 +47,72 @@ pub(crate) enum Tooltip { CompileFail, ShouldPanic, Edition(Edition), - None, } /// Highlights `src` as an inline example, returning the HTML output. pub(crate) fn render_example_with_highlighting( src: &str, - out: &mut String, - tooltip: Tooltip, + tooltip: Option<&Tooltip>, playground_button: Option<&str>, extra_classes: &[String], -) { - write_header(out, "rust-example-rendered", None, tooltip, extra_classes); - write_code(out, src, None, None, None); - write_footer(out, playground_button); +) -> impl Display { + fmt::from_fn(move |f| { + write_header("rust-example-rendered", tooltip, extra_classes).fmt(f)?; + write_code(f, src, None, None, None); + write_footer(playground_button).fmt(f) + }) } -fn write_header( - out: &mut String, - class: &str, - extra_content: Option<&str>, - tooltip: Tooltip, - extra_classes: &[String], -) { - write_str( - out, - format_args!( +fn write_header(class: &str, tooltip: Option<&Tooltip>, extra_classes: &[String]) -> impl Display { + fmt::from_fn(move |f| { + write!( + f, "<div class=\"example-wrap{}\">", - match tooltip { - Tooltip::IgnoreAll | Tooltip::IgnoreSome(_) => " ignore", - Tooltip::CompileFail => " compile_fail", - Tooltip::ShouldPanic => " should_panic", - Tooltip::Edition(_) => " edition", - Tooltip::None => "", - } - ), - ); - - if tooltip != Tooltip::None { - let tooltip = fmt::from_fn(|f| match &tooltip { - Tooltip::IgnoreAll => f.write_str("This example is not tested"), - Tooltip::IgnoreSome(platforms) => { - f.write_str("This example is not tested on ")?; - match &platforms[..] { - [] => unreachable!(), - [platform] => f.write_str(platform)?, - [first, second] => write!(f, "{first} or {second}")?, - [platforms @ .., last] => { - for platform in platforms { - write!(f, "{platform}, ")?; + tooltip + .map(|tooltip| match tooltip { + Tooltip::IgnoreAll | Tooltip::IgnoreSome(_) => " ignore", + Tooltip::CompileFail => " compile_fail", + Tooltip::ShouldPanic => " should_panic", + Tooltip::Edition(_) => " edition", + }) + .unwrap_or_default() + )?; + + if let Some(tooltip) = tooltip { + let tooltip = fmt::from_fn(|f| match tooltip { + Tooltip::IgnoreAll => f.write_str("This example is not tested"), + Tooltip::IgnoreSome(platforms) => { + f.write_str("This example is not tested on ")?; + match &platforms[..] { + [] => unreachable!(), + [platform] => f.write_str(platform)?, + [first, second] => write!(f, "{first} or {second}")?, + [platforms @ .., last] => { + for platform in platforms { + write!(f, "{platform}, ")?; + } + write!(f, "or {last}")?; } - write!(f, "or {last}")?; } + Ok(()) } - Ok(()) - } - Tooltip::CompileFail => f.write_str("This example deliberately fails to compile"), - Tooltip::ShouldPanic => f.write_str("This example panics"), - Tooltip::Edition(edition) => write!(f, "This example runs with edition {edition}"), - Tooltip::None => unreachable!(), + Tooltip::CompileFail => f.write_str("This example deliberately fails to compile"), + Tooltip::ShouldPanic => f.write_str("This example panics"), + Tooltip::Edition(edition) => write!(f, "This example runs with edition {edition}"), + }); + + write!(f, "<a href=\"#\" class=\"tooltip\" title=\"{tooltip}\">ⓘ</a>")?; + } + + let classes = fmt::from_fn(|f| { + iter::once("rust") + .chain(Some(class).filter(|class| !class.is_empty())) + .chain(extra_classes.iter().map(String::as_str)) + .joined(" ", f) }); - write_str(out, format_args!("<a href=\"#\" class=\"tooltip\" title=\"{tooltip}\">ⓘ</a>")); - } - if let Some(extra) = extra_content { - out.push_str(extra); - } - if class.is_empty() { - write_str( - out, - format_args!( - "<pre class=\"rust{}{}\">", - if extra_classes.is_empty() { "" } else { " " }, - extra_classes.join(" ") - ), - ); - } else { - write_str( - out, - format_args!( - "<pre class=\"rust {class}{}{}\">", - if extra_classes.is_empty() { "" } else { " " }, - extra_classes.join(" ") - ), - ); - } - write_str(out, format_args!("<code>")); + write!(f, "<pre class=\"{classes}\"><code>") + }) } /// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None` @@ -577,8 +559,8 @@ pub(super) fn write_code( }); } -fn write_footer(out: &mut String, playground_button: Option<&str>) { - write_str(out, format_args!("</code></pre>{}</div>", playground_button.unwrap_or_default())); +fn write_footer(playground_button: Option<&str>) -> impl Display { + fmt::from_fn(move |f| write!(f, "</code></pre>{}</div>", playground_button.unwrap_or_default())) } /// How a span of text is classified. Mostly corresponds to token kinds. @@ -1262,6 +1244,64 @@ fn string<W: Write>( } } +fn generate_link_to_def( + out: &mut impl Write, + text_s: &str, + klass: Class, + href_context: &Option<HrefContext<'_, '_>>, + def_span: Span, + open_tag: bool, +) -> bool { + if let Some(href_context) = href_context + && let Some(href) = + href_context.context.shared.span_correspondence_map.get(&def_span).and_then(|href| { + let context = href_context.context; + // FIXME: later on, it'd be nice to provide two links (if possible) for all items: + // one to the documentation page and one to the source definition. + // FIXME: currently, external items only generate a link to their documentation, + // a link to their definition can be generated using this: + // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338 + match href { + LinkFromSrc::Local(span) => { + context.href_from_span_relative(*span, &href_context.current_href) + } + LinkFromSrc::External(def_id) => { + format::href_with_root_path(*def_id, context, Some(href_context.root_path)) + .ok() + .map(|(url, _, _)| url) + } + LinkFromSrc::Primitive(prim) => format::href_with_root_path( + PrimitiveType::primitive_locations(context.tcx())[prim], + context, + Some(href_context.root_path), + ) + .ok() + .map(|(url, _, _)| url), + LinkFromSrc::Doc(def_id) => { + format::href_with_root_path(*def_id, context, Some(href_context.root_path)) + .ok() + .map(|(doc_link, _, _)| doc_link) + } + } + }) + { + if !open_tag { + // We're already inside an element which has the same klass, no need to give it + // again. + write!(out, "<a href=\"{href}\">{text_s}").unwrap(); + } else { + let klass_s = klass.as_html(); + if klass_s.is_empty() { + write!(out, "<a href=\"{href}\">{text_s}").unwrap(); + } else { + write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap(); + } + } + return true; + } + false +} + /// This function writes `text` into `out` with some modifications depending on `klass`: /// /// * If `klass` is `None`, `text` is written into `out` with no modification. @@ -1291,10 +1331,14 @@ fn string_without_closing_tag<T: Display>( return Some("</span>"); }; + let mut added_links = false; let mut text_s = text.to_string(); if text_s.contains("::") { + let mut span = def_span.with_hi(def_span.lo()); text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| { + span = span.with_hi(span.hi() + BytePos(t.len() as _)); match t { + "::" => write!(&mut path, "::"), "self" | "Self" => write!( &mut path, "<span class=\"{klass}\">{t}</span>", @@ -1307,58 +1351,24 @@ fn string_without_closing_tag<T: Display>( klass = Class::KeyWord.as_html(), ) } - t => write!(&mut path, "{t}"), + t => { + if !t.is_empty() + && generate_link_to_def(&mut path, t, klass, href_context, span, open_tag) + { + added_links = true; + write!(&mut path, "</a>") + } else { + write!(&mut path, "{t}") + } + } } .expect("Failed to build source HTML path"); + span = span.with_lo(span.lo() + BytePos(t.len() as _)); path }); } - if let Some(href_context) = href_context - && let Some(href) = href_context.context.shared.span_correspondence_map.get(&def_span) - && let Some(href) = { - let context = href_context.context; - // FIXME: later on, it'd be nice to provide two links (if possible) for all items: - // one to the documentation page and one to the source definition. - // FIXME: currently, external items only generate a link to their documentation, - // a link to their definition can be generated using this: - // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338 - match href { - LinkFromSrc::Local(span) => { - context.href_from_span_relative(*span, &href_context.current_href) - } - LinkFromSrc::External(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|(url, _, _)| url) - } - LinkFromSrc::Primitive(prim) => format::href_with_root_path( - PrimitiveType::primitive_locations(context.tcx())[prim], - context, - Some(href_context.root_path), - ) - .ok() - .map(|(url, _, _)| url), - LinkFromSrc::Doc(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|(doc_link, _, _)| doc_link) - } - } - } - { - if !open_tag { - // We're already inside an element which has the same klass, no need to give it - // again. - write!(out, "<a href=\"{href}\">{text_s}").unwrap(); - } else { - let klass_s = klass.as_html(); - if klass_s.is_empty() { - write!(out, "<a href=\"{href}\">{text_s}").unwrap(); - } else { - write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap(); - } - } + if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) { return Some("</a>"); } if !open_tag { diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 4addf2c3c96..7065de14c8e 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -321,31 +321,34 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> { )) }); - let tooltip = if ignore == Ignore::All { - highlight::Tooltip::IgnoreAll - } else if let Ignore::Some(platforms) = ignore { - highlight::Tooltip::IgnoreSome(platforms) - } else if compile_fail { - highlight::Tooltip::CompileFail - } else if should_panic { - highlight::Tooltip::ShouldPanic - } else if explicit_edition { - highlight::Tooltip::Edition(edition) - } else { - highlight::Tooltip::None + let tooltip = { + use highlight::Tooltip::*; + + if ignore == Ignore::All { + Some(IgnoreAll) + } else if let Ignore::Some(platforms) = ignore { + Some(IgnoreSome(platforms)) + } else if compile_fail { + Some(CompileFail) + } else if should_panic { + Some(ShouldPanic) + } else if explicit_edition { + Some(Edition(edition)) + } else { + None + } }; // insert newline to clearly separate it from the // previous block so we can shorten the html output - let mut s = String::new(); - s.push('\n'); - - highlight::render_example_with_highlighting( - &text, - &mut s, - tooltip, - playground_button.as_deref(), - &added_classes, + let s = format!( + "\n{}", + highlight::render_example_with_highlighting( + &text, + tooltip.as_ref(), + playground_button.as_deref(), + &added_classes, + ) ); Some(Event::Html(s.into())) } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 6d684449b6d..9a14137a6e8 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -51,7 +51,9 @@ use askama::Template; use itertools::Either; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; -use rustc_hir::attrs::{DeprecatedSince, Deprecation}; +use rustc_hir as hir; +use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation}; +use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ConstStability, Mutability, RustcVersion, StabilityLevel, StableSince}; use rustc_middle::ty::print::PrintTraitRefExt; @@ -1310,43 +1312,6 @@ fn render_assoc_item( }) } -struct CodeAttribute(String); - -fn render_code_attribute(prefix: &str, code_attr: CodeAttribute, w: &mut impl fmt::Write) { - write!( - w, - "<div class=\"code-attribute\">{prefix}{attr}</div>", - prefix = prefix, - attr = code_attr.0 - ) - .unwrap(); -} - -// When an attribute is rendered inside a <code> tag, it is formatted using -// a div to produce a newline after it. -fn render_attributes_in_code( - w: &mut impl fmt::Write, - it: &clean::Item, - prefix: &str, - cx: &Context<'_>, -) { - for attr in it.attributes(cx.tcx(), cx.cache()) { - render_code_attribute(prefix, CodeAttribute(attr), w); - } -} - -/// used for type aliases to only render their `repr` attribute. -fn render_repr_attributes_in_code( - w: &mut impl fmt::Write, - cx: &Context<'_>, - def_id: DefId, - item_type: ItemType, -) { - if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) { - render_code_attribute("", CodeAttribute(repr), w); - } -} - #[derive(Copy, Clone)] enum AssocItemLink<'a> { Anchor(Option<&'a str>), @@ -2959,3 +2924,142 @@ fn render_call_locations<W: fmt::Write>( w.write_str("</div>") } + +fn render_attributes_in_code( + w: &mut impl fmt::Write, + item: &clean::Item, + prefix: &str, + cx: &Context<'_>, +) { + for attr in &item.attrs.other_attrs { + let hir::Attribute::Parsed(kind) = attr else { continue }; + let attr = match kind { + AttributeKind::LinkSection { name, .. } => { + Cow::Owned(format!("#[unsafe(link_section = {})]", Escape(&format!("{name:?}")))) + } + AttributeKind::NoMangle(..) => Cow::Borrowed("#[unsafe(no_mangle)]"), + AttributeKind::ExportName { name, .. } => { + Cow::Owned(format!("#[unsafe(export_name = {})]", Escape(&format!("{name:?}")))) + } + AttributeKind::NonExhaustive(..) => Cow::Borrowed("#[non_exhaustive]"), + _ => continue, + }; + render_code_attribute(prefix, attr.as_ref(), w); + } + + if let Some(def_id) = item.def_id() + && let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) + { + render_code_attribute(prefix, &repr, w); + } +} + +fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) { + if let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) { + render_code_attribute("", &repr, w); + } +} + +fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) { + write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap(); +} + +/// Compute the *public* `#[repr]` of the item given by `DefId`. +/// +/// Read more about it here: +/// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>. +fn repr_attribute<'tcx>( + tcx: TyCtxt<'tcx>, + cache: &Cache, + def_id: DefId, +) -> Option<Cow<'static, str>> { + let adt = match tcx.def_kind(def_id) { + DefKind::Struct | DefKind::Enum | DefKind::Union => tcx.adt_def(def_id), + _ => return None, + }; + let repr = adt.repr(); + + let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id); + let is_public_field = |field: &ty::FieldDef| { + (cache.document_private || field.vis.is_public()) && is_visible(field.did) + }; + + if repr.transparent() { + // The transparent repr is public iff the non-1-ZST field is public and visible or + // – in case all fields are 1-ZST fields — at least one field is public and visible. + let is_public = 'is_public: { + // `#[repr(transparent)]` can only be applied to structs and single-variant enums. + let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant + + if !is_visible(var.def_id) { + break 'is_public false; + } + + // Side note: There can only ever be one or zero non-1-ZST fields. + let non_1zst_field = var.fields.iter().find(|field| { + let ty = ty::TypingEnv::post_analysis(tcx, field.did) + .as_query_input(tcx.type_of(field.did).instantiate_identity()); + tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst()) + }); + + match non_1zst_field { + Some(field) => is_public_field(field), + None => var.fields.is_empty() || var.fields.iter().any(is_public_field), + } + }; + + // Since the transparent repr can't have any other reprs or + // repr modifiers beside it, we can safely return early here. + return is_public.then(|| "#[repr(transparent)]".into()); + } + + // Fast path which avoids looking through the variants and fields in + // the common case of no `#[repr]` or in the case of `#[repr(Rust)]`. + // FIXME: This check is not very robust / forward compatible! + if !repr.c() + && !repr.simd() + && repr.int.is_none() + && repr.pack.is_none() + && repr.align.is_none() + { + return None; + } + + // The repr is public iff all components are public and visible. + let is_public = adt + .variants() + .iter() + .all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field)); + if !is_public { + return None; + } + + let mut result = Vec::<Cow<'_, _>>::new(); + + if repr.c() { + result.push("C".into()); + } + if repr.simd() { + result.push("simd".into()); + } + if let Some(int) = repr.int { + let prefix = if int.is_signed() { 'i' } else { 'u' }; + let int = match int { + rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"), + rustc_abi::IntegerType::Fixed(int, _) => { + format!("{prefix}{}", int.size().bytes() * 8) + } + }; + result.push(int.into()); + } + + // Render modifiers last. + if let Some(pack) = repr.pack { + result.push(format!("packed({})", pack.bytes()).into()); + } + if let Some(align) = repr.align { + result.push(format!("align({})", align.bytes()).into()); + } + + (!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into()) +} diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index afa438f2596..adfc7481c73 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -21,7 +21,7 @@ use super::{ collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference, item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls, render_assoc_item, render_assoc_items, render_attributes_in_code, render_impl, - render_repr_attributes_in_code, render_rightside, render_stability_since_raw, + render_repr_attribute_in_code, render_rightside, render_stability_since_raw, render_stability_since_raw_with_extra, write_section_heading, }; use crate::clean; @@ -1555,7 +1555,7 @@ impl<'clean> DisplayEnum<'clean> { wrap_item(w, |w| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum); + render_repr_attribute_in_code(w, cx, self.def_id); } else { render_attributes_in_code(w, it, "", cx); } @@ -2017,7 +2017,7 @@ impl<'a> DisplayStruct<'a> { wrap_item(w, |w| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct); + render_repr_attribute_in_code(w, cx, self.def_id); } else { render_attributes_in_code(w, it, "", cx); } @@ -2371,7 +2371,7 @@ fn render_union( fmt::from_fn(move |mut f| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attributes_in_code(f, cx, def_id, ItemType::Union); + render_repr_attribute_in_code(f, cx, def_id); } else { render_attributes_in_code(f, it, "", cx); } diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 8bc2e0bd957..ef7ce33298d 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -2,11 +2,9 @@ use std::path::{Path, PathBuf}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{ - ExprKind, HirId, Item, ItemKind, Mod, Node, Pat, PatExpr, PatExprKind, PatKind, QPath, -}; +use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; +use rustc_hir::intravisit::{self, Visitor, VisitorExt}; +use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::hygiene::MacroKind; @@ -67,7 +65,7 @@ struct SpanMapVisitor<'tcx> { impl SpanMapVisitor<'_> { /// This function is where we handle `hir::Path` elements and add them into the "span map". - fn handle_path(&mut self, path: &rustc_hir::Path<'_>) { + fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) { match path.res { // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. // Would be nice to support them too alongside the other `DefKind` @@ -79,24 +77,36 @@ impl SpanMapVisitor<'_> { LinkFromSrc::External(def_id) }; // In case the path ends with generics, we remove them from the span. - let span = path - .segments - .last() - .map(|last| { - // In `use` statements, the included item is not in the path segments. - // However, it doesn't matter because you can't have generics on `use` - // statements. - if path.span.contains(last.ident.span) { - path.span.with_hi(last.ident.span.hi()) - } else { - path.span - } - }) - .unwrap_or(path.span); + let span = if only_use_last_segment + && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) + { + path_span + } else { + path.segments + .last() + .map(|last| { + // In `use` statements, the included item is not in the path segments. + // However, it doesn't matter because you can't have generics on `use` + // statements. + if path.span.contains(last.ident.span) { + path.span.with_hi(last.ident.span.hi()) + } else { + path.span + } + }) + .unwrap_or(path.span) + }; self.matches.insert(span, link); } Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => { - self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span))); + let path_span = if only_use_last_segment + && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) + { + path_span + } else { + path.span + }; + self.matches.insert(path_span, LinkFromSrc::Local(clean::Span::new(span))); } Res::PrimTy(p) => { // FIXME: Doesn't handle "path-like" primitives like arrays or tuples. @@ -189,31 +199,23 @@ impl SpanMapVisitor<'_> { self.matches.insert(span, link); } } +} - fn handle_pat(&mut self, p: &Pat<'_>) { - let mut check_qpath = |qpath, hir_id| match qpath { - QPath::TypeRelative(_, path) if matches!(path.res, Res::Err) => { - self.infer_id(path.hir_id, Some(hir_id), qpath.span()); - } - QPath::Resolved(_, path) => self.handle_path(path), - _ => {} - }; - match p.kind { - PatKind::Binding(_, _, _, Some(p)) => self.handle_pat(p), - PatKind::Struct(qpath, _, _) | PatKind::TupleStruct(qpath, _, _) => { - check_qpath(qpath, p.hir_id) - } - PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, .. }) => { - check_qpath(*qpath, *hir_id) - } - PatKind::Or(pats) => { - for pat in pats { - self.handle_pat(pat); - } - } - _ => {} +// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without +// panicking. +fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> { + for (_, node) in tcx.hir_parent_iter(hir_id) { + // FIXME: associated type impl items don't have an associated body, so we don't handle + // them currently. + if let Node::ImplItem(impl_item) = node + && matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_)) + { + return None; + } else if let Some((def_id, _)) = node.associated_body() { + return Some(def_id); } } + None } impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { @@ -227,12 +229,42 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { if self.handle_macro(path.span) { return; } - self.handle_path(path); + self.handle_path(path, false); intravisit::walk_path(self, path); } - fn visit_pat(&mut self, p: &Pat<'tcx>) { - self.handle_pat(p); + fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: Span) { + match *qpath { + QPath::TypeRelative(qself, path) => { + if matches!(path.res, Res::Err) { + let tcx = self.tcx; + if let Some(body_id) = hir_enclosing_body_owner(tcx, id) { + let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id()); + let path = rustc_hir::Path { + // We change the span to not include parens. + span: path.ident.span, + res: typeck_results.qpath_res(qpath, id), + segments: &[], + }; + self.handle_path(&path, false); + } + } else { + self.infer_id(path.hir_id, Some(id), path.ident.span); + } + + rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); + self.visit_path_segment(path); + } + QPath::Resolved(maybe_qself, path) => { + self.handle_path(path, true); + + rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself); + if !self.handle_macro(path.span) { + intravisit::walk_path(self, path); + } + } + _ => {} + } } fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) { diff --git a/src/tools/miri/tests/pass/vec.rs b/src/tools/miri/tests/pass/vec.rs index 3e526813bb4..8b1b1e143b1 100644 --- a/src/tools/miri/tests/pass/vec.rs +++ b/src/tools/miri/tests/pass/vec.rs @@ -169,6 +169,15 @@ fn miri_issue_2759() { input.replace_range(0..0, "0"); } +/// This was skirting the edge of UB, let's make sure it remains on the sound side. +/// Context: <https://github.com/rust-lang/rust/pull/141032>. +fn extract_if() { + let mut v = vec![Box::new(0u64), Box::new(1u64)]; + for item in v.extract_if(.., |x| **x == 0) { + drop(item); + } +} + fn main() { assert_eq!(vec_reallocate().len(), 5); @@ -199,4 +208,5 @@ fn main() { swap_remove(); reverse(); miri_issue_2759(); + extract_if(); } |
