diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index e56a715e85780..6bbb82897f306 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -596,6 +596,8 @@ fn build_module_items( } pub(crate) fn print_inlined_const(tcx: TyCtxt<'_>, did: DefId) -> String { + // FIXME: Both branches rely on HIR pretty-printing which + // leaks private and doc(hidden) struct fields. if let Some(did) = did.as_local() { let hir_id = tcx.hir().local_def_id_to_hir_id(did); rustc_hir_pretty::id_to_string(&tcx.hir(), hir_id) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 1a4786c9b0664..ef7fbe871fc08 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -37,11 +37,11 @@ use crate::clean::cfg::Cfg; use crate::clean::clean_visibility; use crate::clean::external_path; use crate::clean::inline::{self, print_inlined_const}; -use crate::clean::utils::{is_literal_expr, print_const_expr, print_evaluated_const}; +use crate::clean::utils::is_literal_expr; use crate::core::DocContext; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; -use crate::html::render::Context; +use crate::html::render::{constant::Renderer as ConstantRenderer, Context}; use crate::passes::collect_intra_doc_links::UrlFragment; pub(crate) use self::FnRetTy::*; @@ -2292,8 +2292,8 @@ impl Constant { self.kind.expr(tcx) } - pub(crate) fn value(&self, tcx: TyCtxt<'_>) -> Option { - self.kind.value(tcx) + pub(crate) fn eval_and_render(&self, renderer: &ConstantRenderer<'_, '_>) -> Option { + self.kind.eval_and_render(renderer) } pub(crate) fn is_literal(&self, tcx: TyCtxt<'_>) -> bool { @@ -2307,16 +2307,16 @@ impl ConstantKind { ConstantKind::TyConst { ref expr } => expr.clone(), ConstantKind::Extern { def_id } => print_inlined_const(tcx, def_id), ConstantKind::Local { body, .. } | ConstantKind::Anonymous { body } => { - print_const_expr(tcx, body) + super::utils::print_const_expr(tcx, body) } } } - pub(crate) fn value(&self, tcx: TyCtxt<'_>) -> Option { + pub(crate) fn eval_and_render(&self, renderer: &ConstantRenderer<'_, '_>) -> Option { match *self { ConstantKind::TyConst { .. } | ConstantKind::Anonymous { .. } => None, ConstantKind::Extern { def_id } | ConstantKind::Local { def_id, .. } => { - print_evaluated_const(tcx, def_id) + crate::html::render::eval_and_render_const(def_id, renderer) } } } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 718cbbd2b8374..b3f3f56e226d0 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -16,17 +16,12 @@ use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_middle::mir; -use rustc_middle::mir::interpret::ConstValue; use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, DefIdTree, TyCtxt}; use rustc_span::symbol::{kw, sym, Symbol}; use std::fmt::Write as _; use std::mem; -#[cfg(test)] -mod tests; - pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { let module = crate::visit_ast::RustdocVisitor::new(cx).visit(); @@ -238,6 +233,8 @@ pub(crate) fn print_const(cx: &DocContext<'_>, n: ty::Const<'_>) -> String { let mut s = if let Some(def) = def.as_local() { print_const_expr(cx.tcx, cx.tcx.hir().body_owned_by(def.did)) } else { + // FIXME: This relies on the HIR pretty-printer which leaks private and + // doc(hidden) struct fields. inline::print_inlined_const(cx.tcx, def.did) }; if let Some(promoted) = promoted { @@ -261,69 +258,6 @@ pub(crate) fn print_const(cx: &DocContext<'_>, n: ty::Const<'_>) -> String { } } -pub(crate) fn print_evaluated_const(tcx: TyCtxt<'_>, def_id: DefId) -> Option { - tcx.const_eval_poly(def_id).ok().and_then(|val| { - let ty = tcx.type_of(def_id); - match (val, ty.kind()) { - (_, &ty::Ref(..)) => None, - (ConstValue::Scalar(_), &ty::Adt(_, _)) => None, - (ConstValue::Scalar(_), _) => { - let const_ = mir::ConstantKind::from_value(val, ty); - Some(print_const_with_custom_print_scalar(tcx, const_)) - } - _ => None, - } - }) -} - -fn format_integer_with_underscore_sep(num: &str) -> String { - let num_chars: Vec<_> = num.chars().collect(); - let mut num_start_index = if num_chars.get(0) == Some(&'-') { 1 } else { 0 }; - let chunk_size = match num[num_start_index..].as_bytes() { - [b'0', b'b' | b'x', ..] => { - num_start_index += 2; - 4 - } - [b'0', b'o', ..] => { - num_start_index += 2; - let remaining_chars = num_chars.len() - num_start_index; - if remaining_chars <= 6 { - // don't add underscores to Unix permissions like 0755 or 100755 - return num.to_string(); - } - 3 - } - _ => 3, - }; - - num_chars[..num_start_index] - .iter() - .chain(num_chars[num_start_index..].rchunks(chunk_size).rev().intersperse(&['_']).flatten()) - .collect() -} - -fn print_const_with_custom_print_scalar(tcx: TyCtxt<'_>, ct: mir::ConstantKind<'_>) -> String { - // Use a slightly different format for integer types which always shows the actual value. - // For all other types, fallback to the original `pretty_print_const`. - match (ct, ct.ty().kind()) { - (mir::ConstantKind::Val(ConstValue::Scalar(int), _), ty::Uint(ui)) => { - format!("{}{}", format_integer_with_underscore_sep(&int.to_string()), ui.name_str()) - } - (mir::ConstantKind::Val(ConstValue::Scalar(int), _), ty::Int(i)) => { - let ty = tcx.lift(ct.ty()).unwrap(); - let size = tcx.layout_of(ty::ParamEnv::empty().and(ty)).unwrap().size; - let data = int.assert_bits(size); - let sign_extended_data = size.sign_extend(data) as i128; - format!( - "{}{}", - format_integer_with_underscore_sep(&sign_extended_data.to_string()), - i.name_str() - ) - } - _ => ct.to_string(), - } -} - pub(crate) fn is_literal_expr(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool { if let hir::Node::Expr(expr) = tcx.hir().get(hir_id) { if let hir::ExprKind::Lit(_) = &expr.kind { diff --git a/src/librustdoc/clean/utils/tests.rs b/src/librustdoc/clean/utils/tests.rs deleted file mode 100644 index ebf4b49548394..0000000000000 --- a/src/librustdoc/clean/utils/tests.rs +++ /dev/null @@ -1,41 +0,0 @@ -use super::*; - -#[test] -fn int_format_decimal() { - assert_eq!(format_integer_with_underscore_sep("12345678"), "12_345_678"); - assert_eq!(format_integer_with_underscore_sep("123"), "123"); - assert_eq!(format_integer_with_underscore_sep("123459"), "123_459"); - assert_eq!(format_integer_with_underscore_sep("-12345678"), "-12_345_678"); - assert_eq!(format_integer_with_underscore_sep("-123"), "-123"); - assert_eq!(format_integer_with_underscore_sep("-123459"), "-123_459"); -} - -#[test] -fn int_format_hex() { - assert_eq!(format_integer_with_underscore_sep("0xab3"), "0xab3"); - assert_eq!(format_integer_with_underscore_sep("0xa2345b"), "0xa2_345b"); - assert_eq!(format_integer_with_underscore_sep("0xa2e6345b"), "0xa2e6_345b"); - assert_eq!(format_integer_with_underscore_sep("-0xab3"), "-0xab3"); - assert_eq!(format_integer_with_underscore_sep("-0xa2345b"), "-0xa2_345b"); - assert_eq!(format_integer_with_underscore_sep("-0xa2e6345b"), "-0xa2e6_345b"); -} - -#[test] -fn int_format_binary() { - assert_eq!(format_integer_with_underscore_sep("0o12345671"), "0o12_345_671"); - assert_eq!(format_integer_with_underscore_sep("0o123"), "0o123"); - assert_eq!(format_integer_with_underscore_sep("0o123451"), "0o123451"); - assert_eq!(format_integer_with_underscore_sep("-0o12345671"), "-0o12_345_671"); - assert_eq!(format_integer_with_underscore_sep("-0o123"), "-0o123"); - assert_eq!(format_integer_with_underscore_sep("-0o123451"), "-0o123451"); -} - -#[test] -fn int_format_octal() { - assert_eq!(format_integer_with_underscore_sep("0b101"), "0b101"); - assert_eq!(format_integer_with_underscore_sep("0b101101011"), "0b1_0110_1011"); - assert_eq!(format_integer_with_underscore_sep("0b01101011"), "0b0110_1011"); - assert_eq!(format_integer_with_underscore_sep("-0b101"), "-0b101"); - assert_eq!(format_integer_with_underscore_sep("-0b101101011"), "-0b1_0110_1011"); - assert_eq!(format_integer_with_underscore_sep("-0b01101011"), "-0b0110_1011"); -} diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index c48b25aea4a37..3e9b49ae245c0 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -379,7 +379,11 @@ pub(crate) fn run_global_ctxt( impl_trait_bounds: Default::default(), generated_synthetics: Default::default(), auto_traits, - cache: Cache::new(access_levels, render_options.document_private), + cache: Cache::new( + access_levels, + render_options.document_private, + render_options.document_hidden, + ), inlined: FxHashSet::default(), output_format, render_options, diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 2b2691e53bbcc..7670dd3f5e67d 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -87,6 +87,10 @@ pub(crate) struct Cache { /// This is stored in `Cache` so it doesn't need to be passed through all rustdoc functions. pub(crate) document_private: bool, + /// Whether to document `#[doc(hidden)]` items. + /// This is stored in `Cache` so it doesn't need to be passed through all rustdoc functions. + pub(crate) document_hidden: bool, + /// Crates marked with [`#[doc(masked)]`][doc_masked]. /// /// [doc_masked]: https://doc.rust-lang.org/nightly/unstable-book/language-features/doc-masked.html @@ -132,8 +136,12 @@ struct CacheBuilder<'a, 'tcx> { } impl Cache { - pub(crate) fn new(access_levels: AccessLevels, document_private: bool) -> Self { - Cache { access_levels, document_private, ..Cache::default() } + pub(crate) fn new( + access_levels: AccessLevels, + document_private: bool, + document_hidden: bool, + ) -> Self { + Cache { access_levels, document_private, document_hidden, ..Cache::default() } } /// Populates the `Cache` with more data. The returned `Crate` will be missing some data that was diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 3dee4d1acc819..24202ef956aca 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -654,6 +654,13 @@ pub(crate) fn href_with_root_path( // documented on their parent's page tcx.parent(did) } + DefKind::Field => { + let parent_id = tcx.parent(did); + match tcx.def_kind(parent_id) { + DefKind::Variant => tcx.parent(parent_id), + _ => parent_id, + } + } _ => did, }; let cache = cx.cache(); diff --git a/src/librustdoc/html/render/constant.rs b/src/librustdoc/html/render/constant.rs new file mode 100644 index 0000000000000..e6b6f2ab70557 --- /dev/null +++ b/src/librustdoc/html/render/constant.rs @@ -0,0 +1,486 @@ +use crate::clean::{inline::print_inlined_const, utils::print_const_expr}; +use crate::formats::{cache::Cache, item_type::ItemType}; +use crate::html::{escape::Escape, format::Buffer}; +use rustc_hir::def::{CtorKind, DefKind}; +use rustc_hir::def_id::DefId; +use rustc_middle::mir::interpret::{AllocRange, ConstValue, Scalar}; +use rustc_middle::mir::ConstantKind; +use rustc_middle::ty::{self, util::is_doc_hidden}; +use rustc_middle::ty::{Const, ConstInt, DefIdTree, FieldDef, ParamConst, ScalarInt}; +use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, Visibility}; +use rustc_span::sym; +use rustc_target::abi::Size; +use std::fmt::Write; + +/// Try to evaluate the given constant expression and render it as HTML code or as plain text. +/// +/// `None` is returned if the expression is “too generic”. +/// +/// This textual representation may not actually be lossless or even valid +/// Rust syntax since +/// +/// * overly long arrays & strings and deeply-nested subexpressions are replaced +/// with ellipses +/// * private and `#[doc(hidden)]` struct fields are omitted +/// * `..` is added to (tuple) struct literals if any fields are omitted +/// or if the type is `#[non_exhaustive]` +/// * `_` is used in place of “unsupported” expressions (e.g. pointers) +pub(crate) fn eval_and_render_const(def_id: DefId, renderer: &Renderer<'_, '_>) -> Option { + let value = renderer.tcx().const_eval_poly(def_id).ok()?; + let mut buffer = renderer.buffer(); + render_const_value(&mut buffer, value, renderer.tcx().type_of(def_id), renderer, 0); + Some(buffer.into_inner()) +} + +pub(crate) enum Renderer<'cx, 'tcx> { + PlainText(crate::json::Context<'tcx>), + Html(&'cx super::Context<'tcx>), +} + +impl<'cx, 'tcx> Renderer<'cx, 'tcx> { + fn buffer(&self) -> Buffer { + match self { + Self::PlainText(_) => Buffer::new(), + Self::Html(_) => Buffer::html(), + } + } + + fn tcx(&self) -> TyCtxt<'tcx> { + match self { + Self::PlainText(cx) => cx.tcx, + Self::Html(cx) => cx.tcx(), + } + } + + fn cache(&self) -> &Cache { + match self { + Self::PlainText(cx) => &cx.cache, + Self::Html(cx) => &cx.shared.cache, + } + } +} + +const DEPTH_LIMIT: u32 = 2; +const STRING_LENGTH_LIMIT: usize = 80; +const ARRAY_LENGTH_LIMIT: usize = 12; + +const DEPTH_ELLIPSIS: &str = "…"; +const LENGTH_ELLIPSIS: &str = "………"; + +fn render_constant_kind<'tcx>( + buffer: &mut Buffer, + ct: ConstantKind<'tcx>, + renderer: &Renderer<'_, 'tcx>, + depth: u32, +) { + if depth > DEPTH_LIMIT { + render_ellipsis(buffer, DEPTH_ELLIPSIS); + return; + } + + match ct { + ConstantKind::Ty(ct) => render_const(buffer, ct, renderer), + ConstantKind::Val(ct, ty) => render_const_value(buffer, ct, ty, renderer, depth), + } +} + +fn render_const<'tcx>(buffer: &mut Buffer, ct: Const<'tcx>, renderer: &Renderer<'_, 'tcx>) { + let tcx = renderer.tcx(); + + match ct.kind() { + ty::ConstKind::Unevaluated(ty::Unevaluated { def, promoted: Some(promoted), .. }) => { + render_path(buffer, def.did, renderer); + write!(buffer, "::{:?}", promoted); + } + ty::ConstKind::Unevaluated(ty::Unevaluated { def, promoted: None, .. }) => { + match tcx.def_kind(def.did) { + DefKind::Static(..) | DefKind::Const | DefKind::AssocConst => { + render_path(buffer, def.did, renderer) + } + _ => { + let expr = match def.as_local() { + Some(def) => print_const_expr(tcx, tcx.hir().body_owned_by(def.did)), + None => print_inlined_const(tcx, def.did), + }; + + if buffer.is_for_html() { + write!(buffer, "{}", Escape(&expr)); + } else { + write!(buffer, "{expr}"); + } + } + } + } + ty::ConstKind::Param(ParamConst { name, .. }) => write!(buffer, "{name}"), + ty::ConstKind::Value(value) => render_valtree(buffer, value, ct.ty()), + ty::ConstKind::Infer(_) + | ty::ConstKind::Bound(..) + | ty::ConstKind::Placeholder(_) + | ty::ConstKind::Error(_) => write!(buffer, "_"), + } +} + +fn render_const_value<'tcx>( + buffer: &mut Buffer, + ct: ConstValue<'tcx>, + ty: Ty<'tcx>, + renderer: &Renderer<'_, 'tcx>, + depth: u32, +) { + let tcx = renderer.tcx(); + // FIXME: The code inside `rustc_middle::mir::pretty_print_const` does this. + // Do we need to do this, too? Why (not)? + // let ct = tcx.lift(ct).unwrap(); + // let ty = tcx.lift(ty).unwrap(); + let u8_type = tcx.types.u8; + + match (ct, ty.kind()) { + (ConstValue::Slice { data, start, end }, ty::Ref(_, inner, _)) => { + match inner.kind() { + ty::Slice(t) if *t == u8_type => { + // The `inspect` here is okay since we checked the bounds, and there are + // no relocations (we have an active slice reference here). We don't use + // this result to affect interpreter execution. + let byte_str = + data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end); + render_byte_str(buffer, byte_str); + } + ty::Str => { + // The `inspect` here is okay since we checked the bounds, and there are no + // relocations (we have an active `str` reference here). We don't use this + // result to affect interpreter execution. + let slice = + data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end); + + // FIXME: Make the limit depend on the `depth` (inversely proportionally) + if slice.len() > STRING_LENGTH_LIMIT { + write!(buffer, "\""); + render_ellipsis(buffer, LENGTH_ELLIPSIS); + write!(buffer, "\""); + } else { + let slice = format!("{:?}", String::from_utf8_lossy(slice)); + + if buffer.is_for_html() { + write!(buffer, "{}", Escape(&slice)); + } else { + write!(buffer, "{slice}"); + } + } + } + // `ConstValue::Slice` is only used for `&[u8]` and `&str`. + _ => unreachable!(), + } + } + (ConstValue::ByRef { alloc, offset }, ty::Array(t, n)) if *t == u8_type => { + let n = n.kind().try_to_bits(tcx.data_layout.pointer_size).unwrap(); + // cast is ok because we already checked for pointer size (32 or 64 bit) above + let range = AllocRange { start: offset, size: Size::from_bytes(n) }; + let byte_str = alloc.inner().get_bytes(&tcx, range).unwrap(); + write!(buffer, "*"); + render_byte_str(buffer, byte_str); + } + // Aggregates. + // + // NB: the `has_param_types_or_consts` check ensures that we can use + // the `destructure_const` query with an empty `ty::ParamEnv` without + // introducing ICEs (e.g. via `layout_of`) from missing bounds. + // E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized` + // to be able to destructure the tuple into `(0u8, *mut T) + (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_param_types_or_consts() => { + // FIXME: The code inside `rustc_middle::mir::pretty_print_const` does this. + // Do we need to do this, too? Why (not)? + // let ct = tcx.lift(ct).unwrap(); + // let ty = tcx.lift(ty).unwrap(); + let Some(contents) = tcx.try_destructure_mir_constant( + ty::ParamEnv::reveal_all().and(ConstantKind::Val(ct, ty)) + ) else { + return write!(buffer, "_"); + }; + + let mut fields = contents.fields.iter().copied(); + + // FIXME: Should we try to print larger structs etc. across multiple lines? + match *ty.kind() { + ty::Array(..) => { + write!(buffer, "["); + + // FIXME: Make the limit depend on the `depth` (inversely proportionally) + if contents.fields.len() > ARRAY_LENGTH_LIMIT { + render_ellipsis(buffer, LENGTH_ELLIPSIS); + } else if let Some(first) = fields.next() { + render_constant_kind(buffer, first, renderer, depth + 1); + for field in fields { + buffer.write_str(", "); + render_constant_kind(buffer, field, renderer, depth + 1); + } + } + + write!(buffer, "]"); + } + ty::Tuple(..) => { + write!(buffer, "("); + + if let Some(first) = fields.next() { + render_constant_kind(buffer, first, renderer, depth + 1); + for field in fields { + buffer.write_str(", "); + render_constant_kind(buffer, field, renderer, depth + 1); + } + } + if contents.fields.len() == 1 { + write!(buffer, ","); + } + + write!(buffer, ")"); + } + ty::Adt(def, _) if !def.variants().is_empty() => { + let should_hide = |field: &FieldDef| { + // FIXME: Should I use `cache.access_levels.is_public(did)` here instead? + is_doc_hidden(tcx, field.did) + && !(renderer.cache().document_hidden && field.did.is_local()) + || field.vis != Visibility::Public + && !(renderer.cache().document_private && field.did.is_local()) + }; + + let is_non_exhaustive = tcx.has_attr(def.did(), sym::non_exhaustive); + + let variant_idx = + contents.variant.expect("destructed const of adt without variant idx"); + let variant_def = &def.variant(variant_idx); + render_path(buffer, variant_def.def_id, renderer); + + match variant_def.ctor_kind { + CtorKind::Const => { + if is_non_exhaustive { + write!(buffer, " {{ .. }}"); + } + } + CtorKind::Fn => { + write!(buffer, "("); + + let mut first = true; + for (field_def, field) in std::iter::zip(&variant_def.fields, fields) { + if !first { + write!(buffer, ", "); + } + first = false; + + if should_hide(field_def) { + write!(buffer, "_"); + continue; + } + + render_constant_kind(buffer, field, renderer, depth + 1); + } + + if is_non_exhaustive { + if !first { + write!(buffer, ", "); + } + + // Using `..` (borrowed from patterns) to mark non-exhaustive tuple + // structs in our pseudo-Rust expression syntax is sadly not without + // caveats since in real-Rust expressions, it denotes full ranges + // (`std::ops::RangeFull`) which may appear as arguments to tuple + // struct constructors (albeit incredibly uncommonly) and thus + // it may lead to confusion. + // NB: Actually we literally render full ranges as `RangeFull`. + // Still, that does not help that much. + // If this issue turns out to be significant, we can change the + // output to e.g. `_ @ ..` (borrowed from slice patterns) which is + // not a valid Rust expression at the time of this writing but + // it's quite cryptic. + write!(buffer, ".."); + } + + write!(buffer, ")"); + } + CtorKind::Fictive => { + write!(buffer, " {{ "); + + let mut first = true; + let mut did_hide_fields = false; + for (field_def, field) in std::iter::zip(&variant_def.fields, fields) { + if should_hide(field_def) { + did_hide_fields = true; + continue; + } + + if !first { + write!(buffer, ", "); + } + first = false; + + render_field_name(buffer, field_def, renderer); + write!(buffer, ": "); + render_constant_kind(buffer, field, renderer, depth + 1); + } + + if did_hide_fields || is_non_exhaustive { + if !first { + write!(buffer, ", "); + } + write!(buffer, ".."); + } + + write!(buffer, " }}"); + } + } + } + _ => unreachable!(), + } + } + (ConstValue::Scalar(Scalar::Int(int)), _) => render_const_scalar_int(buffer, int, ty), + // FIXME: Support `&[_]`: `(ByRef { .. }, ty::Ref(_, ty, Not)) if let ty::Slice(_) = ty` + // Blocker: try_destructure_mir_constant does not support slices. + _ => write!(buffer, "_"), + } +} + +fn render_valtree<'tcx>(buffer: &mut Buffer, _valtree: ty::ValTree<'tcx>, _ty: Ty<'tcx>) { + // FIXME: If this case is actually reachable, adopt the code from + // rustc_middle::ty::print::pretty::PrettyPrinter::pretty_print_const_valtree + write!(buffer, "_"); +} + +fn render_path<'tcx>(buffer: &mut Buffer, def_id: DefId, renderer: &Renderer<'_, 'tcx>) { + let tcx = renderer.tcx(); + let name = tcx.item_name(def_id); + + match renderer { + Renderer::PlainText(_) => write!(buffer, "{name}"), + Renderer::Html(cx) => { + if let Ok((mut url, item_type, path)) = super::href(def_id, cx) { + let mut needs_fragment = true; + let item_type = match tcx.def_kind(def_id) { + DefKind::AssocFn => { + if tcx.associated_item(def_id).defaultness(tcx).has_value() { + ItemType::Method + } else { + ItemType::TyMethod + } + } + DefKind::AssocTy => ItemType::AssocType, + DefKind::AssocConst => ItemType::AssocConst, + DefKind::Variant => ItemType::Variant, + _ => { + needs_fragment = false; + item_type + } + }; + + let mut path = super::join_with_double_colon(&path); + + if needs_fragment { + write!(url, "#{item_type}.{name}").unwrap(); + write!(path, "::{name}").unwrap(); + } + + write!( + buffer, + r#"{name}"#, + ); + } else { + write!(buffer, "{name}"); + } + } + } +} + +fn render_byte_str(buffer: &mut Buffer, byte_str: &[u8]) { + buffer.write_str("b\""); + + // FIXME: Make the limit depend on the `depth` (inversely proportionally) + if byte_str.len() > STRING_LENGTH_LIMIT { + render_ellipsis(buffer, LENGTH_ELLIPSIS); + } else { + for &char in byte_str { + for char in std::ascii::escape_default(char) { + let char = char::from(char).to_string(); + + if buffer.is_for_html() { + write!(buffer, "{}", Escape(&char)); + } else { + write!(buffer, "{char}"); + } + } + } + } + + buffer.write_str("\""); +} + +fn render_const_scalar_int<'tcx>(buffer: &mut Buffer, int: ScalarInt, ty: Ty<'tcx>) { + extern crate rustc_apfloat; + use rustc_apfloat::ieee::{Double, Single}; + + match ty.kind() { + ty::Bool if int == ScalarInt::FALSE => write!(buffer, "false"), + ty::Bool if int == ScalarInt::TRUE => write!(buffer, "true"), + + ty::Float(ty::FloatTy::F32) => { + write!(buffer, "{}", Single::try_from(int).unwrap()); + } + ty::Float(ty::FloatTy::F64) => { + write!(buffer, "{}", Double::try_from(int).unwrap()); + } + + ty::Uint(_) | ty::Int(_) => { + let int = + ConstInt::new(int, matches!(ty.kind(), ty::Int(_)), ty.is_ptr_sized_integral()); + // FIXME: We probably shouldn't use the *Debug* impl for *user-facing output*. + // However, it looks really nice and its implementation is non-trivial. + // Should we modify rustc_middle and make it a *Display* impl? + write!(buffer, "{int:?}"); + } + ty::Char if char::try_from(int).is_ok() => { + // FIXME: We probably shouldn't use the *Debug* impl here (see fixme above). + write!(buffer, "{:?}", char::try_from(int).unwrap()); + } + _ => write!(buffer, "_"), + } +} + +fn render_field_name(buffer: &mut Buffer, field_def: &FieldDef, renderer: &Renderer<'_, '_>) { + let tcx = renderer.tcx(); + + match renderer { + Renderer::PlainText(_) => write!(buffer, "{}", field_def.name), + Renderer::Html(cx) => match super::href(field_def.did, cx) { + Ok((mut url, ..)) => { + write!(url, "#").unwrap(); + let parent_id = tcx.parent(field_def.did); + if tcx.def_kind(parent_id) == DefKind::Variant { + write!(url, "{}.{}.field", ItemType::Variant, tcx.item_name(parent_id)) + } else { + write!(url, "{}", ItemType::StructField) + } + .unwrap(); + + write!(url, ".{}", field_def.name).unwrap(); + + write!( + buffer, + r#"{}"#, + ItemType::StructField, + url, + field_def.name, + field_def.name, + ); + } + Err(_) => write!(buffer, "{}", field_def.name), + }, + } +} + +fn render_ellipsis(buffer: &mut Buffer, ellipsis: &str) { + if buffer.is_for_html() { + write!(buffer, r#""#); + } + + write!(buffer, "{ellipsis}"); + + if buffer.is_for_html() { + write!(buffer, ""); + } +} diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 5ed5299e09bc0..58b73a61cf470 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -28,11 +28,13 @@ pub(crate) mod search_index; #[cfg(test)] mod tests; +pub(crate) mod constant; mod context; mod print_item; mod span_map; mod write_shared; +pub(crate) use self::constant::eval_and_render_const; pub(crate) use self::context::*; pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc}; @@ -715,14 +717,13 @@ fn assoc_const( ty = ty.print(cx), ); if let Some(default) = default { - write!(w, " = "); - - // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the - // hood which adds noisy underscores and a type suffix to number literals. - // This hurts readability in this context especially when more complex expressions - // are involved and it doesn't add much of value. - // Find a way to print constants here without all that jazz. - write!(w, "{}", Escape(&default.value(cx.tcx()).unwrap_or_else(|| default.expr(cx.tcx())))); + write!( + w, + " = {}", + default + .eval_and_render(&constant::Renderer::Html(cx)) + .unwrap_or_else(|| default.expr(cx.tcx())), + ); } } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6d0a825fec866..7881361a03b26 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1377,36 +1377,13 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle typ = c.type_.print(cx), ); - // FIXME: The code below now prints - // ` = _; // 100i32` - // if the expression is - // `50 + 50` - // which looks just wrong. - // Should we print - // ` = 100i32;` - // instead? - - let value = c.value(cx.tcx()); - let is_literal = c.is_literal(cx.tcx()); - let expr = c.expr(cx.tcx()); - if value.is_some() || is_literal { - write!(w, " = {expr};", expr = Escape(&expr)); - } else { - w.write_str(";"); + // FIXME: (Maybe) preserve hexadecimal literals like on master. + // FIXME: (Maybe) add numeric underscores like on master. + if let Some(value) = c.eval_and_render(&super::constant::Renderer::Html(cx)) { + write!(w, " = {}", value); } - if !is_literal { - if let Some(value) = &value { - let value_lowercase = value.to_lowercase(); - let expr_lowercase = expr.to_lowercase(); - - if value_lowercase != expr_lowercase - && value_lowercase.trim_end_matches("i32") != expr_lowercase - { - write!(w, " // {value}", value = Escape(value)); - } - } - } + w.write_str(";"); }); }); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 710ca3ee7c7e7..3bcd9bec6c6bd 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -828,6 +828,12 @@ pre, .rustdoc.source .example-wrap { margin-left: 0; } +.content .ellipsis { + border: 1px solid var(--main-color); + border-radius: 4px; + opacity: 0.5; +} + nav.sub { flex-grow: 1; margin-bottom: 25px; diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 5f3793ead42ba..c66c1e760bb8e 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -6,6 +6,7 @@ use std::convert::From; use std::fmt; +use std::rc::Rc; use rustc_ast::ast; use rustc_hir::{def::CtorKind, def_id::DefId}; @@ -17,7 +18,8 @@ use rustdoc_json_types::*; use crate::clean::utils::print_const_expr; use crate::clean::{self, ItemId}; -use crate::formats::item_type::ItemType; +use crate::formats::{cache::Cache, item_type::ItemType}; +use crate::html::render::constant::Renderer as ConstantRenderer; use crate::json::JsonRenderer; impl JsonRenderer<'_> { @@ -49,11 +51,13 @@ impl JsonRenderer<'_> { // We document non-empty stripped modules as with `Module::is_stripped` set to // `true`, to prevent contained items from being orphaned for downstream users, // as JSON does no inlining. - clean::ModuleItem(m) if !m.items.is_empty() => from_clean_item(item, self.tcx), + clean::ModuleItem(m) if !m.items.is_empty() => { + from_clean_item(item, &Context::new(self.tcx, self.cache.clone())) + } _ => return None, } } - _ => from_clean_item(item, self.tcx), + _ => from_clean_item(item, &Context::new(self.tcx, self.cache.clone())), }; Some(Item { id: from_item_id_with_name(item_id, self.tcx, name), @@ -102,30 +106,42 @@ impl JsonRenderer<'_> { } } -pub(crate) trait FromWithTcx { - fn from_tcx(f: T, tcx: TyCtxt<'_>) -> Self; +#[derive(Clone)] +pub(crate) struct Context<'tcx> { + pub(crate) tcx: TyCtxt<'tcx>, + pub(crate) cache: Rc, } -pub(crate) trait IntoWithTcx { - fn into_tcx(self, tcx: TyCtxt<'_>) -> T; +impl<'tcx> Context<'tcx> { + pub(crate) fn new(tcx: TyCtxt<'tcx>, cache: Rc) -> Self { + Self { tcx, cache } + } +} + +pub(crate) trait FromWithCx { + fn from_cx(f: T, cx: &Context<'_>) -> Self; } -impl IntoWithTcx for T +pub(crate) trait IntoWithCx { + fn into_cx(self, cx: &Context<'_>) -> T; +} + +impl IntoWithCx for T where - U: FromWithTcx, + U: FromWithCx, { - fn into_tcx(self, tcx: TyCtxt<'_>) -> U { - U::from_tcx(self, tcx) + fn into_cx(self, cx: &Context<'_>) -> U { + U::from_cx(self, cx) } } -impl FromWithTcx for Vec +impl FromWithCx for Vec where I: IntoIterator, - U: FromWithTcx, + U: FromWithCx, { - fn from_tcx(f: I, tcx: TyCtxt<'_>) -> Vec { - f.into_iter().map(|x| x.into_tcx(tcx)).collect() + fn from_cx(f: I, cx: &Context<'_>) -> Vec { + f.into_iter().map(|x| x.into_cx(cx)).collect() } } @@ -135,59 +151,60 @@ pub(crate) fn from_deprecation(deprecation: rustc_attr::Deprecation) -> Deprecat Deprecation { since: since.map(|s| s.to_string()), note: note.map(|s| s.to_string()) } } -impl FromWithTcx for GenericArgs { - fn from_tcx(args: clean::GenericArgs, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for GenericArgs { + fn from_cx(args: clean::GenericArgs, cx: &Context<'_>) -> Self { use clean::GenericArgs::*; match args { AngleBracketed { args, bindings } => GenericArgs::AngleBracketed { - args: args.into_vec().into_tcx(tcx), - bindings: bindings.into_tcx(tcx), + args: args.into_vec().into_cx(cx), + bindings: bindings.into_cx(cx), }, Parenthesized { inputs, output } => GenericArgs::Parenthesized { - inputs: inputs.into_vec().into_tcx(tcx), - output: output.map(|a| (*a).into_tcx(tcx)), + inputs: inputs.into_vec().into_cx(cx), + output: output.map(|a| (*a).into_cx(cx)), }, } } } -impl FromWithTcx for GenericArg { - fn from_tcx(arg: clean::GenericArg, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for GenericArg { + fn from_cx(arg: clean::GenericArg, cx: &Context<'_>) -> Self { use clean::GenericArg::*; match arg { Lifetime(l) => GenericArg::Lifetime(convert_lifetime(l)), - Type(t) => GenericArg::Type(t.into_tcx(tcx)), - Const(box c) => GenericArg::Const(c.into_tcx(tcx)), + Type(t) => GenericArg::Type(t.into_cx(cx)), + Const(box c) => GenericArg::Const(c.into_cx(cx)), Infer => GenericArg::Infer, } } } -impl FromWithTcx for Constant { - fn from_tcx(constant: clean::Constant, tcx: TyCtxt<'_>) -> Self { - let expr = constant.expr(tcx); - let value = constant.value(tcx); - let is_literal = constant.is_literal(tcx); - Constant { type_: constant.type_.into_tcx(tcx), expr, value, is_literal } +impl FromWithCx for Constant { + fn from_cx(constant: clean::Constant, cx: &Context<'_>) -> Self { + let expr = constant.expr(cx.tcx); + // FIXME: Should we “disable” depth and length limits for the JSON backend? + let value = constant.eval_and_render(&ConstantRenderer::PlainText(cx.clone())); + let is_literal = constant.is_literal(cx.tcx); + Constant { type_: constant.type_.into_cx(cx), expr, value, is_literal } } } -impl FromWithTcx for TypeBinding { - fn from_tcx(binding: clean::TypeBinding, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for TypeBinding { + fn from_cx(binding: clean::TypeBinding, cx: &Context<'_>) -> Self { TypeBinding { name: binding.assoc.name.to_string(), - args: binding.assoc.args.into_tcx(tcx), - binding: binding.kind.into_tcx(tcx), + args: binding.assoc.args.into_cx(cx), + binding: binding.kind.into_cx(cx), } } } -impl FromWithTcx for TypeBindingKind { - fn from_tcx(kind: clean::TypeBindingKind, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for TypeBindingKind { + fn from_cx(kind: clean::TypeBindingKind, cx: &Context<'_>) -> Self { use clean::TypeBindingKind::*; match kind { - Equality { term } => TypeBindingKind::Equality(term.into_tcx(tcx)), - Constraint { bounds } => TypeBindingKind::Constraint(bounds.into_tcx(tcx)), + Equality { term } => TypeBindingKind::Equality(term.into_cx(cx)), + Constraint { bounds } => TypeBindingKind::Constraint(bounds.into_cx(cx)), } } } @@ -230,51 +247,55 @@ pub(crate) fn from_item_id_with_name(item_id: ItemId, tcx: TyCtxt<'_>, name: Opt } } -fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { +fn from_clean_item(item: clean::Item, cx: &Context<'_>) -> ItemEnum { use clean::ItemKind::*; let name = item.name; let is_crate = item.is_crate(); - let header = item.fn_header(tcx); + let header = item.fn_header(cx.tcx); match *item.kind { ModuleItem(m) => { - ItemEnum::Module(Module { is_crate, items: ids(m.items, tcx), is_stripped: false }) - } - ImportItem(i) => ItemEnum::Import(i.into_tcx(tcx)), - StructItem(s) => ItemEnum::Struct(s.into_tcx(tcx)), - UnionItem(u) => ItemEnum::Union(u.into_tcx(tcx)), - StructFieldItem(f) => ItemEnum::StructField(f.into_tcx(tcx)), - EnumItem(e) => ItemEnum::Enum(e.into_tcx(tcx)), - VariantItem(v) => ItemEnum::Variant(v.into_tcx(tcx)), - FunctionItem(f) => ItemEnum::Function(from_function(f, header.unwrap(), tcx)), - ForeignFunctionItem(f) => ItemEnum::Function(from_function(f, header.unwrap(), tcx)), - TraitItem(t) => ItemEnum::Trait(t.into_tcx(tcx)), - TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_tcx(tcx)), - MethodItem(m, _) => ItemEnum::Method(from_function_method(m, true, header.unwrap(), tcx)), - TyMethodItem(m) => ItemEnum::Method(from_function_method(m, false, header.unwrap(), tcx)), - ImplItem(i) => ItemEnum::Impl((*i).into_tcx(tcx)), - StaticItem(s) => ItemEnum::Static(s.into_tcx(tcx)), - ForeignStaticItem(s) => ItemEnum::Static(s.into_tcx(tcx)), + ItemEnum::Module(Module { is_crate, items: ids(m.items, cx.tcx), is_stripped: false }) + } + ImportItem(i) => ItemEnum::Import(i.into_cx(cx)), + StructItem(s) => ItemEnum::Struct(s.into_cx(cx)), + UnionItem(u) => ItemEnum::Union(u.into_cx(cx)), + StructFieldItem(f) => ItemEnum::StructField(f.into_cx(cx)), + EnumItem(e) => ItemEnum::Enum(e.into_cx(cx)), + VariantItem(v) => ItemEnum::Variant(v.into_cx(cx)), + FunctionItem(f) => ItemEnum::Function(from_function(f, header.unwrap(), cx)), + ForeignFunctionItem(f) => ItemEnum::Function(from_function(f, header.unwrap(), cx)), + TraitItem(t) => ItemEnum::Trait(t.into_cx(cx)), + TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_cx(cx)), + MethodItem(m, _) => ItemEnum::Method(from_function_method(m, true, header.unwrap(), cx)), + TyMethodItem(m) => ItemEnum::Method(from_function_method(m, false, header.unwrap(), cx)), + ImplItem(i) => ItemEnum::Impl((*i).into_cx(cx)), + StaticItem(s) => ItemEnum::Static(s.into_cx(cx)), + ForeignStaticItem(s) => ItemEnum::Static(s.into_cx(cx)), ForeignTypeItem => ItemEnum::ForeignType, - TypedefItem(t) => ItemEnum::Typedef(t.into_tcx(tcx)), - OpaqueTyItem(t) => ItemEnum::OpaqueTy(t.into_tcx(tcx)), - ConstantItem(c) => ItemEnum::Constant(c.into_tcx(tcx)), + TypedefItem(t) => ItemEnum::Typedef(t.into_cx(cx)), + OpaqueTyItem(t) => ItemEnum::OpaqueTy(t.into_cx(cx)), + ConstantItem(c) => ItemEnum::Constant(c.into_cx(cx)), MacroItem(m) => ItemEnum::Macro(m.source), - ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_tcx(tcx)), + ProcMacroItem(m) => ItemEnum::ProcMacro(m.into_cx(cx)), PrimitiveItem(p) => ItemEnum::PrimitiveType(p.as_sym().to_string()), - TyAssocConstItem(ty) => ItemEnum::AssocConst { type_: ty.into_tcx(tcx), default: None }, - AssocConstItem(ty, default) => { - ItemEnum::AssocConst { type_: ty.into_tcx(tcx), default: Some(default.expr(tcx)) } - } - TyAssocTypeItem(g, b) => ItemEnum::AssocType { - generics: (*g).into_tcx(tcx), - bounds: b.into_tcx(tcx), - default: None, + TyAssocConstItem(ty) => ItemEnum::AssocConst { type_: ty.into_cx(cx), default: None }, + AssocConstItem(ty, default) => ItemEnum::AssocConst { + type_: ty.into_cx(cx), + // FIXME: Should we “disable” depth and length limits for the JSON backend? + default: Some( + default + .eval_and_render(&ConstantRenderer::PlainText(cx.clone())) + .unwrap_or_else(|| default.expr(cx.tcx)), + ), }, + TyAssocTypeItem(g, b) => { + ItemEnum::AssocType { generics: (*g).into_cx(cx), bounds: b.into_cx(cx), default: None } + } AssocTypeItem(t, b) => ItemEnum::AssocType { - generics: t.generics.into_tcx(tcx), - bounds: b.into_tcx(tcx), - default: Some(t.item_type.unwrap_or(t.type_).into_tcx(tcx)), + generics: t.generics.into_cx(cx), + bounds: b.into_cx(cx), + default: Some(t.item_type.unwrap_or(t.type_).into_cx(cx)), }, // `convert_item` early returns `None` for stripped items and keywords. KeywordItem => unreachable!(), @@ -282,7 +303,7 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { match *inner { ModuleItem(m) => ItemEnum::Module(Module { is_crate, - items: ids(m.items, tcx), + items: ids(m.items, cx.tcx), is_stripped: true, }), // `convert_item` early returns `None` for stripped items we're not including @@ -296,28 +317,28 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { } } -impl FromWithTcx for Struct { - fn from_tcx(struct_: clean::Struct, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Struct { + fn from_cx(struct_: clean::Struct, cx: &Context<'_>) -> Self { let fields_stripped = struct_.has_stripped_entries(); let clean::Struct { struct_type, generics, fields } = struct_; Struct { struct_type: from_ctor_kind(struct_type), - generics: generics.into_tcx(tcx), + generics: generics.into_cx(cx), fields_stripped, - fields: ids(fields, tcx), + fields: ids(fields, cx.tcx), impls: Vec::new(), // Added in JsonRenderer::item } } } -impl FromWithTcx for Union { - fn from_tcx(union_: clean::Union, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Union { + fn from_cx(union_: clean::Union, cx: &Context<'_>) -> Self { let fields_stripped = union_.has_stripped_entries(); let clean::Union { generics, fields } = union_; Union { - generics: generics.into_tcx(tcx), + generics: generics.into_cx(cx), fields_stripped, - fields: ids(fields, tcx), + fields: ids(fields, cx.tcx), impls: Vec::new(), // Added in JsonRenderer::item } } @@ -359,51 +380,51 @@ fn convert_lifetime(l: clean::Lifetime) -> String { l.0.to_string() } -impl FromWithTcx for Generics { - fn from_tcx(generics: clean::Generics, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Generics { + fn from_cx(generics: clean::Generics, cx: &Context<'_>) -> Self { Generics { - params: generics.params.into_tcx(tcx), - where_predicates: generics.where_predicates.into_tcx(tcx), + params: generics.params.into_cx(cx), + where_predicates: generics.where_predicates.into_cx(cx), } } } -impl FromWithTcx for GenericParamDef { - fn from_tcx(generic_param: clean::GenericParamDef, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for GenericParamDef { + fn from_cx(generic_param: clean::GenericParamDef, cx: &Context<'_>) -> Self { GenericParamDef { name: generic_param.name.to_string(), - kind: generic_param.kind.into_tcx(tcx), + kind: generic_param.kind.into_cx(cx), } } } -impl FromWithTcx for GenericParamDefKind { - fn from_tcx(kind: clean::GenericParamDefKind, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for GenericParamDefKind { + fn from_cx(kind: clean::GenericParamDefKind, cx: &Context<'_>) -> Self { use clean::GenericParamDefKind::*; match kind { Lifetime { outlives } => GenericParamDefKind::Lifetime { outlives: outlives.into_iter().map(convert_lifetime).collect(), }, Type { did: _, bounds, default, synthetic } => GenericParamDefKind::Type { - bounds: bounds.into_tcx(tcx), - default: default.map(|x| (*x).into_tcx(tcx)), + bounds: bounds.into_cx(cx), + default: default.map(|x| (*x).into_cx(cx)), synthetic, }, Const { did: _, ty, default } => GenericParamDefKind::Const { - type_: (*ty).into_tcx(tcx), + type_: (*ty).into_cx(cx), default: default.map(|x| *x), }, } } } -impl FromWithTcx for WherePredicate { - fn from_tcx(predicate: clean::WherePredicate, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for WherePredicate { + fn from_cx(predicate: clean::WherePredicate, cx: &Context<'_>) -> Self { use clean::WherePredicate::*; match predicate { BoundPredicate { ty, bounds, bound_params } => WherePredicate::BoundPredicate { - type_: ty.into_tcx(tcx), - bounds: bounds.into_tcx(tcx), + type_: ty.into_cx(cx), + bounds: bounds.into_cx(cx), generic_params: bound_params .into_iter() .map(|x| GenericParamDef { @@ -414,23 +435,23 @@ impl FromWithTcx for WherePredicate { }, RegionPredicate { lifetime, bounds } => WherePredicate::RegionPredicate { lifetime: convert_lifetime(lifetime), - bounds: bounds.into_tcx(tcx), + bounds: bounds.into_cx(cx), }, EqPredicate { lhs, rhs } => { - WherePredicate::EqPredicate { lhs: lhs.into_tcx(tcx), rhs: rhs.into_tcx(tcx) } + WherePredicate::EqPredicate { lhs: lhs.into_cx(cx), rhs: rhs.into_cx(cx) } } } } } -impl FromWithTcx for GenericBound { - fn from_tcx(bound: clean::GenericBound, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for GenericBound { + fn from_cx(bound: clean::GenericBound, cx: &Context<'_>) -> Self { use clean::GenericBound::*; match bound { TraitBound(clean::PolyTrait { trait_, generic_params }, modifier) => { GenericBound::TraitBound { - trait_: trait_.into_tcx(tcx), - generic_params: generic_params.into_tcx(tcx), + trait_: trait_.into_cx(cx), + generic_params: generic_params.into_cx(cx), modifier: from_trait_bound_modifier(modifier), } } @@ -450,67 +471,67 @@ pub(crate) fn from_trait_bound_modifier( } } -impl FromWithTcx for Type { - fn from_tcx(ty: clean::Type, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Type { + fn from_cx(ty: clean::Type, cx: &Context<'_>) -> Self { use clean::Type::{ Array, BareFunction, BorrowedRef, Generic, ImplTrait, Infer, Primitive, QPath, RawPointer, Slice, Tuple, }; match ty { - clean::Type::Path { path } => Type::ResolvedPath(path.into_tcx(tcx)), + clean::Type::Path { path } => Type::ResolvedPath(path.into_cx(cx)), clean::Type::DynTrait(bounds, lt) => Type::DynTrait(DynTrait { lifetime: lt.map(convert_lifetime), - traits: bounds.into_tcx(tcx), + traits: bounds.into_cx(cx), }), Generic(s) => Type::Generic(s.to_string()), Primitive(p) => Type::Primitive(p.as_sym().to_string()), - BareFunction(f) => Type::FunctionPointer(Box::new((*f).into_tcx(tcx))), - Tuple(t) => Type::Tuple(t.into_tcx(tcx)), - Slice(t) => Type::Slice(Box::new((*t).into_tcx(tcx))), - Array(t, s) => Type::Array { type_: Box::new((*t).into_tcx(tcx)), len: s }, - ImplTrait(g) => Type::ImplTrait(g.into_tcx(tcx)), + BareFunction(f) => Type::FunctionPointer(Box::new((*f).into_cx(cx))), + Tuple(t) => Type::Tuple(t.into_cx(cx)), + Slice(t) => Type::Slice(Box::new((*t).into_cx(cx))), + Array(t, s) => Type::Array { type_: Box::new((*t).into_cx(cx)), len: s }, + ImplTrait(g) => Type::ImplTrait(g.into_cx(cx)), Infer => Type::Infer, RawPointer(mutability, type_) => Type::RawPointer { mutable: mutability == ast::Mutability::Mut, - type_: Box::new((*type_).into_tcx(tcx)), + type_: Box::new((*type_).into_cx(cx)), }, BorrowedRef { lifetime, mutability, type_ } => Type::BorrowedRef { lifetime: lifetime.map(convert_lifetime), mutable: mutability == ast::Mutability::Mut, - type_: Box::new((*type_).into_tcx(tcx)), + type_: Box::new((*type_).into_cx(cx)), }, QPath { assoc, self_type, trait_, .. } => Type::QualifiedPath { name: assoc.name.to_string(), - args: Box::new(assoc.args.clone().into_tcx(tcx)), - self_type: Box::new((*self_type).into_tcx(tcx)), - trait_: trait_.into_tcx(tcx), + args: Box::new(assoc.args.clone().into_cx(cx)), + self_type: Box::new((*self_type).into_cx(cx)), + trait_: trait_.into_cx(cx), }, } } } -impl FromWithTcx for Path { - fn from_tcx(path: clean::Path, tcx: TyCtxt<'_>) -> Path { +impl FromWithCx for Path { + fn from_cx(path: clean::Path, cx: &Context<'_>) -> Path { Path { name: path.whole_name(), - id: from_item_id(path.def_id().into(), tcx), - args: path.segments.last().map(|args| Box::new(args.clone().args.into_tcx(tcx))), + id: from_item_id(path.def_id().into(), cx.tcx), + args: path.segments.last().map(|args| Box::new(args.clone().args.into_cx(cx))), } } } -impl FromWithTcx for Term { - fn from_tcx(term: clean::Term, tcx: TyCtxt<'_>) -> Term { +impl FromWithCx for Term { + fn from_cx(term: clean::Term, cx: &Context<'_>) -> Term { match term { - clean::Term::Type(ty) => Term::Type(FromWithTcx::from_tcx(ty, tcx)), - clean::Term::Constant(c) => Term::Constant(FromWithTcx::from_tcx(c, tcx)), + clean::Term::Type(ty) => Term::Type(FromWithCx::from_cx(ty, cx)), + clean::Term::Constant(c) => Term::Constant(FromWithCx::from_cx(c, cx)), } } } -impl FromWithTcx for FunctionPointer { - fn from_tcx(bare_decl: clean::BareFunctionDecl, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for FunctionPointer { + fn from_cx(bare_decl: clean::BareFunctionDecl, cx: &Context<'_>) -> Self { let clean::BareFunctionDecl { unsafety, generic_params, decl, abi } = bare_decl; FunctionPointer { header: Header { @@ -519,23 +540,23 @@ impl FromWithTcx for FunctionPointer { async_: false, abi: convert_abi(abi), }, - generic_params: generic_params.into_tcx(tcx), - decl: decl.into_tcx(tcx), + generic_params: generic_params.into_cx(cx), + decl: decl.into_cx(cx), } } } -impl FromWithTcx for FnDecl { - fn from_tcx(decl: clean::FnDecl, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for FnDecl { + fn from_cx(decl: clean::FnDecl, cx: &Context<'_>) -> Self { let clean::FnDecl { inputs, output, c_variadic } = decl; FnDecl { inputs: inputs .values .into_iter() - .map(|arg| (arg.name.to_string(), arg.type_.into_tcx(tcx))) + .map(|arg| (arg.name.to_string(), arg.type_.into_cx(cx))) .collect(), output: match output { - clean::FnRetTy::Return(t) => Some(t.into_tcx(tcx)), + clean::FnRetTy::Return(t) => Some(t.into_cx(cx)), clean::FnRetTy::DefaultReturn => None, }, c_variadic, @@ -543,34 +564,34 @@ impl FromWithTcx for FnDecl { } } -impl FromWithTcx for Trait { - fn from_tcx(trait_: clean::Trait, tcx: TyCtxt<'_>) -> Self { - let is_auto = trait_.is_auto(tcx); - let is_unsafe = trait_.unsafety(tcx) == rustc_hir::Unsafety::Unsafe; +impl FromWithCx for Trait { + fn from_cx(trait_: clean::Trait, cx: &Context<'_>) -> Self { + let is_auto = trait_.is_auto(cx.tcx); + let is_unsafe = trait_.unsafety(cx.tcx) == rustc_hir::Unsafety::Unsafe; let clean::Trait { items, generics, bounds, .. } = trait_; Trait { is_auto, is_unsafe, - items: ids(items, tcx), - generics: generics.into_tcx(tcx), - bounds: bounds.into_tcx(tcx), + items: ids(items, cx.tcx), + generics: generics.into_cx(cx), + bounds: bounds.into_cx(cx), implementations: Vec::new(), // Added in JsonRenderer::item } } } -impl FromWithTcx for PolyTrait { - fn from_tcx( +impl FromWithCx for PolyTrait { + fn from_cx( clean::PolyTrait { trait_, generic_params }: clean::PolyTrait, - tcx: TyCtxt<'_>, + cx: &Context<'_>, ) -> Self { - PolyTrait { trait_: trait_.into_tcx(tcx), generic_params: generic_params.into_tcx(tcx) } + PolyTrait { trait_: trait_.into_cx(cx), generic_params: generic_params.into_cx(cx) } } } -impl FromWithTcx for Impl { - fn from_tcx(impl_: clean::Impl, tcx: TyCtxt<'_>) -> Self { - let provided_trait_methods = impl_.provided_trait_methods(tcx); +impl FromWithCx for Impl { + fn from_cx(impl_: clean::Impl, cx: &Context<'_>) -> Self { + let provided_trait_methods = impl_.provided_trait_methods(cx.tcx); let clean::Impl { unsafety, generics, trait_, for_, items, polarity, kind } = impl_; // FIXME: use something like ImplKind in JSON? let (synthetic, blanket_impl) = match kind { @@ -584,17 +605,17 @@ impl FromWithTcx for Impl { }; Impl { is_unsafe: unsafety == rustc_hir::Unsafety::Unsafe, - generics: generics.into_tcx(tcx), + generics: generics.into_cx(cx), provided_trait_methods: provided_trait_methods .into_iter() .map(|x| x.to_string()) .collect(), - trait_: trait_.map(|path| path.into_tcx(tcx)), - for_: for_.into_tcx(tcx), - items: ids(items, tcx), + trait_: trait_.map(|path| path.into_cx(cx)), + for_: for_.into_cx(cx), + items: ids(items, cx.tcx), negative: negative_polarity, synthetic, - blanket_impl: blanket_impl.map(|x| x.into_tcx(tcx)), + blanket_impl: blanket_impl.map(|x| x.into_cx(cx)), } } } @@ -602,12 +623,12 @@ impl FromWithTcx for Impl { pub(crate) fn from_function( function: Box, header: rustc_hir::FnHeader, - tcx: TyCtxt<'_>, + cx: &Context<'_>, ) -> Function { let clean::Function { decl, generics } = *function; Function { - decl: decl.into_tcx(tcx), - generics: generics.into_tcx(tcx), + decl: decl.into_cx(cx), + generics: generics.into_cx(cx), header: from_fn_header(&header), } } @@ -616,46 +637,46 @@ pub(crate) fn from_function_method( function: Box, has_body: bool, header: rustc_hir::FnHeader, - tcx: TyCtxt<'_>, + cx: &Context<'_>, ) -> Method { let clean::Function { decl, generics } = *function; Method { - decl: decl.into_tcx(tcx), - generics: generics.into_tcx(tcx), + decl: decl.into_cx(cx), + generics: generics.into_cx(cx), header: from_fn_header(&header), has_body, } } -impl FromWithTcx for Enum { - fn from_tcx(enum_: clean::Enum, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Enum { + fn from_cx(enum_: clean::Enum, cx: &Context<'_>) -> Self { let variants_stripped = enum_.has_stripped_entries(); let clean::Enum { variants, generics } = enum_; Enum { - generics: generics.into_tcx(tcx), + generics: generics.into_cx(cx), variants_stripped, - variants: ids(variants, tcx), + variants: ids(variants, cx.tcx), impls: Vec::new(), // Added in JsonRenderer::item } } } -impl FromWithTcx for Struct { - fn from_tcx(struct_: clean::VariantStruct, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Struct { + fn from_cx(struct_: clean::VariantStruct, cx: &Context<'_>) -> Self { let fields_stripped = struct_.has_stripped_entries(); let clean::VariantStruct { struct_type, fields } = struct_; Struct { struct_type: from_ctor_kind(struct_type), generics: Generics { params: vec![], where_predicates: vec![] }, fields_stripped, - fields: ids(fields, tcx), + fields: ids(fields, cx.tcx), impls: Vec::new(), } } } -impl FromWithTcx for Variant { - fn from_tcx(variant: clean::Variant, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Variant { + fn from_cx(variant: clean::Variant, cx: &Context<'_>) -> Self { use clean::Variant::*; match variant { CLike => Variant::Plain, @@ -664,20 +685,20 @@ impl FromWithTcx for Variant { .into_iter() .map(|f| { if let clean::StructFieldItem(ty) = *f.kind { - ty.into_tcx(tcx) + ty.into_cx(cx) } else { unreachable!() } }) .collect(), ), - Struct(s) => Variant::Struct(ids(s.fields, tcx)), + Struct(s) => Variant::Struct(ids(s.fields, cx.tcx)), } } } -impl FromWithTcx for Import { - fn from_tcx(import: clean::Import, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Import { + fn from_cx(import: clean::Import, cx: &Context<'_>) -> Self { use clean::ImportKind::*; let (name, glob) = match import.kind { Simple(s) => (s.to_string(), false), @@ -689,14 +710,14 @@ impl FromWithTcx for Import { Import { source: import.source.path.whole_name(), name, - id: import.source.did.map(ItemId::from).map(|i| from_item_id(i, tcx)), + id: import.source.did.map(ItemId::from).map(|i| from_item_id(i, cx.tcx)), glob, } } } -impl FromWithTcx for ProcMacro { - fn from_tcx(mac: clean::ProcMacro, _tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for ProcMacro { + fn from_cx(mac: clean::ProcMacro, _cx: &Context<'_>) -> Self { ProcMacro { kind: from_macro_kind(mac.kind), helpers: mac.helpers.iter().map(|x| x.to_string()).collect(), @@ -713,37 +734,38 @@ pub(crate) fn from_macro_kind(kind: rustc_span::hygiene::MacroKind) -> MacroKind } } -impl FromWithTcx> for Typedef { - fn from_tcx(typedef: Box, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx> for Typedef { + fn from_cx(typedef: Box, cx: &Context<'_>) -> Self { let clean::Typedef { type_, generics, item_type: _ } = *typedef; - Typedef { type_: type_.into_tcx(tcx), generics: generics.into_tcx(tcx) } + Typedef { type_: type_.into_cx(cx), generics: generics.into_cx(cx) } } } -impl FromWithTcx for OpaqueTy { - fn from_tcx(opaque: clean::OpaqueTy, tcx: TyCtxt<'_>) -> Self { - OpaqueTy { bounds: opaque.bounds.into_tcx(tcx), generics: opaque.generics.into_tcx(tcx) } +impl FromWithCx for OpaqueTy { + fn from_cx(opaque: clean::OpaqueTy, cx: &Context<'_>) -> Self { + OpaqueTy { bounds: opaque.bounds.into_cx(cx), generics: opaque.generics.into_cx(cx) } } } -impl FromWithTcx for Static { - fn from_tcx(stat: clean::Static, tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for Static { + fn from_cx(stat: clean::Static, cx: &Context<'_>) -> Self { Static { - type_: stat.type_.into_tcx(tcx), + type_: stat.type_.into_cx(cx), mutable: stat.mutability == ast::Mutability::Mut, - expr: stat.expr.map(|e| print_const_expr(tcx, e)).unwrap_or_default(), + // FIXME: Consider using `eval_and_render` here instead (despite the name of the field) + expr: stat.expr.map(|e| print_const_expr(cx.tcx, e)).unwrap_or_default(), } } } -impl FromWithTcx for TraitAlias { - fn from_tcx(alias: clean::TraitAlias, tcx: TyCtxt<'_>) -> Self { - TraitAlias { generics: alias.generics.into_tcx(tcx), params: alias.bounds.into_tcx(tcx) } +impl FromWithCx for TraitAlias { + fn from_cx(alias: clean::TraitAlias, cx: &Context<'_>) -> Self { + TraitAlias { generics: alias.generics.into_cx(cx), params: alias.bounds.into_cx(cx) } } } -impl FromWithTcx for ItemKind { - fn from_tcx(kind: ItemType, _tcx: TyCtxt<'_>) -> Self { +impl FromWithCx for ItemKind { + fn from_cx(kind: ItemType, _cx: &Context<'_>) -> Self { use ItemType::*; match kind { Module => ItemKind::Module, diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 6364d00d0624e..bf514e02aa853 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -27,9 +27,11 @@ use crate::docfs::PathError; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::FormatRenderer; -use crate::json::conversions::{from_item_id, from_item_id_with_name, IntoWithTcx}; +use crate::json::conversions::{from_item_id, from_item_id_with_name, IntoWithCx}; use crate::{clean, try_err}; +pub(crate) use crate::json::conversions::Context; + #[derive(Clone)] pub(crate) struct JsonRenderer<'tcx> { tcx: TyCtxt<'tcx>, @@ -127,7 +129,11 @@ impl<'tcx> JsonRenderer<'tcx> { .last() .map(|s| s.to_string()), visibility: types::Visibility::Public, - inner: types::ItemEnum::Trait(trait_item.clone().into_tcx(self.tcx)), + inner: types::ItemEnum::Trait( + trait_item + .clone() + .into_cx(&Context::new(self.tcx, self.cache.clone())), + ), span: None, docs: Default::default(), links: Default::default(), @@ -283,7 +289,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { types::ItemSummary { crate_id: k.krate.as_u32(), path: path.iter().map(|s| s.to_string()).collect(), - kind: kind.into_tcx(self.tcx), + kind: kind.into_cx(&Context::new(self.tcx, self.cache.clone())), }, ) }) diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 7dcad66b1f992..f9a65b7a863a6 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -166,7 +166,26 @@ pub enum GenericArg { pub struct Constant { #[serde(rename = "type")] pub type_: Type, + /// The *unevaluated* constant expression assigned to this constant item. + /// + /// This textual representation may not actually be what the user wrote + /// or be valid Rust syntax since it may contain underscores in place of + /// overly big or complex expressions. pub expr: String, + /// The (constant) value of this constant item if the evaluation succeeded. + /// + /// It is the result of the *evaluation* of the constant expression assigned + /// to this constant item or `None` if the expression is “too generic”. + /// + /// This textual representation may not actually be lossless or even valid + /// Rust syntax since + /// + /// * overly long arrays & strings and deeply-nested subexpressions are replaced + /// with ellipses + /// * private and `#[doc(hidden)]` struct fields are omitted + /// * `..` is added to (tuple) struct literals if any fields are omitted + /// or if the type is `#[non_exhaustive]` + /// * `_` is used in place of “unsupported” expressions (e.g. pointers) pub value: Option, pub is_literal: bool, } @@ -259,7 +278,19 @@ pub enum ItemEnum { AssocConst { #[serde(rename = "type")] type_: Type, - /// e.g. `const X: usize = 5;` + /// The default (constant) value of this associated constant if available. + /// + /// E.g. the `5` in `const X: usize = 5;`. + /// + /// This textual representation may not actually be lossless or even valid + /// Rust syntax since + /// + /// * overly long arrays & strings and deeply-nested subexpressions are replaced + /// with ellipses + /// * private and `#[doc(hidden)]` struct fields are omitted + /// * `..` is added to (tuple) struct literals if any fields are omitted + /// or if the type is `#[non_exhaustive]` + /// * `_` is used in place of “unsupported” expressions (e.g. pointers) default: Option, }, AssocType { diff --git a/src/test/rustdoc-json/auxiliary/data.rs b/src/test/rustdoc-json/auxiliary/data.rs new file mode 100644 index 0000000000000..b4b2efb53c4f2 --- /dev/null +++ b/src/test/rustdoc-json/auxiliary/data.rs @@ -0,0 +1,26 @@ +use std::cell::Cell; + +pub struct Data { + pub open: (i8, i8, i8), + closed: bool, + #[doc(hidden)] + pub internal: Cell, +} + +impl Data { + pub const fn new(value: (i8, i8, i8)) -> Self { + Self { + open: value, + closed: false, + internal: Cell::new(0), + } + } +} + +pub struct Opaque(u32); + +impl Opaque { + pub const fn new(value: u32) -> Self { + Self(value) + } +} diff --git a/src/test/rustdoc-json/const_value.rs b/src/test/rustdoc-json/const_value.rs new file mode 100644 index 0000000000000..6b554576797fd --- /dev/null +++ b/src/test/rustdoc-json/const_value.rs @@ -0,0 +1,283 @@ +// Testing the formatting of constant values (i.e. evaluated constant expressions) +// where the specific format was first proposed in issue #98929. + +// ignore-tidy-linelength +// edition:2021 +// aux-crate:data=data.rs + +// @has const_value.json + +// Check that constant expressions are printed in their evaluated form. +// +// @is - "$.index[*][?(@.name=='HOUR_IN_SECONDS')].kind" \"constant\" +// @is - "$.index[*][?(@.name=='HOUR_IN_SECONDS')].inner.value" \"3600\" +pub const HOUR_IN_SECONDS: u64 = 60 * 60; + +// @is - "$.index[*][?(@.name=='NEGATIVE')].kind" \"constant\" +// @is - "$.index[*][?(@.name=='NEGATIVE')].inner.value" \"-3600\" +pub const NEGATIVE: i64 = -60 * 60; + +// @is - "$.index[*][?(@.name=='CONCATENATED')].kind" \"constant\" +// @is - "$.index[*][?(@.name=='CONCATENATED')].inner.value" '"\"[0, +∞)\""' +pub const CONCATENATED: &str = concat!("[", stringify!(0), ", ", "+∞", ")"); + +pub struct Record<'r> { + pub one: &'r str, + pub two: (i32,), +} + +// Test that structs whose fields are all public and 1-tuples are displayed correctly. +// Furthermore, the struct fields should appear in definition order. +// +// @is - "$.index[*][?(@.name=='REC')].kind" \"constant\" +// @is - "$.index[*][?(@.name=='REC')].inner.value" '"Record { one: \"thriving\", two: (180,) }"' +pub const REC: Record<'_> = { + assert!(true); + + let auxiliary = 90 * "||".len() as i32; + Record { + two: ( + auxiliary, + #[cfg(FALSE)] + "vanished", + ), + one: "thriving", + } +}; + +// Check that private and doc(hidden) struct fields are not displayed. +// Instead, an ellipsis (namely `..`) should be printed. +// +// @is - "$.index[*][?(@.name=='STRUCT')].kind" \"constant\" +// @is - "$.index[*][?(@.name=='STRUCT')].inner.value" '"Struct { public: (), .. }"' +pub const STRUCT: Struct = Struct { + private : /* SourceMap::span_to_snippet trap */ (), + public: { 1 + 3; }, + hidden: () +}; + +// Test that enum variants, 2-tuples, bools and structs (with private and doc(hidden) fields) nested +// within are rendered correctly. Further, check that there is a maximum depth. +// +// @is - "$.index[*][?(@.name=='NESTED')].kind" \"constant\" +// @is - "$.index[*][?(@.name=='NESTED')].inner.value" '"Some((Struct { public: …, .. }, false))"' +pub const NESTED: Option<(Struct, bool)> = Some(( + Struct { + public: (), + private: (), + hidden: (), + }, + false, +)); + +use std::sync::atomic::AtomicBool; + +pub struct Struct { + private: (), + pub public: (), + #[doc(hidden)] + pub hidden: (), +} + +impl Struct { + // Check that even inside inherent impl blocks private and doc(hidden) struct fields + // are not displayed. + // + // @is - "$.index[*][?(@.name=='SELF')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SELF')].inner.default" '"Struct { public: (), .. }"' + pub const SELF: Self = Self { + private: (), + public: match () { + () => {} + }, + hidden: (), + }; + + // Verify that private and doc(hidden) *tuple* struct fields are not shown. + // In their place, an underscore should be rendered. + // + // @is - "$.index[*][?(@.name=='TUP_STRUCT')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='TUP_STRUCT')].inner.default" '"TupStruct(_, -45, _, _)"' + pub const TUP_STRUCT: TupStruct = TupStruct((), -45, (), false); + + // Check that structs whose fields are all doc(hidden) are rendered correctly. + // + // @is - "$.index[*][?(@.name=='SEALED0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SEALED0')].inner.default" '"Container0 { .. }"' + pub const SEALED0: Container0 = Container0 { hack: () }; + + // Check that *tuple* structs whose fields are all private are rendered correctly. + // + // @is - "$.index[*][?(@.name=='SEALED1')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SEALED1')].inner.default" '"Container1(_)"' + pub const SEALED1: Container1 = Container1(None); + + // Verify that cross-crate structs are displayed correctly and that their fields + // are not leaked. + // + // @is - "$.index[*][?(@.name=='SEALED2')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SEALED2')].inner.default" '"AtomicBool { .. }"' + pub const SEALED2: AtomicBool = AtomicBool::new(true); + + // Test that (local) *unit* enum variants are rendered properly. + // + // @is - "$.index[*][?(@.name=='SUM0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SUM0')].inner.default" '"Uninhabited"' + pub const SUM0: Size = self::Size::Uninhabited; + + // Test that (local) *struct* enum variants are rendered properly. + // + // @is - "$.index[*][?(@.name=='SUM1')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SUM1')].inner.default" '"Inhabited { inhabitants: 9000 }"' + pub const SUM1: Size = AdtSize::Inhabited { inhabitants: 9_000 }; + + // Test that (local) *tuple* enum variants are rendered properly. + // + // @is - "$.index[*][?(@.name=='SUM2')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SUM2')].inner.default" '"Unknown(Reason)"' + pub const SUM2: Size = Size::Unknown(Reason); + + // @is - "$.index[*][?(@.name=='INT')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='INT')].inner.default" '"2368"' + pub const INT: i64 = 2345 + 23; + + // @is - "$.index[*][?(@.name=='STR0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='STR0')].inner.default" '"\"hello friends >.<\""' + pub const STR0: &'static str = "hello friends >.<"; + + // @is - "$.index[*][?(@.name=='FLOAT0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='FLOAT0')].inner.default" '"2930.21997"' + pub const FLOAT0: f32 = 2930.21997; + + // @is - "$.index[*][?(@.name=='FLOAT1')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='FLOAT1')].inner.default" '"-3.42E+21"' + pub const FLOAT1: f64 = -3.42e+21; + + // FIXME: Should we attempt more sophisticated formatting for references? + // + // @is - "$.index[*][?(@.name=='REF')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='REF')].inner.default" '"_"' + pub const REF: &'static i32 = &234; + + // FIXME: Should we attempt more sophisticated formatting for raw pointers? + // + // @is - "$.index[*][?(@.name=='PTR')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='PTR')].inner.default" '"_"' + pub const PTR: *const u16 = &90; + + // @is - "$.index[*][?(@.name=='ARR0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='ARR0')].inner.default" '"[1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080]"' + pub const ARR0: [u16; 8] = [12 * 90; 8]; + + // Check that after a certain unspecified size threshold, array elements + // won't be displayed anymore and that instead a series of ellipses is shown. + // + // @is - "$.index[*][?(@.name=='ARR1')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='ARR1')].inner.default" '"[………]"' + pub const ARR1: [u16; 100] = [12; 52 + 50 - 2]; + + // FIXME: We actually want to print the contents of slices! + // @is - "$.index[*][?(@.name=='SLICE0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='SLICE0')].inner.default" '"_"' + pub const SLICE0: &'static [bool] = &[false, !true, true]; + + // + // Make sure that we don't leak private and doc(hidden) struct fields + // of cross-crate structs (i.e. structs from external crates). + // + + // @is - "$.index[*][?(@.name=='DATA')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='DATA')].inner.default" '"Data { open: (0, 0, 1), .. }"' + pub const DATA: data::Data = data::Data::new((0, 0, 1)); + + // @is - "$.index[*][?(@.name=='OPAQ')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='OPAQ')].inner.default" '"Opaque(_)"' + pub const OPAQ: data::Opaque = data::Opaque::new(0xff00); +} + +pub struct TupStruct(#[doc(hidden)] pub (), pub i32, (), #[doc(hidden)] pub bool); + +pub struct Container0 { + #[doc(hidden)] + pub hack: (), +} + +pub struct Container1(Option>); + +pub type AdtSize = Size; + +pub enum Size { + Inhabited { inhabitants: u128 }, + Uninhabited, + Unknown(Reason), +} + +pub struct Reason; + +use std::cmp::Ordering; + +pub trait Protocol { + // Make sure that this formatting also applies to const exprs inside of trait items, not just + // inside of inherent impl blocks or free constants. + + // @is - "$.index[*][?(@.name=='MATCH')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='MATCH')].inner.default" '"99"' + const MATCH: u64 = match 1 + 4 { + SUPPORT => 99, + _ => 0, + }; + + // @is - "$.index[*][?(@.name=='OPT')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='OPT')].inner.default" '"Some(Some(Equal))"' + const OPT: Option> = Some(Some(Ordering::Equal)); + + // Test that there is a depth limit. Subexpressions exceeding the maximum depth are + // rendered as ellipses. + // + // @is - "$.index[*][?(@.name=='DEEP0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='DEEP0')].inner.default" '"Some(Some(Some(…)))"' + const DEEP0: Option>> = Some(Some(Some(Ordering::Equal))); + + // FIXME: Add more depth tests + + // Check that after a certain unspecified size threshold, the string contents + // won't be displayed anymore and that instead a series of ellipses is shown. + // + // @is - "$.index[*][?(@.name=='STR1')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='STR1')].inner.default" '"\"………\""' + const STR1: &'static str = "\ + This is the start of a relatively long text. \ + I might as well throw some more words into it. \ + Informative content? Never heard of it! \ + That's probably one of the reasons why I shouldn't be included \ + into the generated documentation, don't you think so, too?\ + "; + + // @is - "$.index[*][?(@.name=='BYTE_STR0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='BYTE_STR0')].inner.default" '"b\"Stuck in the days of yore! >.<\""' + const BYTE_STR0: &'static [u8] = b"Stuck in the days of yore! >.<"; + + // Check that after a certain unspecified size threshold, the byte string contents + // won't be displayed anymore and that instead a series of ellipses is shown. + // + // @is - "$.index[*][?(@.name=='BYTE_STR1')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='BYTE_STR1')].inner.default" '"b\"………\""' + const BYTE_STR1: &'static [u8] = b"\ + AGTC CCTG GAAT TACC AAAA AACA TCCA AGTC CTCT \ + AGTC CCTG TCCA AGTC CTCT GAAT TACC AAAA AACA \ + AGTC CCTG GAAT TACC AAAA GGGG GGGG AGTC GTTT \ + GGGG AACA TCCA AGTC CTCT AGTC CCTG GAAT TACC \ + AGTC AAAA GAAT TACC CGAG AACA TCCA AGTC CTCT \ + AGTC CCTG GAAT TACC TTCC AACA TCCA AGTC CTCT\ + "; + + // @is - "$.index[*][?(@.name=='BYTE_ARR0')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='BYTE_ARR0')].inner.default" '"*b\"DEREFERENCED\""' + const BYTE_ARR0: [u8; 12] = *b"DEREFERENCED"; + + // @is - "$.index[*][?(@.name=='BYTE_ARR1')].kind" \"assoc_const\" + // @is - "$.index[*][?(@.name=='BYTE_ARR1')].inner.default" '"*b\"MINCED\\x00\""' + const BYTE_ARR1: [u8; 7] = [b'M', b'I', b'N', b'C', b'E', b'D', b'\0']; +} + +const SUPPORT: i32 = 5; diff --git a/src/test/rustdoc/anchors.no_const_anchor2.html b/src/test/rustdoc/anchors.no_const_anchor2.html index 6d37e8e5eee5e..95607679af2f9 100644 --- a/src/test/rustdoc/anchors.no_const_anchor2.html +++ b/src/test/rustdoc/anchors.no_const_anchor2.html @@ -1 +1 @@ - + diff --git a/src/test/rustdoc/assoc-consts.rs b/src/test/rustdoc/assoc-consts.rs index 97b7739b4c975..46345c7a5bb72 100644 --- a/src/test/rustdoc/assoc-consts.rs +++ b/src/test/rustdoc/assoc-consts.rs @@ -1,6 +1,6 @@ pub trait Foo { // @has assoc_consts/trait.Foo.html '//*[@class="rust trait"]' \ - // 'const FOO: usize = 13usize;' + // 'const FOO: usize = 13;' // @has - '//*[@id="associatedconstant.FOO"]' 'const FOO: usize' const FOO: usize = 12 + 1; // @has - '//*[@id="associatedconstant.FOO_NO_DEFAULT"]' 'const FOO_NO_DEFAULT: bool' diff --git a/src/test/rustdoc/auxiliary/const-value.rs b/src/test/rustdoc/auxiliary/const-value.rs new file mode 100644 index 0000000000000..b4b2efb53c4f2 --- /dev/null +++ b/src/test/rustdoc/auxiliary/const-value.rs @@ -0,0 +1,26 @@ +use std::cell::Cell; + +pub struct Data { + pub open: (i8, i8, i8), + closed: bool, + #[doc(hidden)] + pub internal: Cell, +} + +impl Data { + pub const fn new(value: (i8, i8, i8)) -> Self { + Self { + open: value, + closed: false, + internal: Cell::new(0), + } + } +} + +pub struct Opaque(u32); + +impl Opaque { + pub const fn new(value: u32) -> Self { + Self(value) + } +} diff --git a/src/test/rustdoc/const-value-dead-links.data.html b/src/test/rustdoc/const-value-dead-links.data.html new file mode 100644 index 0000000000000..4d47a5e6d4abd --- /dev/null +++ b/src/test/rustdoc/const-value-dead-links.data.html @@ -0,0 +1 @@ +pub const DATA: Data = Data { open: (0, 0, 1), .. }; \ No newline at end of file diff --git a/src/test/rustdoc/const-value-dead-links.opaq.html b/src/test/rustdoc/const-value-dead-links.opaq.html new file mode 100644 index 0000000000000..65b96790bc74b --- /dev/null +++ b/src/test/rustdoc/const-value-dead-links.opaq.html @@ -0,0 +1 @@ +pub const OPAQ: Opaque = Opaque(_); \ No newline at end of file diff --git a/src/test/rustdoc/const-value-dead-links.rs b/src/test/rustdoc/const-value-dead-links.rs new file mode 100644 index 0000000000000..670ec77bfcb85 --- /dev/null +++ b/src/test/rustdoc/const-value-dead-links.rs @@ -0,0 +1,22 @@ +// aux-crate:aux=const-value.rs +// edition:2021 +#![crate_name = "consts"] + +// Test that *no* hyperlink anchors are created for the structs here since their +// documentation wasn't built (the dependency crate was only *compiled*). +// Re snapshots: Check that this is indeed the case. +// +// NB: The corresponding test cases where the docs of the dependency *were* built +// can be found in `./const-value.rs` (as `Struct::{DATA, OPAQ}`). + +// @has 'consts/constant.DATA.html' +// @has - '//*[@class="docblock item-decl"]//code' \ +// 'const DATA: Data = Data { open: (0, 0, 1), .. }' +// @snapshot data - '//*[@class="docblock item-decl"]//code' +pub const DATA: aux::Data = aux::Data::new((0, 0, 1)); + +// @has 'consts/constant.OPAQ.html' +// @has - '//*[@class="docblock item-decl"]//code' \ +// 'const OPAQ: Opaque = Opaque(_)' +// @snapshot opaq - '//*[@class="docblock item-decl"]//code' +pub const OPAQ: aux::Opaque = aux::Opaque::new(0xff00); diff --git a/src/test/rustdoc/const-value-display.rs b/src/test/rustdoc/const-value-display.rs deleted file mode 100644 index 5b2f3c48d57fa..0000000000000 --- a/src/test/rustdoc/const-value-display.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![crate_name = "foo"] - -// @has 'foo/constant.HOUR_IN_SECONDS.html' -// @has - '//*[@class="docblock item-decl"]//code' 'pub const HOUR_IN_SECONDS: u64 = _; // 3_600u64' -pub const HOUR_IN_SECONDS: u64 = 60 * 60; - -// @has 'foo/constant.NEGATIVE.html' -// @has - '//*[@class="docblock item-decl"]//code' 'pub const NEGATIVE: i64 = _; // -3_600i64' -pub const NEGATIVE: i64 = -60 * 60; diff --git a/src/test/rustdoc/const-value-document-hidden.rs b/src/test/rustdoc/const-value-document-hidden.rs new file mode 100644 index 0000000000000..13ac2231dc58e --- /dev/null +++ b/src/test/rustdoc/const-value-document-hidden.rs @@ -0,0 +1,27 @@ +// aux-crate:aux=const-value.rs +// compile-flags: -Zunstable-options --document-hidden-items + +// edition:2021 +#![crate_name = "consts"] + +// @has 'consts/struct.Context.html' +pub struct Context { + yi: i32, + pub er: bool, + #[doc(hidden)] + pub san: aux::Data, +} + +impl Context { + // Test that with `--document-hidden-items`, the hidden fields of the *local* type `Context` + // show up in the documentation but + // the hidden field `internal` of the *non-local* type `aux::Data` does *not*. + // + // @has - '//*[@id="associatedconstant.DUMMY"]' \ + // 'const DUMMY: Context = Context { er: false, san: Data { open: (…, …, …), .. }, .. }' + pub const DUMMY: Context = Context { + yi: 0xFFFFFF, + er: false, + san: aux::Data::new((2, 0, -1)), + }; +} diff --git a/src/test/rustdoc/const-value-document-private.rs b/src/test/rustdoc/const-value-document-private.rs new file mode 100644 index 0000000000000..4c30aacf41233 --- /dev/null +++ b/src/test/rustdoc/const-value-document-private.rs @@ -0,0 +1,31 @@ +// aux-crate:aux=const-value.rs +// compile-flags: --document-private-items + +// edition:2021 +#![crate_name = "consts"] + +// ignore-tidy-linelength + +// @has 'consts/struct.Context.html' +pub struct Context { + yi: i32, + pub er: bool, + san: aux::Data, + #[doc(hidden)] + pub si: (), +} + +impl Context { + // Test that with `--document-private-items`, the private fields of the *local* type `Context` + // show up in the documentation but + // the private field `closed` of the *non-local* type `aux::Data` does *not*. + // + // @has - '//*[@id="associatedconstant.DUMMY"]' \ + // 'const DUMMY: Context = Context { yi: 16777215, er: false, san: Data { open: (…, …, …), .. }, .. }' + pub const DUMMY: Context = Context { + yi: 0xFFFFFF, + er: false, + san: aux::Data::new((2, 0, -1)), + si: (), + }; +} diff --git a/src/test/rustdoc/const-value.arr1.html b/src/test/rustdoc/const-value.arr1.html new file mode 100644 index 0000000000000..20b3c3b510d13 --- /dev/null +++ b/src/test/rustdoc/const-value.arr1.html @@ -0,0 +1 @@ +

pub const ARR1: [u16; 100] = [………]

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.byte-str1.html b/src/test/rustdoc/const-value.byte-str1.html new file mode 100644 index 0000000000000..bdb4205ba46ab --- /dev/null +++ b/src/test/rustdoc/const-value.byte-str1.html @@ -0,0 +1 @@ +

const BYTE_STR1: &'static [u8] = b"………"

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.data.html b/src/test/rustdoc/const-value.data.html new file mode 100644 index 0000000000000..1f62dea211b68 --- /dev/null +++ b/src/test/rustdoc/const-value.data.html @@ -0,0 +1 @@ +

pub const DATA: Data = Data { open: (0, 0, 1), .. }

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.deep0.html b/src/test/rustdoc/const-value.deep0.html new file mode 100644 index 0000000000000..6d8315112fcd5 --- /dev/null +++ b/src/test/rustdoc/const-value.deep0.html @@ -0,0 +1 @@ +

const DEEP0: Option<Option<Option<Ordering>>> = Some(Some(Some()))

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.nested.html b/src/test/rustdoc/const-value.nested.html new file mode 100644 index 0000000000000..a3916bdc566fe --- /dev/null +++ b/src/test/rustdoc/const-value.nested.html @@ -0,0 +1 @@ +pub const NESTED: Option<(Struct, bool)> = Some((Struct { public: , .. }, false)); \ No newline at end of file diff --git a/src/test/rustdoc/const-value.opaq.html b/src/test/rustdoc/const-value.opaq.html new file mode 100644 index 0000000000000..761c6a5beab15 --- /dev/null +++ b/src/test/rustdoc/const-value.opaq.html @@ -0,0 +1 @@ +

pub const OPAQ: Opaque = Opaque(_)

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.opt.html b/src/test/rustdoc/const-value.opt.html new file mode 100644 index 0000000000000..b1da5dd7178d7 --- /dev/null +++ b/src/test/rustdoc/const-value.opt.html @@ -0,0 +1 @@ +

const OPT: Option<Option<Ordering>> = Some(Some(Equal))

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.rec.html b/src/test/rustdoc/const-value.rec.html new file mode 100644 index 0000000000000..6490f697b948f --- /dev/null +++ b/src/test/rustdoc/const-value.rec.html @@ -0,0 +1 @@ +pub const REC: Record<'static> = Record { one: "thriving", two: (180,) }; \ No newline at end of file diff --git a/src/test/rustdoc/const-value.rs b/src/test/rustdoc/const-value.rs new file mode 100644 index 0000000000000..0f63de4a5b5d8 --- /dev/null +++ b/src/test/rustdoc/const-value.rs @@ -0,0 +1,378 @@ +// Testing the formatting of constant values (i.e. evaluated constant expressions) +// where the specific format was first proposed in issue #98929. + +// edition:2021 +#![crate_name = "consts"] + +// aux-build:const-value.rs +// build-aux-docs +// ignore-cross-compile +extern crate const_value as aux; + +// ignore-tidy-linelength + +// FIXME: Some tests in here might be redundant and already present in other test files. +// FIXME: Test restricted visibilities (e.g. `pub(super)`, `pub(in crate::some::thing)`). + +// Check that constant expressions are printed in their evaluated form. +// +// @has 'consts/constant.HOUR_IN_SECONDS.html' +// @has - '//*[@class="docblock item-decl"]//code' 'pub const HOUR_IN_SECONDS: u64 = 3600;' +pub const HOUR_IN_SECONDS: u64 = 60 * 60; + +// @has 'consts/constant.NEGATIVE.html' +// @has - '//*[@class="docblock item-decl"]//code' 'pub const NEGATIVE: i64 = -3600;' +pub const NEGATIVE: i64 = -60 * 60; + +// @has 'consts/constant.CONCATENATED.html' +// @has - '//*[@class="docblock item-decl"]//code' \ +// "pub const CONCATENATED: &'static str = \"[0, +∞)\";" +pub const CONCATENATED: &str = concat!("[", stringify!(0), ", ", "+∞", ")"); + +// @has 'consts/struct.Record.html' +pub struct Record<'r> { + pub one: &'r str, + pub two: (i32,), +} + +// Test that structs whose fields are all public and 1-tuples are displayed correctly. +// Furthermore, the struct fields should appear in definition order. +// Re snapshot: Check that hyperlinks are generated for the struct name and fields. +// +// @has 'consts/constant.REC.html' +// @has - '//*[@class="docblock item-decl"]//code' \ +// "const REC: Record<'static> = Record { one: \"thriving\", two: (180,) }" +// @snapshot rec - '//*[@class="docblock item-decl"]//code' +pub const REC: Record<'_> = { + assert!(true); + + let auxiliary = 90 * "||".len() as i32; + Record { + two: ( + auxiliary, + #[cfg(FALSE)] + "vanished", + ), + one: "thriving", + } +}; + +// Check that private and doc(hidden) struct fields are not displayed. +// Instead, an ellipsis (namely `..`) should be printed. +// Re snapshot: Check that hyperlinks are generated for the struct name and the public struct field. +// +// @has 'consts/constant.STRUCT.html' +// @has - '//*[@class="docblock item-decl"]//code' \ +// 'const STRUCT: Struct = Struct { public: (), .. }' +// @snapshot struct - '//*[@class="docblock item-decl"]//code' +pub const STRUCT: Struct = Struct { + private : /* SourceMap::span_to_snippet trap */ (), + public: { 1 + 3; }, + hidden: () +}; + +// Test that enum variants, 2-tuples, bools and structs (with private and doc(hidden) fields) nested +// within are rendered correctly. Further, check that there is a maximum depth. +// Re snapshot: Test the hyperlinks are generated for the cross-crate enum variant etc. +// +// @has 'consts/constant.NESTED.html' +// @has - '//*[@class="docblock item-decl"]//code' \ +// 'const NESTED: Option<(Struct, bool)> = Some((Struct { public: …, .. }, false))' +// @snapshot nested - '//*[@class="docblock item-decl"]//code' +pub const NESTED: Option<(Struct, bool)> = Some(( + Struct { + public: (), + private: (), + hidden: (), + }, + false, +)); + +use std::sync::atomic::AtomicBool; + +// @has 'consts/struct.Struct.html' +pub struct Struct { + private: (), + pub public: (), + #[doc(hidden)] + pub hidden: (), +} + +impl Struct { + // Check that even inside inherent impl blocks private and doc(hidden) struct fields + // are not displayed. + // Re snapshot: Check that hyperlinks are generated for the struct name and + // the public struct field. + // + // @has - '//*[@id="associatedconstant.SELF"]' \ + // 'const SELF: Self = Struct { public: (), .. }' + // @snapshot self - '//*[@id="associatedconstant.SELF"]//*[@class="code-header"]' + pub const SELF: Self = Self { + private: (), + public: match () { + () => {} + }, + hidden: (), + }; + + // Verify that private and doc(hidden) *tuple* struct fields are not shown. + // In their place, an underscore should be rendered. + // Re snapshot: Check that a hyperlink is generated for the tuple struct name. + // + // @has - '//*[@id="associatedconstant.TUP_STRUCT"]' \ + // 'const TUP_STRUCT: TupStruct = TupStruct(_, -45, _, _)' + // @snapshot tup-struct - '//*[@id="associatedconstant.TUP_STRUCT"]//*[@class="code-header"]' + pub const TUP_STRUCT: TupStruct = TupStruct((), -45, (), false); + + // Check that structs whose fields are all doc(hidden) are rendered correctly. + // + // @has - '//*[@id="associatedconstant.SEALED0"]' \ + // 'const SEALED0: Container0 = Container0 { .. }' + pub const SEALED0: Container0 = Container0 { hack: () }; + + // Check that *tuple* structs whose fields are all private are rendered correctly. + // + // @has - '//*[@id="associatedconstant.SEALED1"]' \ + // 'const SEALED1: Container1 = Container1(_)' + pub const SEALED1: Container1 = Container1(None); + + // Verify that cross-crate structs are displayed correctly and that their fields + // are not leaked. + // Re snapshot: Check that a hyperlink is generated for the name of the cross-crate struct. + // + // @has - '//*[@id="associatedconstant.SEALED2"]' \ + // 'const SEALED2: AtomicBool = AtomicBool { .. }' + // @snapshot sealed2 - '//*[@id="associatedconstant.SEALED2"]//*[@class="code-header"]' + pub const SEALED2: AtomicBool = AtomicBool::new(true); + + // Test that (local) *unit* enum variants are rendered properly. + // Re snapshot: Test that a hyperlink is generated for the variant. + // + // @has - '//*[@id="associatedconstant.SUM0"]' \ + // 'const SUM0: Size = Uninhabited' + // @snapshot sum0 - '//*[@id="associatedconstant.SUM0"]//*[@class="code-header"]' + pub const SUM0: Size = self::Size::Uninhabited; + + // Test that (local) *struct* enum variants are rendered properly. + // Re snapshot: Test that a hyperlink is generated for the variant. + // + // @has - '//*[@id="associatedconstant.SUM1"]' \ + // 'const SUM1: Size = Inhabited { inhabitants: 9000 }' + // @snapshot sum1 - '//*[@id="associatedconstant.SUM1"]//*[@class="code-header"]' + pub const SUM1: Size = AdtSize::Inhabited { inhabitants: 9_000 }; + + // Test that (local) *tuple* enum variants are rendered properly. + // Re snapshot: Test that a hyperlink is generated for the variant. + // + // @has - '//*[@id="associatedconstant.SUM2"]' \ + // 'const SUM2: Size = Unknown(Reason)' + // @snapshot sum2 - '//*[@id="associatedconstant.SUM2"]//*[@class="code-header"]' + pub const SUM2: Size = Size::Unknown(Reason); + + // @has - '//*[@id="associatedconstant.INT"]' \ + // 'const INT: i64 = 2368' + pub const INT: i64 = 2345 + 23; + + // @has - '//*[@id="associatedconstant.STR"]' \ + // "const STR: &'static str = \"hello friends\"" + pub const STR: &'static str = "hello friends"; + + // @has - '//*[@id="associatedconstant.FLOAT0"]' \ + // 'const FLOAT0: f32 = 2930.21997' + pub const FLOAT0: f32 = 2930.21997; + + // @has - '//*[@id="associatedconstant.FLOAT1"]' \ + // 'const FLOAT1: f64 = -3.42E+21' + pub const FLOAT1: f64 = -3.42e+21; + + // @has - '//*[@id="associatedconstant.REF"]' \ + // "const REF: &'static i32 = _" + pub const REF: &'static i32 = &234; + + // @has - '//*[@id="associatedconstant.PTR"]' \ + // 'const PTR: *const u16 = _' + pub const PTR: *const u16 = &90; + + // @has - '//*[@id="associatedconstant.ARR0"]' \ + // 'const ARR0: [u16; 8] = [1080, 1080, 1080, 1080, 1080, 1080, 1080, 1080]' + pub const ARR0: [u16; 8] = [12 * 90; 8]; + + // Check that after a certain unspecified size threshold, array elements + // won't be displayed anymore and that instead a *styled* series of ellipses is shown. + // Re snapshot: Check that the series of ellipses is styled (has a certain CSS class). + // + // @has - '//*[@id="associatedconstant.ARR1"]' \ + // 'const ARR1: [u16; 100] = [………]' + // @snapshot arr1 - '//*[@id="associatedconstant.ARR1"]//*[@class="code-header"]' + pub const ARR1: [u16; 100] = [12; 52 + 50 - 2]; + + // FIXME: We actually want to print the contents of slices! + // @has - '//*[@id="associatedconstant.SLICE0"]' \ + // "const SLICE0: &'static [bool] = _" + pub const SLICE0: &'static [bool] = &[false, !true, true]; + + // + // The following two test cases are regression tests for issue #99630: + // Make sure that we don't leak private and doc(hidden) struct fields + // of cross-crate structs (i.e. structs from external crates). + // + + // @has - '//*[@id="associatedconstant.DATA"]' \ + // 'const DATA: Data = Data { open: (0, 0, 1), .. }' + // @snapshot data - '//*[@id="associatedconstant.DATA"]//*[@class="code-header"]' + pub const DATA: aux::Data = aux::Data::new((0, 0, 1)); + + // @has - '//*[@id="associatedconstant.OPAQ"]' \ + // 'const OPAQ: Opaque = Opaque(_)' + // @snapshot opaq - '//*[@id="associatedconstant.OPAQ"]//*[@class="code-header"]' + pub const OPAQ: aux::Opaque = aux::Opaque::new(0xff00); +} + +pub struct TupStruct(#[doc(hidden)] pub (), pub i32, (), #[doc(hidden)] pub bool); + +pub struct Container0 { + #[doc(hidden)] + pub hack: (), +} + +pub struct Container1(Option>); + +pub type AdtSize = Size; + +pub enum Size { + Inhabited { inhabitants: u128 }, + Uninhabited, + Unknown(Reason), +} + +pub struct Reason; + +use std::cmp::Ordering; + +// @has 'consts/trait.Protocol.html' +pub trait Protocol { + // Make sure that this formatting also applies to const exprs inside of trait items, not just + // inside of inherent impl blocks or free constants. + + // @has - '//*[@id="associatedconstant.MATCH"]' \ + // 'const MATCH: u64 = 99' + const MATCH: u64 = match 1 + 4 { + SUPPORT => 99, + _ => 0, + }; + + // Re snapshot: Verify that hyperlinks are created. + // + // @has - '//*[@id="associatedconstant.OPT"]' \ + // 'const OPT: Option> = Some(Some(Equal))' + // @snapshot opt - '//*[@id="associatedconstant.OPT"]//*[@class="code-header"]' + const OPT: Option> = Some(Some(Ordering::Equal)); + + // Test that there is a depth limit. Subexpressions exceeding the maximum depth are + // rendered as *styled* ellipses. + // Re snapshot: Check that the ellipses are styled (have a certain CSS class). + // + // @has - '//*[@id="associatedconstant.DEEP0"]' \ + // 'const DEEP0: Option>> = Some(Some(Some(…)))' + // @snapshot deep0 - '//*[@id="associatedconstant.DEEP0"]//*[@class="code-header"]' + const DEEP0: Option>> = Some(Some(Some(Ordering::Equal))); + + // FIXME: Add more depth tests + + // @has - '//*[@id="associatedconstant.STR0"]' \ + // "const STR0: &'static str = \"I want to escape!\"" + const STR0: &'static str = "I want to escape!"; + + // Check that after a certain unspecified size threshold, the string contents + // won't be displayed anymore and that instead a *styled* series of ellipses is shown. + // Re snapshot: Check that the series of ellipses is styled (has a certain CSS class). + // + // @has - '//*[@id="associatedconstant.STR1"]' \ + // "const STR1: &'static str = \"………\"" + // @snapshot str1 - '//*[@id="associatedconstant.STR1"]//*[@class="code-header"]' + const STR1: &'static str = "\ + This is the start of a relatively long text. \ + I might as well throw some more words into it. \ + Informative content? Never heard of it! \ + That's probably one of the reasons why I shouldn't be included \ + into the generated documentation, don't you think so, too?\ + "; + + // @has - '//*[@id="associatedconstant.BYTE_STR0"]' \ + // "const BYTE_STR0: &'static [u8] = b\"I want to escape!\"" + const BYTE_STR0: &'static [u8] = b"I want to escape!"; + + // Check that after a certain unspecified size threshold, the byte string contents + // won't be displayed anymore and that instead a *styled* series of ellipses is shown. + // Re snapshot: Check that the series of ellipses is styled (has a certain CSS class). + // + // @has - '//*[@id="associatedconstant.BYTE_STR1"]' \ + // "const BYTE_STR1: &'static [u8] = b\"………\"" + // @snapshot byte-str1 - '//*[@id="associatedconstant.BYTE_STR1"]//*[@class="code-header"]' + const BYTE_STR1: &'static [u8] = b"\ + AGTC CCTG GAAT TACC AAAA AACA TCCA AGTC CTCT \ + AGTC CCTG TCCA AGTC CTCT GAAT TACC AAAA AACA \ + AGTC CCTG GAAT TACC AAAA GGGG GGGG AGTC GTTT \ + GGGG AACA TCCA AGTC CTCT AGTC CCTG GAAT TACC \ + AGTC AAAA GAAT TACC CGAG AACA TCCA AGTC CTCT \ + AGTC CCTG GAAT TACC TTCC AACA TCCA AGTC CTCT\ + "; + + // @has - '//*[@id="associatedconstant.BYTE_ARR0"]' \ + // 'const BYTE_ARR0: [u8; 12] = *b"DEREFERENCED"' + const BYTE_ARR0: [u8; 12] = *b"DEREFERENCED"; + + // @has - '//*[@id="associatedconstant.BYTE_ARR1"]' \ + // "const BYTE_ARR1: [u8; 7] = *b\"MINCED\\x00\"" + const BYTE_ARR1: [u8; 7] = [b'M', b'I', b'N', b'C', b'E', b'D', b'\0']; +} + +const SUPPORT: i32 = 5; + +pub mod exhaustiveness { + // @has 'consts/exhaustiveness/constant.EXHAUSTIVE_UNIT_STRUCT.html' + // @has - '//*[@class="docblock item-decl"]//code' \ + // 'const EXHAUSTIVE_UNIT_STRUCT: ExhaustiveUnitStruct = ExhaustiveUnitStruct' + pub const EXHAUSTIVE_UNIT_STRUCT: ExhaustiveUnitStruct = ExhaustiveUnitStruct; + + // @has 'consts/exhaustiveness/constant.EXHAUSTIVE_TUPLE_STRUCT.html' + // @has - '//*[@class="docblock item-decl"]//code' \ + // 'const EXHAUSTIVE_TUPLE_STRUCT: ExhaustiveTupleStruct = ExhaustiveTupleStruct(())' + pub const EXHAUSTIVE_TUPLE_STRUCT: ExhaustiveTupleStruct = ExhaustiveTupleStruct(()); + + // @has 'consts/exhaustiveness/constant.EXHAUSTIVE_STRUCT.html' + // @has - '//*[@class="docblock item-decl"]//code' \ + // 'const EXHAUSTIVE_STRUCT: ExhaustiveStruct = ExhaustiveStruct { inner: () }' + pub const EXHAUSTIVE_STRUCT: ExhaustiveStruct = ExhaustiveStruct { inner: () }; + + // @has 'consts/exhaustiveness/constant.NON_EXHAUSTIVE_UNIT_STRUCT.html' + // @has - '//*[@class="docblock item-decl"]//code' \ + // 'const NON_EXHAUSTIVE_UNIT_STRUCT: NonExhaustiveUnitStruct = NonExhaustiveUnitStruct { .. }' + pub const NON_EXHAUSTIVE_UNIT_STRUCT: NonExhaustiveUnitStruct = NonExhaustiveUnitStruct; + + // @has 'consts/exhaustiveness/constant.NON_EXHAUSTIVE_TUPLE_STRUCT.html' + // @has - '//*[@class="docblock item-decl"]//code' \ + // 'const NON_EXHAUSTIVE_TUPLE_STRUCT: NonExhaustiveTupleStruct = NonExhaustiveTupleStruct((), ..)' + pub const NON_EXHAUSTIVE_TUPLE_STRUCT: NonExhaustiveTupleStruct = NonExhaustiveTupleStruct(()); + + // @has 'consts/exhaustiveness/constant.NON_EXHAUSTIVE_STRUCT.html' + // @has - '//*[@class="docblock item-decl"]//code' \ + // 'const NON_EXHAUSTIVE_STRUCT: NonExhaustiveStruct = NonExhaustiveStruct { inner: (), .. }' + pub const NON_EXHAUSTIVE_STRUCT: NonExhaustiveStruct = NonExhaustiveStruct { inner: () }; + + // Assert that full ranges are literally rendered as `RangeFull` and not `..` to make sure that + // `..` unambiguously means “omitted fields” in our pseudo-Rust expression syntax. + // See the comment in `render_const_value` for more details. + // + // @has 'consts/exhaustiveness/constant.RANGE_FULL.html' + // @has - '//*[@class="docblock item-decl"]//code' \ + // 'const RANGE_FULL: RangeFull = RangeFull' + pub const RANGE_FULL: std::ops::RangeFull = ..; + + pub struct ExhaustiveUnitStruct; + pub struct ExhaustiveTupleStruct(pub ()); + pub struct ExhaustiveStruct { pub inner: () } + #[non_exhaustive] pub struct NonExhaustiveUnitStruct; + #[non_exhaustive] pub struct NonExhaustiveTupleStruct(pub ()); + #[non_exhaustive] pub struct NonExhaustiveStruct { pub inner: () } +} diff --git a/src/test/rustdoc/const-value.sealed2.html b/src/test/rustdoc/const-value.sealed2.html new file mode 100644 index 0000000000000..3c1c7033c0a1a --- /dev/null +++ b/src/test/rustdoc/const-value.sealed2.html @@ -0,0 +1 @@ +

pub const SEALED2: AtomicBool = AtomicBool { .. }

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.self.html b/src/test/rustdoc/const-value.self.html new file mode 100644 index 0000000000000..c74e3363c4960 --- /dev/null +++ b/src/test/rustdoc/const-value.self.html @@ -0,0 +1 @@ +

pub const SELF: Self = Struct { public: (), .. }

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.str1.html b/src/test/rustdoc/const-value.str1.html new file mode 100644 index 0000000000000..a381fdead9727 --- /dev/null +++ b/src/test/rustdoc/const-value.str1.html @@ -0,0 +1 @@ +

const STR1: &'static str = "………"

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.struct.html b/src/test/rustdoc/const-value.struct.html new file mode 100644 index 0000000000000..830247e35a63c --- /dev/null +++ b/src/test/rustdoc/const-value.struct.html @@ -0,0 +1 @@ +pub const STRUCT: Struct = Struct { public: (), .. }; \ No newline at end of file diff --git a/src/test/rustdoc/const-value.sum0.html b/src/test/rustdoc/const-value.sum0.html new file mode 100644 index 0000000000000..a60193c6bd71b --- /dev/null +++ b/src/test/rustdoc/const-value.sum0.html @@ -0,0 +1 @@ +

pub const SUM0: Size = Uninhabited

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.sum1.html b/src/test/rustdoc/const-value.sum1.html new file mode 100644 index 0000000000000..24abc72d545e0 --- /dev/null +++ b/src/test/rustdoc/const-value.sum1.html @@ -0,0 +1 @@ +

pub const SUM1: Size = Inhabited { inhabitants: 9000 }

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.sum2.html b/src/test/rustdoc/const-value.sum2.html new file mode 100644 index 0000000000000..fda05bd3c6299 --- /dev/null +++ b/src/test/rustdoc/const-value.sum2.html @@ -0,0 +1 @@ +

pub const SUM2: Size = Unknown(Reason)

\ No newline at end of file diff --git a/src/test/rustdoc/const-value.tup-struct.html b/src/test/rustdoc/const-value.tup-struct.html new file mode 100644 index 0000000000000..8ad6b4068239b --- /dev/null +++ b/src/test/rustdoc/const-value.tup-struct.html @@ -0,0 +1 @@ +

pub const TUP_STRUCT: TupStruct = TupStruct(_, -45, _, _)

\ No newline at end of file diff --git a/src/test/rustdoc/hide-complex-unevaluated-consts.rs b/src/test/rustdoc/hide-complex-unevaluated-consts.rs index ba623246a01e0..7089532f4bb38 100644 --- a/src/test/rustdoc/hide-complex-unevaluated-consts.rs +++ b/src/test/rustdoc/hide-complex-unevaluated-consts.rs @@ -7,6 +7,8 @@ // Read the documentation of `rustdoc::clean::utils::print_const_expr` // for further details. +// FIXME: This test file now partially overlaps with `const-value-display.rs`. + // @has hide_complex_unevaluated_consts/trait.Container.html pub trait Container { // A helper constant that prevents const expressions containing it @@ -18,20 +20,15 @@ pub trait Container { // Ensure that the private field does not get leaked: // // @has - '//*[@id="associatedconstant.STRUCT0"]' \ - // 'const STRUCT0: Struct = _' + // 'const STRUCT0: Struct = Struct { .. }' const STRUCT0: Struct = Struct { private: () }; // @has - '//*[@id="associatedconstant.STRUCT1"]' \ - // 'const STRUCT1: (Struct,) = _' + // 'const STRUCT1: (Struct,) = (Struct { .. },)' const STRUCT1: (Struct,) = (Struct{private: /**/()},); - // Although the struct field is public here, check that it is not - // displayed. In a future version of rustdoc, we definitely want to - // show it. However for the time being, the printing logic is a bit - // conservative. - // // @has - '//*[@id="associatedconstant.STRUCT2"]' \ - // 'const STRUCT2: Record = _' + // 'const STRUCT2: Record = Record { public: 5 }' const STRUCT2: Record = Record { public: 5 }; // Test that we do not show the incredibly verbose match expr: diff --git a/src/test/rustdoc/show-const-contents.rs b/src/test/rustdoc/show-const-contents.rs index 69e742ee74739..c2f11424481c5 100644 --- a/src/test/rustdoc/show-const-contents.rs +++ b/src/test/rustdoc/show-const-contents.rs @@ -1,6 +1,15 @@ // Test that the contents of constants are displayed as part of the // documentation. +// FIXME: This test file now partially overlaps with `const-value-display.rs`. +// FIXME: I (temporarily?) removed this “split view” for const items. +// The RHS used to be `; // ` when +// the LITERAL_CONST_EXPR was a “literal” to +// preserve hexadecimal notation and numeric underscores. +// Personally, I've never come to like that special treatment +// but I can add it back in. Let me just say that this old system +// is quite inflexible and it doesn't scale to more complex expressions. + // @hasraw show_const_contents/constant.CONST_S.html 'show this' // @!hasraw show_const_contents/constant.CONST_S.html '; //' pub const CONST_S: &'static str = "show this"; @@ -9,7 +18,7 @@ pub const CONST_S: &'static str = "show this"; // @!hasraw show_const_contents/constant.CONST_I32.html '; //' pub const CONST_I32: i32 = 42; -// @hasraw show_const_contents/constant.CONST_I32_HEX.html '= 0x42;' +// @hasraw show_const_contents/constant.CONST_I32_HEX.html '= 66;' // @!hasraw show_const_contents/constant.CONST_I32_HEX.html '; //' pub const CONST_I32_HEX: i32 = 0x42; @@ -17,21 +26,23 @@ pub const CONST_I32_HEX: i32 = 0x42; // @!hasraw show_const_contents/constant.CONST_NEG_I32.html '; //' pub const CONST_NEG_I32: i32 = -42; -// @hasraw show_const_contents/constant.CONST_EQ_TO_VALUE_I32.html '= 42i32;' -// @!hasraw show_const_contents/constant.CONST_EQ_TO_VALUE_I32.html '// 42i32' +// @hasraw show_const_contents/constant.CONST_EQ_TO_VALUE_I32.html '= 42;' +// @!hasraw show_const_contents/constant.CONST_EQ_TO_VALUE_I32.html '; //' pub const CONST_EQ_TO_VALUE_I32: i32 = 42i32; -// @hasraw show_const_contents/constant.CONST_CALC_I32.html '= _; // 43i32' +// @hasraw show_const_contents/constant.CONST_CALC_I32.html '= 43;' +// @!hasraw show_const_contents/constant.CONST_CALC_I32.html '; //' pub const CONST_CALC_I32: i32 = 42 + 1; // @!hasraw show_const_contents/constant.CONST_REF_I32.html '= &42;' // @!hasraw show_const_contents/constant.CONST_REF_I32.html '; //' pub const CONST_REF_I32: &'static i32 = &42; -// @hasraw show_const_contents/constant.CONST_I32_MAX.html '= i32::MAX; // 2_147_483_647i32' +// @hasraw show_const_contents/constant.CONST_I32_MAX.html '= i32::MAX;' +// @!hasraw show_const_contents/constant.CONST_REF_I32.html '; //' pub const CONST_I32_MAX: i32 = i32::MAX; -// @!hasraw show_const_contents/constant.UNIT.html '= ();' +// @hasraw show_const_contents/constant.UNIT.html '= ();' // @!hasraw show_const_contents/constant.UNIT.html '; //' pub const UNIT: () = (); @@ -47,11 +58,14 @@ pub struct MyTypeWithStr(&'static str); // @!hasraw show_const_contents/constant.MY_TYPE_WITH_STR.html '; //' pub const MY_TYPE_WITH_STR: MyTypeWithStr = MyTypeWithStr("show this"); -// @hasraw show_const_contents/constant.PI.html '= 3.14159265358979323846264338327950288f32;' -// @hasraw show_const_contents/constant.PI.html '; // 3.14159274f32' +// FIXME: Hmm, that's bothersome :( +// @hasraw show_const_contents/constant.PI.html '= 3.14159274;' +// @!hasraw show_const_contents/constant.PI.html '; //' pub use std::f32::consts::PI; -// @hasraw show_const_contents/constant.MAX.html '= i32::MAX; // 2_147_483_647i32' +// FIXME: This is also quite sad (concrete value not shown anymore). +// @hasraw show_const_contents/constant.MAX.html '= i32::MAX;' +// @!hasraw show_const_contents/constant.PI.html '; //' #[allow(deprecated, deprecated_in_future)] pub use std::i32::MAX; @@ -61,8 +75,9 @@ macro_rules! int_module { ) } -// @hasraw show_const_contents/constant.MIN.html '= i16::MIN; // -32_768i16' +// @hasraw show_const_contents/constant.MIN.html '= i16::MIN;' +// @!hasraw show_const_contents/constant.MIN.html '; //' int_module!(i16); -// @has show_const_contents/constant.ESCAPE.html //pre '= r#""#;' +// @has show_const_contents/constant.ESCAPE.html //code '= "";' pub const ESCAPE: &str = r#""#;