diff --git a/compiler/rustc/src/main.rs b/compiler/rustc/src/main.rs index 6bc5aa6382c4c..3f03d1b68695e 100644 --- a/compiler/rustc/src/main.rs +++ b/compiler/rustc/src/main.rs @@ -24,6 +24,20 @@ fn main() { static _F5: unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void = jemalloc_sys::realloc; #[used] static _F6: unsafe extern "C" fn(*mut c_void) = jemalloc_sys::free; + + // On OSX, jemalloc doesn't directly override malloc/free, but instead + // registers itself with the allocator's zone APIs in a ctor. However, + // the linker doesn't seem to consider ctors as "used" when statically + // linking, so we need to explicitly depend on the function. + #[cfg(target_os = "macos")] + { + extern "C" { + fn _rjem_je_zone_register(); + } + + #[used] + static _F7: unsafe extern "C" fn() = _rjem_je_zone_register(); + } } rustc_driver::set_sigpipe_handler(); diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs index c293fbe4ef8ca..f2f7242a8fdde 100644 --- a/compiler/rustc_middle/src/mir/query.rs +++ b/compiler/rustc_middle/src/mir/query.rs @@ -438,18 +438,6 @@ impl<'tcx> TyCtxt<'tcx> { } } - #[inline] - pub fn optimized_mir_or_const_arg_mir( - self, - def: ty::WithOptConstParam, - ) -> &'tcx Body<'tcx> { - if let Some((did, param_did)) = def.as_const_arg() { - self.mir_for_ctfe_of_const_arg((did, param_did)) - } else { - self.optimized_mir(def.did) - } - } - #[inline] pub fn mir_for_ctfe_opt_const_arg(self, def: ty::WithOptConstParam) -> &'tcx Body<'tcx> { if let Some((did, param_did)) = def.as_const_arg() { diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs index de012a6957419..23cedfd499eaa 100644 --- a/compiler/rustc_middle/src/ty/instance.rs +++ b/compiler/rustc_middle/src/ty/instance.rs @@ -499,7 +499,7 @@ impl<'tcx> Instance<'tcx> { } /// Returns a new `Instance` where generic parameters in `instance.substs` are replaced by - /// identify parameters if they are determined to be unused in `instance.def`. + /// identity parameters if they are determined to be unused in `instance.def`. pub fn polymorphize(self, tcx: TyCtxt<'tcx>) -> Self { debug!("polymorphize: running polymorphization analysis"); if !tcx.sess.opts.debugging_opts.polymorphize { diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 2e077827873d1..c27a337554e67 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -2963,7 +2963,10 @@ impl<'tcx> TyCtxt<'tcx> { | DefKind::AnonConst => self.mir_for_ctfe_opt_const_arg(def), // If the caller wants `mir_for_ctfe` of a function they should not be using // `instance_mir`, so we'll assume const fn also wants the optimized version. - _ => self.optimized_mir_or_const_arg_mir(def), + _ => { + assert_eq!(def.const_param_did, None); + self.optimized_mir(def.did) + } }, ty::InstanceDef::VtableShim(..) | ty::InstanceDef::ReifyShim(..) diff --git a/compiler/rustc_mir/src/monomorphize/polymorphize.rs b/compiler/rustc_mir/src/monomorphize/polymorphize.rs index 4ad71ab4913ba..05b0e3a7dab95 100644 --- a/compiler/rustc_mir/src/monomorphize/polymorphize.rs +++ b/compiler/rustc_mir/src/monomorphize/polymorphize.rs @@ -30,9 +30,8 @@ pub fn provide(providers: &mut Providers) { /// Determine which generic parameters are used by the function/method/closure represented by /// `def_id`. Returns a bitset where bits representing unused parameters are set (`is_empty` /// indicates all parameters are used). +#[instrument(skip(tcx))] fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet { - debug!("unused_generic_params({:?})", def_id); - if !tcx.sess.opts.debugging_opts.polymorphize { // If polymorphization disabled, then all parameters are used. return FiniteBitSet::new_empty(); @@ -46,7 +45,7 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet { } let generics = tcx.generics_of(def_id); - debug!("unused_generic_params: generics={:?}", generics); + debug!(?generics); // Exit early when there are no parameters to be unused. if generics.count() == 0 { @@ -57,11 +56,11 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet { let context = tcx.hir().body_const_context(def_id.expect_local()); match context { Some(ConstContext::ConstFn) | None if !tcx.is_mir_available(def_id) => { - debug!("unused_generic_params: (no mir available) def_id={:?}", def_id); + debug!("no mir available"); return FiniteBitSet::new_empty(); } Some(_) if !tcx.is_ctfe_mir_available(def_id) => { - debug!("unused_generic_params: (no ctfe mir available) def_id={:?}", def_id); + debug!("no ctfe mir available"); return FiniteBitSet::new_empty(); } _ => {} @@ -72,9 +71,9 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet { generics.count().try_into().expect("more generic parameters than can fit into a `u32`"); let mut unused_parameters = FiniteBitSet::::new_empty(); unused_parameters.set_range(0..generics_count); - debug!("unused_generic_params: (start) unused_parameters={:?}", unused_parameters); + debug!(?unused_parameters, "(start)"); mark_used_by_default_parameters(tcx, def_id, generics, &mut unused_parameters); - debug!("unused_generic_params: (after default) unused_parameters={:?}", unused_parameters); + debug!(?unused_parameters, "(after default)"); // Visit MIR and accumululate used generic parameters. let body = match context { @@ -85,10 +84,10 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet { }; let mut vis = MarkUsedGenericParams { tcx, def_id, unused_parameters: &mut unused_parameters }; vis.visit_body(body); - debug!("unused_generic_params: (after visitor) unused_parameters={:?}", unused_parameters); + debug!(?unused_parameters, "(after visitor)"); mark_used_by_predicates(tcx, def_id, &mut unused_parameters); - debug!("unused_generic_params: (end) unused_parameters={:?}", unused_parameters); + debug!(?unused_parameters, "(end)"); // Emit errors for debugging and testing if enabled. if !unused_parameters.is_empty() { @@ -101,24 +100,55 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet { /// Some parameters are considered used-by-default, such as non-generic parameters and the dummy /// generic parameters from closures, this function marks them as used. `leaf_is_closure` should /// be `true` if the item that `unused_generic_params` was invoked on is a closure. +#[instrument(skip(tcx, def_id, generics, unused_parameters))] fn mark_used_by_default_parameters<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, generics: &'tcx ty::Generics, unused_parameters: &mut FiniteBitSet, ) { - if !tcx.is_trait(def_id) && (tcx.is_closure(def_id) || tcx.type_of(def_id).is_generator()) { - for param in &generics.params { - debug!("mark_used_by_default_parameters: (closure/gen) param={:?}", param); - unused_parameters.clear(param.index); - } - } else { - for param in &generics.params { - debug!("mark_used_by_default_parameters: (other) param={:?}", param); - if let ty::GenericParamDefKind::Lifetime = param.kind { + match tcx.def_kind(def_id) { + DefKind::Closure | DefKind::Generator => { + for param in &generics.params { + debug!(?param, "(closure/gen)"); unused_parameters.clear(param.index); } } + DefKind::Mod + | DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Variant + | DefKind::Trait + | DefKind::TyAlias + | DefKind::ForeignTy + | DefKind::TraitAlias + | DefKind::AssocTy + | DefKind::TyParam + | DefKind::Fn + | DefKind::Const + | DefKind::ConstParam + | DefKind::Static + | DefKind::Ctor(_, _) + | DefKind::AssocFn + | DefKind::AssocConst + | DefKind::Macro(_) + | DefKind::ExternCrate + | DefKind::Use + | DefKind::ForeignMod + | DefKind::AnonConst + | DefKind::OpaqueTy + | DefKind::Field + | DefKind::LifetimeParam + | DefKind::GlobalAsm + | DefKind::Impl => { + for param in &generics.params { + debug!(?param, "(other)"); + if let ty::GenericParamDefKind::Lifetime = param.kind { + unused_parameters.clear(param.index); + } + } + } } if let Some(parent) = generics.parent { @@ -128,6 +158,7 @@ fn mark_used_by_default_parameters<'tcx>( /// Search the predicates on used generic parameters for any unused generic parameters, and mark /// those as used. +#[instrument(skip(tcx, def_id))] fn mark_used_by_predicates<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, @@ -135,16 +166,12 @@ fn mark_used_by_predicates<'tcx>( ) { let def_id = tcx.closure_base_def_id(def_id); let predicates = tcx.explicit_predicates_of(def_id); - debug!("mark_used_by_predicates: predicates_of={:?}", predicates); let mut current_unused_parameters = FiniteBitSet::new_empty(); // Run to a fixed point to support `where T: Trait, U: Trait`, starting with an empty // bit set so that this is skipped if all parameters are already used. while current_unused_parameters != *unused_parameters { - debug!( - "mark_used_by_predicates: current_unused_parameters={:?} = unused_parameters={:?}", - current_unused_parameters, unused_parameters - ); + debug!(?current_unused_parameters, ?unused_parameters); current_unused_parameters = *unused_parameters; for (predicate, _) in predicates.predicates { @@ -169,13 +196,13 @@ fn mark_used_by_predicates<'tcx>( /// Emit errors for the function annotated by `#[rustc_polymorphize_error]`, labelling each generic /// parameter which was unused. +#[instrument(skip(tcx, generics))] fn emit_unused_generic_params_error<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, generics: &'tcx ty::Generics, unused_parameters: &FiniteBitSet, ) { - debug!("emit_unused_generic_params_error: def_id={:?}", def_id); let base_def_id = tcx.closure_base_def_id(def_id); if !tcx .get_attrs(base_def_id) @@ -185,7 +212,6 @@ fn emit_unused_generic_params_error<'tcx>( return; } - debug!("emit_unused_generic_params_error: unused_parameters={:?}", unused_parameters); let fn_span = match tcx.opt_item_name(def_id) { Some(ident) => ident.span, _ => tcx.def_span(def_id), @@ -197,7 +223,7 @@ fn emit_unused_generic_params_error<'tcx>( while let Some(generics) = next_generics { for param in &generics.params { if unused_parameters.contains(param.index).unwrap_or(false) { - debug!("emit_unused_generic_params_error: param={:?}", param); + debug!(?param); let def_span = tcx.def_span(param.def_id); err.span_label(def_span, &format!("generic parameter `{}` is unused", param.name)); } @@ -219,25 +245,23 @@ struct MarkUsedGenericParams<'a, 'tcx> { impl<'a, 'tcx> MarkUsedGenericParams<'a, 'tcx> { /// Invoke `unused_generic_params` on a body contained within the current item (e.g. /// a closure, generator or constant). + #[instrument(skip(self, def_id, substs))] fn visit_child_body(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) { let unused = self.tcx.unused_generic_params(def_id); - debug!( - "visit_child_body: unused_parameters={:?} unused={:?}", - self.unused_parameters, unused - ); + debug!(?self.unused_parameters, ?unused); for (i, arg) in substs.iter().enumerate() { let i = i.try_into().unwrap(); if !unused.contains(i).unwrap_or(false) { arg.visit_with(self); } } - debug!("visit_child_body: unused_parameters={:?}", self.unused_parameters); + debug!(?self.unused_parameters); } } impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { + #[instrument(skip(self, local))] fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) { - debug!("visit_local_decl: local_decl={:?}", local_decl); if local == Local::from_usize(1) { let def_kind = self.tcx.def_kind(self.def_id); if matches!(def_kind, DefKind::Closure | DefKind::Generator) { @@ -245,7 +269,7 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { // happens because the first argument to the closure is a reference to itself and // that will call `visit_substs`, resulting in each generic parameter captured being // considered used by default. - debug!("visit_local_decl: skipping closure substs"); + debug!("skipping closure substs"); return; } } @@ -263,15 +287,15 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { } impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { + #[instrument(skip(self))] fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow { - debug!("visit_const: c={:?}", c); if !c.has_param_types_or_consts() { return ControlFlow::CONTINUE; } match c.val { ty::ConstKind::Param(param) => { - debug!("visit_const: param={:?}", param); + debug!(?param); self.unused_parameters.clear(param.index); ControlFlow::CONTINUE } @@ -296,15 +320,15 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { } } + #[instrument(skip(self))] fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { - debug!("visit_ty: ty={:?}", ty); if !ty.has_param_types_or_consts() { return ControlFlow::CONTINUE; } match *ty.kind() { ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => { - debug!("visit_ty: def_id={:?}", def_id); + debug!(?def_id); // Avoid cycle errors with generators. if def_id == self.def_id { return ControlFlow::CONTINUE; @@ -316,7 +340,7 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { ControlFlow::CONTINUE } ty::Param(param) => { - debug!("visit_ty: param={:?}", param); + debug!(?param); self.unused_parameters.clear(param.index); ControlFlow::CONTINUE } @@ -333,8 +357,8 @@ struct HasUsedGenericParams<'a> { impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> { type BreakTy = (); + #[instrument(skip(self))] fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow { - debug!("visit_const: c={:?}", c); if !c.has_param_types_or_consts() { return ControlFlow::CONTINUE; } @@ -351,8 +375,8 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> { } } + #[instrument(skip(self))] fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { - debug!("visit_ty: ty={:?}", ty); if !ty.has_param_types_or_consts() { return ControlFlow::CONTINUE; } diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index bd0296751a535..61f4c00a4ca42 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -156,6 +156,21 @@ impl<'a> NameResolution<'a> { } } +// Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;` +// are permitted for backward-compatibility under a deprecation lint. +fn pub_use_of_private_extern_crate_hack(import: &Import<'_>, binding: &NameBinding<'_>) -> bool { + match (&import.kind, &binding.kind) { + ( + ImportKind::Single { .. }, + NameBindingKind::Import { + import: Import { kind: ImportKind::ExternCrate { .. }, .. }, + .. + }, + ) => import.vis.get() == ty::Visibility::Public, + _ => false, + } +} + impl<'a> Resolver<'a> { crate fn resolve_ident_in_module_unadjusted( &mut self, @@ -263,10 +278,7 @@ impl<'a> Resolver<'a> { return Err((Determined, Weak::No)); } } - // `extern crate` are always usable for backwards compatibility, see issue #37020, - // remove this together with `PUB_USE_OF_PRIVATE_EXTERN_CRATE`. - let usable = this.is_accessible_from(binding.vis, parent_scope.module) - || binding.is_extern_crate(); + let usable = this.is_accessible_from(binding.vis, parent_scope.module); if usable { Ok(binding) } else { Err((Determined, Weak::No)) } }; @@ -309,10 +321,7 @@ impl<'a> Resolver<'a> { } } - if !(self.is_accessible_from(binding.vis, parent_scope.module) || - // Remove this together with `PUB_USE_OF_PRIVATE_EXTERN_CRATE` - (self.last_import_segment && binding.is_extern_crate())) - { + if !self.is_accessible_from(binding.vis, parent_scope.module) { self.privacy_errors.push(PrivacyError { ident, binding, @@ -455,9 +464,8 @@ impl<'a> Resolver<'a> { binding: &'a NameBinding<'a>, import: &'a Import<'a>, ) -> &'a NameBinding<'a> { - let vis = if binding.vis.is_at_least(import.vis.get(), self) || - // cf. `PUB_USE_OF_PRIVATE_EXTERN_CRATE` - !import.is_glob() && binding.is_extern_crate() + let vis = if binding.vis.is_at_least(import.vis.get(), self) + || pub_use_of_private_extern_crate_hack(import, binding) { import.vis.get() } else { @@ -1188,7 +1196,7 @@ impl<'a, 'b> ImportResolver<'a, 'b> { // All namespaces must be re-exported with extra visibility for an error to occur. if !any_successful_reexport { let (ns, binding) = reexport_error.unwrap(); - if ns == TypeNS && binding.is_extern_crate() { + if pub_use_of_private_extern_crate_hack(import, binding) { let msg = format!( "extern crate `{}` is private, and cannot be \ re-exported (error E0365), consider declaring with \ diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs index 783f88f026b8f..622983996aa08 100644 --- a/library/alloc/src/collections/btree/map.rs +++ b/library/alloc/src/collections/btree/map.rs @@ -14,7 +14,7 @@ use super::node::{self, marker, ForceResult::*, Handle, NodeRef, Root}; use super::search::SearchResult::*; mod entry; -pub use entry::{Entry, OccupiedEntry, VacantEntry}; +pub use entry::{Entry, OccupiedEntry, OccupiedError, VacantEntry}; use Entry::*; /// Minimum number of elements in nodes that are not a root. @@ -836,6 +836,40 @@ impl BTreeMap { } } + /// Tries to insert a key-value pair into the map, and returns + /// a mutable reference to the value in the entry. + /// + /// If the map already had this key present, nothing is updated, and + /// an error containing the occupied entry and the value is returned. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(map_try_insert)] + /// + /// use std::collections::BTreeMap; + /// + /// let mut map = BTreeMap::new(); + /// assert_eq!(map.try_insert(37, "a").unwrap(), &"a"); + /// + /// let err = map.try_insert(37, "b").unwrap_err(); + /// assert_eq!(err.entry.key(), &37); + /// assert_eq!(err.entry.get(), &"a"); + /// assert_eq!(err.value, "b"); + /// ``` + #[unstable(feature = "map_try_insert", issue = "82766")] + pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>> + where + K: Ord, + { + match self.entry(key) { + Occupied(entry) => Err(OccupiedError { entry, value }), + Vacant(entry) => Ok(entry.insert(value)), + } + } + /// Removes a key from the map, returning the value at the key if the key /// was previously in the map. /// diff --git a/library/alloc/src/collections/btree/map/entry.rs b/library/alloc/src/collections/btree/map/entry.rs index 941f82a8070a0..6b30d95977395 100644 --- a/library/alloc/src/collections/btree/map/entry.rs +++ b/library/alloc/src/collections/btree/map/entry.rs @@ -71,6 +71,41 @@ impl Debug for OccupiedEntry<'_, K, V> { } } +/// The error returned by [`try_insert`](BTreeMap::try_insert) when the key already exists. +/// +/// Contains the occupied entry, and the value that was not inserted. +#[unstable(feature = "map_try_insert", issue = "82766")] +pub struct OccupiedError<'a, K: 'a, V: 'a> { + /// The entry in the map that was already occupied. + pub entry: OccupiedEntry<'a, K, V>, + /// The value which was not inserted, because the entry was already occupied. + pub value: V, +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl Debug for OccupiedError<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OccupiedError") + .field("key", self.entry.key()) + .field("old_value", self.entry.get()) + .field("new_value", &self.value) + .finish() + } +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<'a, K: Debug + Ord, V: Debug> fmt::Display for OccupiedError<'a, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "failed to insert {:?}, key {:?} already exists with value {:?}", + self.value, + self.entry.key(), + self.entry.get(), + ) + } +} + impl<'a, K: Ord, V> Entry<'a, K, V> { /// Ensures a value is in the entry by inserting the default if empty, and returns /// a mutable reference to the value in the entry. diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 9a54921f07b49..3e70ba81d4997 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -110,6 +110,60 @@ macro_rules! assert_ne { }); } +/// Asserts that an expression matches any of the given patterns. +/// +/// Like in a `match` expression, the pattern can be optionally followed by `if` +/// and a guard expression that has access to names bound by the pattern. +/// +/// On panic, this macro will print the value of the expression with its +/// debug representation. +/// +/// Like [`assert!`], this macro has a second form, where a custom +/// panic message can be provided. +/// +/// # Examples +/// +/// ``` +/// #![feature(assert_matches)] +/// +/// let a = 1u32.checked_add(2); +/// let b = 1u32.checked_sub(2); +/// assert_matches!(a, Some(_)); +/// assert_matches!(b, None); +/// +/// let c = Ok("abc".to_string()); +/// assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); +/// ``` +#[macro_export] +#[unstable(feature = "assert_matches", issue = "82775")] +#[allow_internal_unstable(core_panic)] +macro_rules! assert_matches { + ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({ + match $left { + $( $pattern )|+ $( if $guard )? => {} + ref left_val => { + $crate::panicking::assert_matches_failed( + left_val, + $crate::stringify!($($pattern)|+ $(if $guard)?), + $crate::option::Option::None + ); + } + } + }); + ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )?, $($arg:tt)+) => ({ + match $left { + $( $pattern )|+ $( if $guard )? => {} + ref left_val => { + $crate::panicking::assert_matches_failed( + left_val, + $crate::stringify!($($pattern)|+ $(if $guard)?), + $crate::option::Option::Some($crate::format_args!($($arg)+)) + ); + } + } + }); +} + /// Asserts that a boolean expression is `true` at runtime. /// /// This will invoke the [`panic!`] macro if the provided expression cannot be @@ -208,6 +262,42 @@ macro_rules! debug_assert_ne { ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_ne!($($arg)*); }) } +/// Asserts that an expression matches any of the given patterns. +/// +/// Like in a `match` expression, the pattern can be optionally followed by `if` +/// and a guard expression that has access to names bound by the pattern. +/// +/// On panic, this macro will print the value of the expression with its +/// debug representation. +/// +/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only +/// enabled in non optimized builds by default. An optimized build will not +/// execute `debug_assert_matches!` statements unless `-C debug-assertions` is +/// passed to the compiler. This makes `debug_assert_matches!` useful for +/// checks that are too expensive to be present in a release build but may be +/// helpful during development. The result of expanding `debug_assert_matches!` +/// is always type checked. +/// +/// # Examples +/// +/// ``` +/// #![feature(assert_matches)] +/// +/// let a = 1u32.checked_add(2); +/// let b = 1u32.checked_sub(2); +/// debug_assert_matches!(a, Some(_)); +/// debug_assert_matches!(b, None); +/// +/// let c = Ok("abc".to_string()); +/// debug_assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); +/// ``` +#[macro_export] +#[unstable(feature = "assert_matches", issue = "82775")] +#[allow_internal_unstable(assert_matches)] +macro_rules! debug_assert_matches { + ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_matches!($($arg)*); }) +} + /// Returns whether the given expression matches any of the given patterns. /// /// Like in a `match` expression, the pattern can be optionally followed by `if` diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index af8a6101392a4..12acf5b4329db 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -97,6 +97,7 @@ pub fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! { pub enum AssertKind { Eq, Ne, + Match, } /// Internal function for `assert_eq!` and `assert_ne!` macros @@ -113,32 +114,54 @@ where T: fmt::Debug + ?Sized, U: fmt::Debug + ?Sized, { - #[track_caller] - fn inner( - kind: AssertKind, - left: &dyn fmt::Debug, - right: &dyn fmt::Debug, - args: Option>, - ) -> ! { - let op = match kind { - AssertKind::Eq => "==", - AssertKind::Ne => "!=", - }; - - match args { - Some(args) => panic!( - r#"assertion failed: `(left {} right)` + assert_failed_inner(kind, &left, &right, args) +} + +/// Internal function for `assert_match!` +#[cold] +#[track_caller] +#[doc(hidden)] +pub fn assert_matches_failed( + left: &T, + right: &str, + args: Option>, +) -> ! { + // Use the Display implementation to display the pattern. + struct Pattern<'a>(&'a str); + impl fmt::Debug for Pattern<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.0, f) + } + } + assert_failed_inner(AssertKind::Match, &left, &Pattern(right), args); +} + +/// Non-generic version of the above functions, to avoid code bloat. +#[track_caller] +fn assert_failed_inner( + kind: AssertKind, + left: &dyn fmt::Debug, + right: &dyn fmt::Debug, + args: Option>, +) -> ! { + let op = match kind { + AssertKind::Eq => "==", + AssertKind::Ne => "!=", + AssertKind::Match => "matches", + }; + + match args { + Some(args) => panic!( + r#"assertion failed: `(left {} right)` left: `{:?}`, right: `{:?}: {}`"#, - op, left, right, args - ), - None => panic!( - r#"assertion failed: `(left {} right)` + op, left, right, args + ), + None => panic!( + r#"assertion failed: `(left {} right)` left: `{:?}`, right: `{:?}`"#, - op, left, right, - ), - } + op, left, right, + ), } - inner(kind, &left, &right, args) } diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs index 27f7191831d41..233afa9238999 100644 --- a/library/std/src/collections/hash/map.rs +++ b/library/std/src/collections/hash/map.rs @@ -1,3 +1,5 @@ +// ignore-tidy-filelength + #[cfg(test)] mod tests; @@ -842,6 +844,37 @@ where self.base.insert(k, v) } + /// Tries to insert a key-value pair into the map, and returns + /// a mutable reference to the value in the entry. + /// + /// If the map already had this key present, nothing is updated, and + /// an error containing the occupied entry and the value is returned. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(map_try_insert)] + /// + /// use std::collections::HashMap; + /// + /// let mut map = HashMap::new(); + /// assert_eq!(map.try_insert(37, "a").unwrap(), &"a"); + /// + /// let err = map.try_insert(37, "b").unwrap_err(); + /// assert_eq!(err.entry.key(), &37); + /// assert_eq!(err.entry.get(), &"a"); + /// assert_eq!(err.value, "b"); + /// ``` + #[unstable(feature = "map_try_insert", issue = "82766")] + pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>> { + match self.entry(key) { + Occupied(entry) => Err(OccupiedError { entry, value }), + Vacant(entry) => Ok(entry.insert(value)), + } + } + /// Removes a key from the map, returning the value at the key if the key /// was previously in the map. /// @@ -1851,6 +1884,41 @@ impl Debug for VacantEntry<'_, K, V> { } } +/// The error returned by [`try_insert`](HashMap::try_insert) when the key already exists. +/// +/// Contains the occupied entry, and the value that was not inserted. +#[unstable(feature = "map_try_insert", issue = "82766")] +pub struct OccupiedError<'a, K: 'a, V: 'a> { + /// The entry in the map that was already occupied. + pub entry: OccupiedEntry<'a, K, V>, + /// The value which was not inserted, because the entry was already occupied. + pub value: V, +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl Debug for OccupiedError<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OccupiedError") + .field("key", self.entry.key()) + .field("old_value", self.entry.get()) + .field("new_value", &self.value) + .finish() + } +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<'a, K: Debug, V: Debug> fmt::Display for OccupiedError<'a, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "failed to insert {:?}, key {:?} already exists with value {:?}", + self.value, + self.entry.key(), + self.entry.get(), + ) + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl<'a, K, V, S> IntoIterator for &'a HashMap { type Item = (&'a K, &'a V); diff --git a/library/std/src/error.rs b/library/std/src/error.rs index 94338c7b04d06..80c35307d52ac 100644 --- a/library/std/src/error.rs +++ b/library/std/src/error.rs @@ -470,6 +470,24 @@ impl Error for char::DecodeUtf16Error { } } +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<'a, K: Debug + Ord, V: Debug> Error + for crate::collections::btree_map::OccupiedError<'a, K, V> +{ + #[allow(deprecated)] + fn description(&self) -> &str { + "key already exists" + } +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<'a, K: Debug, V: Debug> Error for crate::collections::hash_map::OccupiedError<'a, K, V> { + #[allow(deprecated)] + fn description(&self) -> &str { + "key already exists" + } +} + #[stable(feature = "box_error", since = "1.8.0")] impl Error for Box { #[allow(deprecated, deprecated_in_future)] diff --git a/library/std/src/io/buffered/bufreader.rs b/library/std/src/io/buffered/bufreader.rs index 987371f50ec22..839c64ee5c443 100644 --- a/library/std/src/io/buffered/bufreader.rs +++ b/library/std/src/io/buffered/bufreader.rs @@ -1,6 +1,8 @@ use crate::cmp; use crate::fmt; -use crate::io::{self, BufRead, Initializer, IoSliceMut, Read, Seek, SeekFrom, DEFAULT_BUF_SIZE}; +use crate::io::{ + self, BufRead, Initializer, IoSliceMut, Read, Seek, SeekFrom, SizeHint, DEFAULT_BUF_SIZE, +}; /// The `BufReader` struct adds buffering to any reader. /// @@ -435,3 +437,9 @@ impl Seek for BufReader { }) } } + +impl SizeHint for BufReader { + fn lower_bound(&self) -> usize { + self.buffer().len() + } +} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 2291498740510..17002e3b8602d 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -2238,6 +2238,19 @@ impl BufRead for Chain { } } +impl SizeHint for Chain { + fn lower_bound(&self) -> usize { + SizeHint::lower_bound(&self.first) + SizeHint::lower_bound(&self.second) + } + + fn upper_bound(&self) -> Option { + match (SizeHint::upper_bound(&self.first), SizeHint::upper_bound(&self.second)) { + (Some(first), Some(second)) => Some(first + second), + _ => None, + } + } +} + /// Reader adaptor which limits the bytes read from an underlying reader. /// /// This struct is generally created by calling [`take`] on a reader. @@ -2464,6 +2477,30 @@ impl Iterator for Bytes { }; } } + + fn size_hint(&self) -> (usize, Option) { + SizeHint::size_hint(&self.inner) + } +} + +trait SizeHint { + fn lower_bound(&self) -> usize; + + fn upper_bound(&self) -> Option; + + fn size_hint(&self) -> (usize, Option) { + (self.lower_bound(), self.upper_bound()) + } +} + +impl SizeHint for T { + default fn lower_bound(&self) -> usize { + 0 + } + + default fn upper_bound(&self) -> Option { + None + } } /// An iterator over the contents of an instance of `BufRead` split on a diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs index f176c2f088cb3..a85dd0d982715 100644 --- a/library/std/src/io/tests.rs +++ b/library/std/src/io/tests.rs @@ -1,7 +1,7 @@ use super::{repeat, Cursor, SeekFrom}; use crate::cmp::{self, min}; use crate::io::{self, IoSlice, IoSliceMut}; -use crate::io::{BufRead, Read, Seek, Write}; +use crate::io::{BufRead, BufReader, Read, Seek, Write}; use crate::ops::Deref; #[test] @@ -198,6 +198,53 @@ fn chain_bufread() { cmp_bufread(chain1, chain2, &testdata[..]); } +#[test] +fn bufreader_size_hint() { + let testdata = b"ABCDEFGHIJKL"; + let mut buf_reader = BufReader::new(&testdata[..]); + assert_eq!(buf_reader.buffer().len(), 0); + + let buffer_length = testdata.len(); + buf_reader.fill_buf().unwrap(); + + // Check that size hint matches buffer contents + let mut buffered_bytes = buf_reader.bytes(); + let (lower_bound, _upper_bound) = buffered_bytes.size_hint(); + assert_eq!(lower_bound, buffer_length); + + // Check that size hint matches buffer contents after advancing + buffered_bytes.next().unwrap().unwrap(); + let (lower_bound, _upper_bound) = buffered_bytes.size_hint(); + assert_eq!(lower_bound, buffer_length - 1); +} + +#[test] +fn empty_size_hint() { + let size_hint = io::empty().bytes().size_hint(); + assert_eq!(size_hint, (0, Some(0))); +} + +#[test] +fn chain_empty_size_hint() { + let chain = io::empty().chain(io::empty()); + let size_hint = chain.bytes().size_hint(); + assert_eq!(size_hint, (0, Some(0))); +} + +#[test] +fn chain_size_hint() { + let testdata = b"ABCDEFGHIJKL"; + let mut buf_reader_1 = BufReader::new(&testdata[..6]); + let mut buf_reader_2 = BufReader::new(&testdata[6..]); + + buf_reader_1.fill_buf().unwrap(); + buf_reader_2.fill_buf().unwrap(); + + let chain = buf_reader_1.chain(buf_reader_2); + let size_hint = chain.bytes().size_hint(); + assert_eq!(size_hint, (testdata.len(), None)); +} + #[test] fn chain_zero_length_read_is_not_eof() { let a = b"A"; diff --git a/library/std/src/io/util.rs b/library/std/src/io/util.rs index e43ce4cdb4b8e..f472361f916db 100644 --- a/library/std/src/io/util.rs +++ b/library/std/src/io/util.rs @@ -4,7 +4,9 @@ mod tests; use crate::fmt; -use crate::io::{self, BufRead, Initializer, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; +use crate::io::{ + self, BufRead, Initializer, IoSlice, IoSliceMut, Read, Seek, SeekFrom, SizeHint, Write, +}; /// A reader which is always at EOF. /// @@ -80,6 +82,12 @@ impl fmt::Debug for Empty { } } +impl SizeHint for Empty { + fn upper_bound(&self) -> Option { + Some(0) + } +} + /// A reader which yields one byte over and over and over and over and over and... /// /// This struct is generally created by calling [`repeat()`]. Please diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ba49dee38e642..07925f255dd54 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -228,6 +228,7 @@ #![feature(arbitrary_self_types)] #![feature(array_error_internals)] #![feature(asm)] +#![feature(assert_matches)] #![feature(associated_type_bounds)] #![feature(atomic_mut_ptr)] #![feature(box_syntax)] @@ -281,6 +282,7 @@ #![feature(linkage)] #![feature(llvm_asm)] #![feature(log_syntax)] +#![feature(map_try_insert)] #![feature(maybe_uninit_extra)] #![feature(maybe_uninit_ref)] #![feature(maybe_uninit_slice)] @@ -550,8 +552,8 @@ pub use std_detect::detect; #[stable(feature = "rust1", since = "1.0.0")] #[allow(deprecated, deprecated_in_future)] pub use core::{ - assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, matches, r#try, todo, - unimplemented, unreachable, write, writeln, + assert_eq, assert_matches, assert_ne, debug_assert, debug_assert_eq, debug_assert_matches, + debug_assert_ne, matches, r#try, todo, unimplemented, unreachable, write, writeln, }; // Re-export built-in macros defined through libcore. diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md index 943aa09f678d7..ae94527e2b4ca 100644 --- a/src/doc/rustdoc/src/SUMMARY.md +++ b/src/doc/rustdoc/src/SUMMARY.md @@ -8,7 +8,7 @@ - [Documentation tests](documentation-tests.md) - [Linking to items by name](linking-to-items-by-name.md) - [Lints](lints.md) -- [Passes](passes.md) - [Advanced features](advanced-features.md) - [Unstable features](unstable-features.md) +- [Passes](passes.md) - [References](references.md) diff --git a/src/doc/rustdoc/src/command-line-arguments.md b/src/doc/rustdoc/src/command-line-arguments.md index 0302fbecb6ed0..2e4016e24bc3f 100644 --- a/src/doc/rustdoc/src/command-line-arguments.md +++ b/src/doc/rustdoc/src/command-line-arguments.md @@ -57,23 +57,6 @@ release: 1.17.0 LLVM version: 3.9 ``` -## `-r`/`--input-format`: input format - -This flag is currently ignored; the idea is that `rustdoc` would support various -input formats, and you could specify them via this flag. - -Rustdoc only supports Rust source code and Markdown input formats. If the -file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file. -Otherwise, it assumes that the input file is Rust. - - -## `-w`/`--output-format`: output format - -This flag is currently ignored; the idea is that `rustdoc` would support -various output formats, and you could specify them via this flag. - -Rustdoc only supports HTML output, and so this flag is redundant today. - ## `-o`/`--output`: output path Using this flag looks like this: @@ -100,6 +83,25 @@ By default, `rustdoc` assumes that the name of your crate is the same name as the `.rs` file. `--crate-name` lets you override this assumption with whatever name you choose. +## `--document-private-items`: Show items that are not public + +Using this flag looks like this: + +```bash +$ rustdoc src/lib.rs --document-private-items +``` + +By default, `rustdoc` only documents items that are publicly reachable. + +```rust +pub fn public() {} // this item is public and will documented +mod private { // this item is private and will not be documented + pub fn unreachable() {} // this item is public, but unreachable, so it will not be documented +} +``` + +`--document-private-items` documents all items, even if they're not public. + ## `-L`/`--library-path`: where to look for dependencies Using this flag looks like this: @@ -166,38 +168,6 @@ affect that. The arguments to this flag are the same as those for the `-C` flag on rustc. Run `rustc -C help` to get the full list. -## `--passes`: add more rustdoc passes - -Using this flag looks like this: - -```bash -$ rustdoc --passes list -$ rustdoc src/lib.rs --passes strip-priv-imports -``` - -An argument of "list" will print a list of possible "rustdoc passes", and other -arguments will be the name of which passes to run in addition to the defaults. - -For more details on passes, see [the chapter on them](passes.md). - -See also `--no-defaults`. - -## `--no-defaults`: don't run default passes - -Using this flag looks like this: - -```bash -$ rustdoc src/lib.rs --no-defaults -``` - -By default, `rustdoc` will run several passes over your code. This -removes those defaults, allowing you to use `--passes` to specify -exactly which passes you want. - -For more details on passes, see [the chapter on them](passes.md). - -See also `--passes`. - ## `--test`: run code examples as tests Using this flag looks like this: @@ -429,3 +399,21 @@ If you specify `@path` on the command-line, then it will open `path` and read command line options from it. These options are one per line; a blank line indicates an empty option. The file can use Unix or Windows style line endings, and must be encoded as UTF-8. + +## `--passes`: add more rustdoc passes + +This flag is **deprecated**. +For more details on passes, see [the chapter on them](passes.md). + +## `--no-defaults`: don't run default passes + +This flag is **deprecated**. +For more details on passes, see [the chapter on them](passes.md). + +## `-r`/`--input-format`: input format + +This flag is **deprecated** and **has no effect**. + +Rustdoc only supports Rust source code and Markdown input formats. If the +file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file. +Otherwise, it assumes that the input file is Rust. diff --git a/src/doc/rustdoc/src/passes.md b/src/doc/rustdoc/src/passes.md index 140b832f19a54..c3c3fd3068ec4 100644 --- a/src/doc/rustdoc/src/passes.md +++ b/src/doc/rustdoc/src/passes.md @@ -3,86 +3,9 @@ Rustdoc has a concept called "passes". These are transformations that `rustdoc` runs on your documentation before producing its final output. -In addition to the passes below, check out the docs for these flags: +Customizing passes is **deprecated**. The available passes are not considered stable and may +change in any release. -* [`--passes`](command-line-arguments.md#--passes-add-more-rustdoc-passes) -* [`--no-defaults`](command-line-arguments.md#--no-defaults-dont-run-default-passes) - -## Default passes - -By default, rustdoc will run some passes, namely: - -* `strip-hidden` -* `strip-private` -* `collapse-docs` -* `unindent-comments` - -However, `strip-private` implies `strip-priv-imports`, and so effectively, -all passes are run by default. - -## `strip-hidden` - -This pass implements the `#[doc(hidden)]` attribute. When this pass runs, it -checks each item, and if it is annotated with this attribute, it removes it -from `rustdoc`'s output. - -Without this pass, these items will remain in the output. - -## `unindent-comments` - -When you write a doc comment like this: - -```rust,no_run -/// This is a documentation comment. -# fn f() {} -``` - -There's a space between the `///` and that `T`. That spacing isn't intended -to be a part of the output; it's there for humans, to help separate the doc -comment syntax from the text of the comment. This pass is what removes that -space. - -The exact rules are left under-specified so that we can fix issues that we find. - -Without this pass, the exact number of spaces is preserved. - -## `collapse-docs` - -With this pass, multiple `#[doc]` attributes are converted into one single -documentation string. - -For example: - -```rust,no_run -#[doc = "This is the first line."] -#[doc = "This is the second line."] -# fn f() {} -``` - -Gets collapsed into a single doc string of - -```text -This is the first line. -This is the second line. -``` - -## `strip-private` - -This removes documentation for any non-public items, so for example: - -```rust,no_run -/// These are private docs. -struct Private; - -/// These are public docs. -pub struct Public; -``` - -This pass removes the docs for `Private`, since they're not public. - -This pass implies `strip-priv-imports`. - -## `strip-priv-imports` - -This is the same as `strip-private`, but for `extern crate` and `use` -statements instead of items. +In the past the most common use case for customizing passes was to omit the `strip-private` pass. +You can do this more easily, and without risk of the pass being changed, by passing +[`--document-private-items`](./unstable-features.md#--document-private-items). diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index b43070510413a..7d1845dc9578e 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -340,6 +340,30 @@ Some methodology notes about what rustdoc counts in this metric: Public items that are not documented can be seen with the built-in `missing_docs` lint. Private items that are not documented can be seen with Clippy's `missing_docs_in_private_items` lint. +## `-w`/`--output-format`: output format + +When using +[`--show-coverage`](https://doc.rust-lang.org/nightly/rustdoc/unstable-features.html#--show-coverage-get-statistics-about-code-documentation-coverage), +passing `--output-format json` will display the coverage information in JSON format. For example, +here is the JSON for a file with one documented item and one undocumented item: + +```rust +/// This item has documentation +pub fn foo() {} + +pub fn no_documentation() {} +``` + +```json +{"no_std.rs":{"total":3,"with_docs":1,"total_examples":3,"with_examples":0}} +``` + +Note that the third item is the crate root, which in this case is undocumented. + +When not using `--show-coverage`, `--output-format json` emits documentation in the experimental +[JSON format](https://github.com/rust-lang/rfcs/pull/2963). `--output-format html` has no effect, +and is also accepted on stable toolchains. + ### `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests Using this flag looks like this: diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs new file mode 100644 index 0000000000000..976168a9ac407 --- /dev/null +++ b/src/librustdoc/html/render/context.rs @@ -0,0 +1,617 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::mpsc::{channel, Receiver}; +use std::sync::Arc; + +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_middle::ty::TyCtxt; +use rustc_session::Session; +use rustc_span::edition::Edition; +use rustc_span::source_map::FileName; +use rustc_span::symbol::sym; + +use super::cache::{build_index, ExternalLocation}; +use super::print_item::{full_path, item_path, print_item}; +use super::write_shared::write_shared; +use super::{ + print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS, + CURRENT_DEPTH, INITIAL_IDS, +}; + +use crate::clean::{self, AttributesExt}; +use crate::config::RenderOptions; +use crate::docfs::{DocFS, PathError}; +use crate::error::Error; +use crate::formats::cache::Cache; +use crate::formats::item_type::ItemType; +use crate::formats::FormatRenderer; +use crate::html::escape::Escape; +use crate::html::format::Buffer; +use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; +use crate::html::{layout, sources}; + +/// Major driving force in all rustdoc rendering. This contains information +/// about where in the tree-like hierarchy rendering is occurring and controls +/// how the current page is being rendered. +/// +/// It is intended that this context is a lightweight object which can be fairly +/// easily cloned because it is cloned per work-job (about once per item in the +/// rustdoc tree). +#[derive(Clone)] +crate struct Context<'tcx> { + /// Current hierarchy of components leading down to what's currently being + /// rendered + crate current: Vec, + /// The current destination folder of where HTML artifacts should be placed. + /// This changes as the context descends into the module hierarchy. + crate dst: PathBuf, + /// A flag, which when `true`, will render pages which redirect to the + /// real location of an item. This is used to allow external links to + /// publicly reused items to redirect to the right location. + crate render_redirect_pages: bool, + /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set + /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of + /// the crate. + crate redirections: Option>>>, + /// The map used to ensure all generated 'id=' attributes are unique. + pub(super) id_map: Rc>, + /// Tracks section IDs for `Deref` targets so they match in both the main + /// body and the sidebar. + pub(super) deref_id_map: Rc>>, + crate shared: Arc>, + all: Rc>, + /// Storage for the errors produced while generating documentation so they + /// can be printed together at the end. + crate errors: Rc>, + crate cache: Rc, +} + +impl<'tcx> Context<'tcx> { + pub(super) fn path(&self, filename: &str) -> PathBuf { + // We use splitn vs Path::extension here because we might get a filename + // like `style.min.css` and we want to process that into + // `style-suffix.min.css`. Path::extension would just return `css` + // which would result in `style.min-suffix.css` which isn't what we + // want. + let (base, ext) = filename.split_once('.').unwrap(); + let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext); + self.dst.join(&filename) + } + + pub(super) fn tcx(&self) -> TyCtxt<'tcx> { + self.shared.tcx + } + + fn sess(&self) -> &'tcx Session { + &self.shared.tcx.sess + } + + pub(super) fn derive_id(&self, id: String) -> String { + let mut map = self.id_map.borrow_mut(); + map.derive(id) + } + + /// String representation of how to get back to the root path of the 'doc/' + /// folder in terms of a relative URL. + pub(super) fn root_path(&self) -> String { + "../".repeat(self.current.len()) + } + + fn render_item(&self, it: &clean::Item, pushname: bool) -> String { + // A little unfortunate that this is done like this, but it sure + // does make formatting *a lot* nicer. + CURRENT_DEPTH.with(|slot| { + slot.set(self.current.len()); + }); + + let mut title = if it.is_primitive() || it.is_keyword() { + // No need to include the namespace for primitive types and keywords + String::new() + } else { + self.current.join("::") + }; + if pushname { + if !title.is_empty() { + title.push_str("::"); + } + title.push_str(&it.name.unwrap().as_str()); + } + title.push_str(" - Rust"); + let tyname = it.type_(); + let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc)); + let desc = if let Some(desc) = desc { + desc + } else if it.is_crate() { + format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) + } else { + format!( + "API documentation for the Rust `{}` {} in crate `{}`.", + it.name.as_ref().unwrap(), + tyname, + self.shared.layout.krate + ) + }; + let keywords = make_item_keywords(it); + let page = layout::Page { + css_class: tyname.as_str(), + root_path: &self.root_path(), + static_root_path: self.shared.static_root_path.as_deref(), + title: &title, + description: &desc, + keywords: &keywords, + resource_suffix: &self.shared.resource_suffix, + extra_scripts: &[], + static_extra_scripts: &[], + }; + + { + self.id_map.borrow_mut().reset(); + self.id_map.borrow_mut().populate(&INITIAL_IDS); + } + + if !self.render_redirect_pages { + layout::render( + &self.shared.layout, + &page, + |buf: &mut _| print_sidebar(self, it, buf), + |buf: &mut _| print_item(self, it, buf), + &self.shared.style_files, + ) + } else { + if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) { + let mut path = String::new(); + for name in &names[..names.len() - 1] { + path.push_str(name); + path.push('/'); + } + path.push_str(&item_path(ty, names.last().unwrap())); + match self.redirections { + Some(ref redirections) => { + let mut current_path = String::new(); + for name in &self.current { + current_path.push_str(name); + current_path.push('/'); + } + current_path.push_str(&item_path(ty, names.last().unwrap())); + redirections.borrow_mut().insert(current_path, path); + } + None => return layout::redirect(&format!("{}{}", self.root_path(), path)), + } + } + String::new() + } + } + + /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. + fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap> { + // BTreeMap instead of HashMap to get a sorted output + let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); + for item in &m.items { + if item.is_stripped() { + continue; + } + + let short = item.type_(); + let myname = match item.name { + None => continue, + Some(ref s) => s.to_string(), + }; + let short = short.to_string(); + map.entry(short).or_default().push(( + myname, + Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))), + )); + } + + if self.shared.sort_modules_alphabetically { + for items in map.values_mut() { + items.sort(); + } + } + map + } + + /// Generates a url appropriate for an `href` attribute back to the source of + /// this item. + /// + /// The url generated, when clicked, will redirect the browser back to the + /// original source code. + /// + /// If `None` is returned, then a source link couldn't be generated. This + /// may happen, for example, with externally inlined items where the source + /// of their crate documentation isn't known. + pub(super) fn src_href(&self, item: &clean::Item) -> Option { + if item.source.is_dummy() { + return None; + } + let mut root = self.root_path(); + let mut path = String::new(); + let cnum = item.source.cnum(self.sess()); + + // We can safely ignore synthetic `SourceFile`s. + let file = match item.source.filename(self.sess()) { + FileName::Real(ref path) => path.local_path().to_path_buf(), + _ => return None, + }; + let file = &file; + + let symbol; + let (krate, path) = if cnum == LOCAL_CRATE { + if let Some(path) = self.shared.local_sources.get(file) { + (self.shared.layout.krate.as_str(), path) + } else { + return None; + } + } else { + let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? { + (name, ref src, ExternalLocation::Local) => (name, src), + (name, ref src, ExternalLocation::Remote(ref s)) => { + root = s.to_string(); + (name, src) + } + (_, _, ExternalLocation::Unknown) => return None, + }; + + sources::clean_path(&src_root, file, false, |component| { + path.push_str(&component.to_string_lossy()); + path.push('/'); + }); + let mut fname = file.file_name().expect("source has no filename").to_os_string(); + fname.push(".html"); + path.push_str(&fname.to_string_lossy()); + symbol = krate.as_str(); + (&*symbol, &path) + }; + + let loline = item.source.lo(self.sess()).line; + let hiline = item.source.hi(self.sess()).line; + let lines = + if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) }; + Some(format!( + "{root}src/{krate}/{path}#{lines}", + root = Escape(&root), + krate = krate, + path = path, + lines = lines + )) + } +} + +/// Generates the documentation for `crate` into the directory `dst` +impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { + fn descr() -> &'static str { + "html" + } + + fn init( + mut krate: clean::Crate, + options: RenderOptions, + edition: Edition, + mut cache: Cache, + tcx: TyCtxt<'tcx>, + ) -> Result<(Self, clean::Crate), Error> { + // need to save a copy of the options for rendering the index page + let md_opts = options.clone(); + let RenderOptions { + output, + external_html, + id_map, + playground_url, + sort_modules_alphabetically, + themes: style_files, + default_settings, + extension_css, + resource_suffix, + static_root_path, + generate_search_filter, + unstable_features, + generate_redirect_map, + .. + } = options; + + let src_root = match krate.src { + FileName::Real(ref p) => match p.local_path().parent() { + Some(p) => p.to_path_buf(), + None => PathBuf::new(), + }, + _ => PathBuf::new(), + }; + // If user passed in `--playground-url` arg, we fill in crate name here + let mut playground = None; + if let Some(url) = playground_url { + playground = + Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url }); + } + let mut layout = layout::Layout { + logo: String::new(), + favicon: String::new(), + external_html, + default_settings, + krate: krate.name.to_string(), + css_file_extension: extension_css, + generate_search_filter, + }; + let mut issue_tracker_base_url = None; + let mut include_sources = true; + + // Crawl the crate attributes looking for attributes which control how we're + // going to emit HTML + if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) { + for attr in attrs.lists(sym::doc) { + match (attr.name_or_empty(), attr.value_str()) { + (sym::html_favicon_url, Some(s)) => { + layout.favicon = s.to_string(); + } + (sym::html_logo_url, Some(s)) => { + layout.logo = s.to_string(); + } + (sym::html_playground_url, Some(s)) => { + playground = Some(markdown::Playground { + crate_name: Some(krate.name.to_string()), + url: s.to_string(), + }); + } + (sym::issue_tracker_base_url, Some(s)) => { + issue_tracker_base_url = Some(s.to_string()); + } + (sym::html_no_source, None) if attr.is_word() => { + include_sources = false; + } + _ => {} + } + } + } + let (sender, receiver) = channel(); + let mut scx = SharedContext { + tcx, + collapsed: krate.collapsed, + src_root, + include_sources, + local_sources: Default::default(), + issue_tracker_base_url, + layout, + created_dirs: Default::default(), + sort_modules_alphabetically, + style_files, + resource_suffix, + static_root_path, + fs: DocFS::new(sender), + edition, + codes: ErrorCodes::from(unstable_features.is_nightly_build()), + playground, + }; + + // Add the default themes to the `Vec` of stylepaths + // + // Note that these must be added before `sources::render` is called + // so that the resulting source pages are styled + // + // `light.css` is not disabled because it is the stylesheet that stays loaded + // by the browser as the theme stylesheet. The theme system (hackily) works by + // changing the href to this stylesheet. All other themes are disabled to + // prevent rule conflicts + scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false }); + scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true }); + scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true }); + + let dst = output; + scx.ensure_dir(&dst)?; + krate = sources::render(&dst, &mut scx, krate)?; + + // Build our search index + let index = build_index(&krate, &mut cache, tcx); + + let mut cx = Context { + current: Vec::new(), + dst, + render_redirect_pages: false, + id_map: Rc::new(RefCell::new(id_map)), + deref_id_map: Rc::new(RefCell::new(FxHashMap::default())), + shared: Arc::new(scx), + all: Rc::new(RefCell::new(AllTypes::new())), + errors: Rc::new(receiver), + cache: Rc::new(cache), + redirections: if generate_redirect_map { Some(Default::default()) } else { None }, + }; + + CURRENT_DEPTH.with(|s| s.set(0)); + + // Write shared runs within a flock; disable thread dispatching of IO temporarily. + Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); + write_shared(&cx, &krate, index, &md_opts)?; + Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); + Ok((cx, krate)) + } + + fn after_krate( + &mut self, + krate: &clean::Crate, + diag: &rustc_errors::Handler, + ) -> Result<(), Error> { + let final_file = self.dst.join(&*krate.name.as_str()).join("all.html"); + let settings_file = self.dst.join("settings.html"); + let crate_name = krate.name; + + let mut root_path = self.dst.to_str().expect("invalid path").to_owned(); + if !root_path.ends_with('/') { + root_path.push('/'); + } + let mut page = layout::Page { + title: "List of all items in this crate", + css_class: "mod", + root_path: "../", + static_root_path: self.shared.static_root_path.as_deref(), + description: "List of all items in this crate", + keywords: BASIC_KEYWORDS, + resource_suffix: &self.shared.resource_suffix, + extra_scripts: &[], + static_extra_scripts: &[], + }; + let sidebar = if let Some(ref version) = self.cache.crate_version { + format!( + "

Crate {}

\ +
\ +

Version {}

\ +
\ +

Back to index

", + crate_name, + Escape(version), + ) + } else { + String::new() + }; + let all = self.all.replace(AllTypes::new()); + let v = layout::render( + &self.shared.layout, + &page, + sidebar, + |buf: &mut Buffer| all.print(buf), + &self.shared.style_files, + ); + self.shared.fs.write(&final_file, v.as_bytes())?; + + // Generating settings page. + page.title = "Rustdoc settings"; + page.description = "Settings of Rustdoc"; + page.root_path = "./"; + + let mut style_files = self.shared.style_files.clone(); + let sidebar = "

Settings

"; + style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false }); + let v = layout::render( + &self.shared.layout, + &page, + sidebar, + settings( + self.shared.static_root_path.as_deref().unwrap_or("./"), + &self.shared.resource_suffix, + &self.shared.style_files, + )?, + &style_files, + ); + self.shared.fs.write(&settings_file, v.as_bytes())?; + if let Some(redirections) = self.redirections.take() { + if !redirections.borrow().is_empty() { + let redirect_map_path = + self.dst.join(&*krate.name.as_str()).join("redirect-map.json"); + let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); + self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?; + self.shared.fs.write(&redirect_map_path, paths.as_bytes())?; + } + } + + // Flush pending errors. + Arc::get_mut(&mut self.shared).unwrap().fs.close(); + let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count(); + if nb_errors > 0 { + Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) + } else { + Ok(()) + } + } + + fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> Result<(), Error> { + // Stripped modules survive the rustdoc passes (i.e., `strip-private`) + // if they contain impls for public types. These modules can also + // contain items such as publicly re-exported structures. + // + // External crates will provide links to these structures, so + // these modules are recursed into, but not rendered normally + // (a flag on the context). + if !self.render_redirect_pages { + self.render_redirect_pages = item.is_stripped(); + } + let scx = &self.shared; + self.dst.push(item_name); + self.current.push(item_name.to_owned()); + + info!("Recursing into {}", self.dst.display()); + + let buf = self.render_item(item, false); + // buf will be empty if the module is stripped and there is no redirect for it + if !buf.is_empty() { + self.shared.ensure_dir(&self.dst)?; + let joint_dst = self.dst.join("index.html"); + scx.fs.write(&joint_dst, buf.as_bytes())?; + } + + // Render sidebar-items.js used throughout this module. + if !self.render_redirect_pages { + let module = match *item.kind { + clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m, + _ => unreachable!(), + }; + let items = self.build_sidebar_items(module); + let js_dst = self.dst.join("sidebar-items.js"); + let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap()); + scx.fs.write(&js_dst, &v)?; + } + Ok(()) + } + + fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> { + info!("Recursed; leaving {}", self.dst.display()); + + // Go back to where we were at + self.dst.pop(); + self.current.pop(); + Ok(()) + } + + fn item(&mut self, item: clean::Item) -> Result<(), Error> { + // Stripped modules survive the rustdoc passes (i.e., `strip-private`) + // if they contain impls for public types. These modules can also + // contain items such as publicly re-exported structures. + // + // External crates will provide links to these structures, so + // these modules are recursed into, but not rendered normally + // (a flag on the context). + if !self.render_redirect_pages { + self.render_redirect_pages = item.is_stripped(); + } + + let buf = self.render_item(&item, true); + // buf will be empty if the item is stripped and there is no redirect for it + if !buf.is_empty() { + let name = item.name.as_ref().unwrap(); + let item_type = item.type_(); + let file_name = &item_path(item_type, &name.as_str()); + self.shared.ensure_dir(&self.dst)?; + let joint_dst = self.dst.join(file_name); + self.shared.fs.write(&joint_dst, buf.as_bytes())?; + + if !self.render_redirect_pages { + self.all.borrow_mut().append(full_path(self, &item), &item_type); + } + // If the item is a macro, redirect from the old macro URL (with !) + // to the new one (without). + if item_type == ItemType::Macro { + let redir_name = format!("{}.{}!.html", item_type, name); + if let Some(ref redirections) = self.redirections { + let crate_name = &self.shared.layout.krate; + redirections.borrow_mut().insert( + format!("{}/{}", crate_name, redir_name), + format!("{}/{}", crate_name, file_name), + ); + } else { + let v = layout::redirect(file_name); + let redir_dst = self.dst.join(redir_name); + self.shared.fs.write(&redir_dst, v.as_bytes())?; + } + } + } + Ok(()) + } + + fn cache(&self) -> &Cache { + &self.cache + } +} + +fn make_item_keywords(it: &clean::Item) -> String { + format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap()) +} diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index fbeeb2a6fa2f2..50cae50c2c3eb 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1,5 +1,3 @@ -// ignore-tidy-filelength - //! Rustdoc's HTML rendering module. //! //! This modules contains the bulk of the logic necessary for rendering a @@ -30,58 +28,48 @@ crate mod cache; #[cfg(test)] mod tests; +mod context; +mod print_item; +mod write_shared; + +crate use context::*; + use std::cell::{Cell, RefCell}; -use std::cmp::Ordering; -use std::collections::{BTreeMap, VecDeque}; +use std::collections::VecDeque; use std::default::Default; -use std::ffi::OsStr; -use std::fmt::{self, Write}; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::io::{self, BufReader}; -use std::path::{Component, Path, PathBuf}; -use std::rc::Rc; +use std::fmt; +use std::path::{Path, PathBuf}; use std::str; use std::string::ToString; -use std::sync::mpsc::{channel, Receiver}; -use std::sync::Arc; use itertools::Itertools; use rustc_ast_pretty::pprust; use rustc_attr::{Deprecation, StabilityLevel}; -use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::def_id::DefId; use rustc_hir::Mutability; use rustc_middle::middle::stability; use rustc_middle::ty::TyCtxt; -use rustc_session::Session; use rustc_span::edition::Edition; -use rustc_span::hygiene::MacroKind; -use rustc_span::source_map::FileName; use rustc_span::symbol::{kw, sym, Symbol}; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; -use crate::clean::{self, AttributesExt, GetDefId, RenderedLink, SelfTy, TypeKind}; -use crate::config::RenderOptions; +use crate::clean::{self, GetDefId, RenderedLink, SelfTy, TypeKind}; use crate::docfs::{DocFS, PathError}; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode}; use crate::html::escape::Escape; -use crate::html::format::Function; -use crate::html::format::{href, print_default_space, print_generic_bounds, WhereClause}; -use crate::html::format::{print_abi_with_space, Buffer, PrintWithSpace}; -use crate::html::markdown::{ - self, plain_text_summary, ErrorCodes, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine, +use crate::html::format::{ + href, print_abi_with_space, print_default_space, print_generic_bounds, Buffer, Function, + PrintWithSpace, WhereClause, }; -use crate::html::sources; -use crate::html::{highlight, layout, static_files}; -use cache::{build_index, ExternalLocation}; +use crate::html::layout; +use crate::html::markdown::{self, ErrorCodes, Markdown, MarkdownHtml, MarkdownSummaryLine}; /// A pair of name and its optional document. crate type NameDoc = (String, Option); @@ -92,42 +80,6 @@ crate fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { }) } -/// Major driving force in all rustdoc rendering. This contains information -/// about where in the tree-like hierarchy rendering is occurring and controls -/// how the current page is being rendered. -/// -/// It is intended that this context is a lightweight object which can be fairly -/// easily cloned because it is cloned per work-job (about once per item in the -/// rustdoc tree). -#[derive(Clone)] -crate struct Context<'tcx> { - /// Current hierarchy of components leading down to what's currently being - /// rendered - crate current: Vec, - /// The current destination folder of where HTML artifacts should be placed. - /// This changes as the context descends into the module hierarchy. - crate dst: PathBuf, - /// A flag, which when `true`, will render pages which redirect to the - /// real location of an item. This is used to allow external links to - /// publicly reused items to redirect to the right location. - crate render_redirect_pages: bool, - /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set - /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of - /// the crate. - crate redirections: Option>>>, - /// The map used to ensure all generated 'id=' attributes are unique. - id_map: Rc>, - /// Tracks section IDs for `Deref` targets so they match in both the main - /// body and the sidebar. - deref_id_map: Rc>>, - crate shared: Arc>, - all: Rc>, - /// Storage for the errors produced while generating documentation so they - /// can be printed together at the end. - crate errors: Rc>, - crate cache: Rc, -} - crate struct SharedContext<'tcx> { crate tcx: TyCtxt<'tcx>, /// The path to the crate root source minus the file name. @@ -169,27 +121,6 @@ crate struct SharedContext<'tcx> { playground: Option, } -impl<'tcx> Context<'tcx> { - fn path(&self, filename: &str) -> PathBuf { - // We use splitn vs Path::extension here because we might get a filename - // like `style.min.css` and we want to process that into - // `style-suffix.min.css`. Path::extension would just return `css` - // which would result in `style.min-suffix.css` which isn't what we - // want. - let (base, ext) = filename.split_once('.').unwrap(); - let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext); - self.dst.join(&filename) - } - - fn tcx(&self) -> TyCtxt<'tcx> { - self.shared.tcx - } - - fn sess(&self) -> &'tcx Session { - &self.shared.tcx.sess - } -} - impl SharedContext<'_> { crate fn ensure_dir(&self, dst: &Path) -> Result<(), Error> { let mut dirs = self.created_dirs.borrow_mut(); @@ -380,860 +311,6 @@ crate const INITIAL_IDS: [&'static str; 15] = [ "implementations", ]; -/// Generates the documentation for `crate` into the directory `dst` -impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { - fn descr() -> &'static str { - "html" - } - - fn init( - mut krate: clean::Crate, - options: RenderOptions, - edition: Edition, - mut cache: Cache, - tcx: TyCtxt<'tcx>, - ) -> Result<(Self, clean::Crate), Error> { - // need to save a copy of the options for rendering the index page - let md_opts = options.clone(); - let RenderOptions { - output, - external_html, - id_map, - playground_url, - sort_modules_alphabetically, - themes: style_files, - default_settings, - extension_css, - resource_suffix, - static_root_path, - generate_search_filter, - unstable_features, - generate_redirect_map, - .. - } = options; - - let src_root = match krate.src { - FileName::Real(ref p) => match p.local_path().parent() { - Some(p) => p.to_path_buf(), - None => PathBuf::new(), - }, - _ => PathBuf::new(), - }; - // If user passed in `--playground-url` arg, we fill in crate name here - let mut playground = None; - if let Some(url) = playground_url { - playground = - Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url }); - } - let mut layout = layout::Layout { - logo: String::new(), - favicon: String::new(), - external_html, - default_settings, - krate: krate.name.to_string(), - css_file_extension: extension_css, - generate_search_filter, - }; - let mut issue_tracker_base_url = None; - let mut include_sources = true; - - // Crawl the crate attributes looking for attributes which control how we're - // going to emit HTML - if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) { - for attr in attrs.lists(sym::doc) { - match (attr.name_or_empty(), attr.value_str()) { - (sym::html_favicon_url, Some(s)) => { - layout.favicon = s.to_string(); - } - (sym::html_logo_url, Some(s)) => { - layout.logo = s.to_string(); - } - (sym::html_playground_url, Some(s)) => { - playground = Some(markdown::Playground { - crate_name: Some(krate.name.to_string()), - url: s.to_string(), - }); - } - (sym::issue_tracker_base_url, Some(s)) => { - issue_tracker_base_url = Some(s.to_string()); - } - (sym::html_no_source, None) if attr.is_word() => { - include_sources = false; - } - _ => {} - } - } - } - let (sender, receiver) = channel(); - let mut scx = SharedContext { - tcx, - collapsed: krate.collapsed, - src_root, - include_sources, - local_sources: Default::default(), - issue_tracker_base_url, - layout, - created_dirs: Default::default(), - sort_modules_alphabetically, - style_files, - resource_suffix, - static_root_path, - fs: DocFS::new(sender), - edition, - codes: ErrorCodes::from(unstable_features.is_nightly_build()), - playground, - }; - - // Add the default themes to the `Vec` of stylepaths - // - // Note that these must be added before `sources::render` is called - // so that the resulting source pages are styled - // - // `light.css` is not disabled because it is the stylesheet that stays loaded - // by the browser as the theme stylesheet. The theme system (hackily) works by - // changing the href to this stylesheet. All other themes are disabled to - // prevent rule conflicts - scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false }); - scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true }); - scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true }); - - let dst = output; - scx.ensure_dir(&dst)?; - krate = sources::render(&dst, &mut scx, krate)?; - - // Build our search index - let index = build_index(&krate, &mut cache, tcx); - - let mut cx = Context { - current: Vec::new(), - dst, - render_redirect_pages: false, - id_map: Rc::new(RefCell::new(id_map)), - deref_id_map: Rc::new(RefCell::new(FxHashMap::default())), - shared: Arc::new(scx), - all: Rc::new(RefCell::new(AllTypes::new())), - errors: Rc::new(receiver), - cache: Rc::new(cache), - redirections: if generate_redirect_map { Some(Default::default()) } else { None }, - }; - - CURRENT_DEPTH.with(|s| s.set(0)); - - // Write shared runs within a flock; disable thread dispatching of IO temporarily. - Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); - write_shared(&cx, &krate, index, &md_opts)?; - Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); - Ok((cx, krate)) - } - - fn after_krate( - &mut self, - krate: &clean::Crate, - diag: &rustc_errors::Handler, - ) -> Result<(), Error> { - let final_file = self.dst.join(&*krate.name.as_str()).join("all.html"); - let settings_file = self.dst.join("settings.html"); - let crate_name = krate.name; - - let mut root_path = self.dst.to_str().expect("invalid path").to_owned(); - if !root_path.ends_with('/') { - root_path.push('/'); - } - let mut page = layout::Page { - title: "List of all items in this crate", - css_class: "mod", - root_path: "../", - static_root_path: self.shared.static_root_path.as_deref(), - description: "List of all items in this crate", - keywords: BASIC_KEYWORDS, - resource_suffix: &self.shared.resource_suffix, - extra_scripts: &[], - static_extra_scripts: &[], - }; - let sidebar = if let Some(ref version) = self.cache.crate_version { - format!( - "

Crate {}

\ -
\ -

Version {}

\ -
\ -

Back to index

", - crate_name, - Escape(version), - ) - } else { - String::new() - }; - let all = self.all.replace(AllTypes::new()); - let v = layout::render( - &self.shared.layout, - &page, - sidebar, - |buf: &mut Buffer| all.print(buf), - &self.shared.style_files, - ); - self.shared.fs.write(&final_file, v.as_bytes())?; - - // Generating settings page. - page.title = "Rustdoc settings"; - page.description = "Settings of Rustdoc"; - page.root_path = "./"; - - let mut style_files = self.shared.style_files.clone(); - let sidebar = "

Settings

"; - style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false }); - let v = layout::render( - &self.shared.layout, - &page, - sidebar, - settings( - self.shared.static_root_path.as_deref().unwrap_or("./"), - &self.shared.resource_suffix, - &self.shared.style_files, - )?, - &style_files, - ); - self.shared.fs.write(&settings_file, v.as_bytes())?; - if let Some(redirections) = self.redirections.take() { - if !redirections.borrow().is_empty() { - let redirect_map_path = - self.dst.join(&*krate.name.as_str()).join("redirect-map.json"); - let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); - self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?; - self.shared.fs.write(&redirect_map_path, paths.as_bytes())?; - } - } - - // Flush pending errors. - Arc::get_mut(&mut self.shared).unwrap().fs.close(); - let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count(); - if nb_errors > 0 { - Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) - } else { - Ok(()) - } - } - - fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> Result<(), Error> { - // Stripped modules survive the rustdoc passes (i.e., `strip-private`) - // if they contain impls for public types. These modules can also - // contain items such as publicly re-exported structures. - // - // External crates will provide links to these structures, so - // these modules are recursed into, but not rendered normally - // (a flag on the context). - if !self.render_redirect_pages { - self.render_redirect_pages = item.is_stripped(); - } - let scx = &self.shared; - self.dst.push(item_name); - self.current.push(item_name.to_owned()); - - info!("Recursing into {}", self.dst.display()); - - let buf = self.render_item(item, false); - // buf will be empty if the module is stripped and there is no redirect for it - if !buf.is_empty() { - self.shared.ensure_dir(&self.dst)?; - let joint_dst = self.dst.join("index.html"); - scx.fs.write(&joint_dst, buf.as_bytes())?; - } - - // Render sidebar-items.js used throughout this module. - if !self.render_redirect_pages { - let module = match *item.kind { - clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m, - _ => unreachable!(), - }; - let items = self.build_sidebar_items(module); - let js_dst = self.dst.join("sidebar-items.js"); - let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap()); - scx.fs.write(&js_dst, &v)?; - } - Ok(()) - } - - fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> { - info!("Recursed; leaving {}", self.dst.display()); - - // Go back to where we were at - self.dst.pop(); - self.current.pop(); - Ok(()) - } - - fn item(&mut self, item: clean::Item) -> Result<(), Error> { - // Stripped modules survive the rustdoc passes (i.e., `strip-private`) - // if they contain impls for public types. These modules can also - // contain items such as publicly re-exported structures. - // - // External crates will provide links to these structures, so - // these modules are recursed into, but not rendered normally - // (a flag on the context). - if !self.render_redirect_pages { - self.render_redirect_pages = item.is_stripped(); - } - - let buf = self.render_item(&item, true); - // buf will be empty if the item is stripped and there is no redirect for it - if !buf.is_empty() { - let name = item.name.as_ref().unwrap(); - let item_type = item.type_(); - let file_name = &item_path(item_type, &name.as_str()); - self.shared.ensure_dir(&self.dst)?; - let joint_dst = self.dst.join(file_name); - self.shared.fs.write(&joint_dst, buf.as_bytes())?; - - if !self.render_redirect_pages { - self.all.borrow_mut().append(full_path(self, &item), &item_type); - } - // If the item is a macro, redirect from the old macro URL (with !) - // to the new one (without). - if item_type == ItemType::Macro { - let redir_name = format!("{}.{}!.html", item_type, name); - if let Some(ref redirections) = self.redirections { - let crate_name = &self.shared.layout.krate; - redirections.borrow_mut().insert( - format!("{}/{}", crate_name, redir_name), - format!("{}/{}", crate_name, file_name), - ); - } else { - let v = layout::redirect(file_name); - let redir_dst = self.dst.join(redir_name); - self.shared.fs.write(&redir_dst, v.as_bytes())?; - } - } - } - Ok(()) - } - - fn cache(&self) -> &Cache { - &self.cache - } -} - -fn write_shared( - cx: &Context<'_>, - krate: &clean::Crate, - search_index: String, - options: &RenderOptions, -) -> Result<(), Error> { - // Write out the shared files. Note that these are shared among all rustdoc - // docs placed in the output directory, so this needs to be a synchronized - // operation with respect to all other rustdocs running around. - let lock_file = cx.dst.join(".lock"); - let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file); - - // Add all the static files. These may already exist, but we just - // overwrite them anyway to make sure that they're fresh and up-to-date. - - write_minify( - &cx.shared.fs, - cx.path("rustdoc.css"), - static_files::RUSTDOC_CSS, - options.enable_minification, - )?; - write_minify( - &cx.shared.fs, - cx.path("settings.css"), - static_files::SETTINGS_CSS, - options.enable_minification, - )?; - write_minify( - &cx.shared.fs, - cx.path("noscript.css"), - static_files::NOSCRIPT_CSS, - options.enable_minification, - )?; - - // To avoid "light.css" to be overwritten, we'll first run over the received themes and only - // then we'll run over the "official" styles. - let mut themes: FxHashSet = FxHashSet::default(); - - for entry in &cx.shared.style_files { - let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path); - let extension = - try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); - - // Handle the official themes - match theme { - "light" => write_minify( - &cx.shared.fs, - cx.path("light.css"), - static_files::themes::LIGHT, - options.enable_minification, - )?, - "dark" => write_minify( - &cx.shared.fs, - cx.path("dark.css"), - static_files::themes::DARK, - options.enable_minification, - )?, - "ayu" => write_minify( - &cx.shared.fs, - cx.path("ayu.css"), - static_files::themes::AYU, - options.enable_minification, - )?, - _ => { - // Handle added third-party themes - let content = try_err!(fs::read(&entry.path), &entry.path); - cx.shared - .fs - .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?; - } - }; - - themes.insert(theme.to_owned()); - } - - let write = |p, c| cx.shared.fs.write(p, c); - if (*cx.shared).layout.logo.is_empty() { - write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?; - } - if (*cx.shared).layout.favicon.is_empty() { - write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?; - write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?; - write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?; - } - write(cx.path("brush.svg"), static_files::BRUSH_SVG)?; - write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?; - write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?; - - let mut themes: Vec<&String> = themes.iter().collect(); - themes.sort(); - // To avoid theme switch latencies as much as possible, we put everything theme related - // at the beginning of the html files into another js file. - let theme_js = format!( - r#"var themes = document.getElementById("theme-choices"); -var themePicker = document.getElementById("theme-picker"); - -function showThemeButtonState() {{ - themes.style.display = "block"; - themePicker.style.borderBottomRightRadius = "0"; - themePicker.style.borderBottomLeftRadius = "0"; -}} - -function hideThemeButtonState() {{ - themes.style.display = "none"; - themePicker.style.borderBottomRightRadius = "3px"; - themePicker.style.borderBottomLeftRadius = "3px"; -}} - -function switchThemeButtonState() {{ - if (themes.style.display === "block") {{ - hideThemeButtonState(); - }} else {{ - showThemeButtonState(); - }} -}}; - -function handleThemeButtonsBlur(e) {{ - var active = document.activeElement; - var related = e.relatedTarget; - - if (active.id !== "theme-picker" && - (!active.parentNode || active.parentNode.id !== "theme-choices") && - (!related || - (related.id !== "theme-picker" && - (!related.parentNode || related.parentNode.id !== "theme-choices")))) {{ - hideThemeButtonState(); - }} -}} - -themePicker.onclick = switchThemeButtonState; -themePicker.onblur = handleThemeButtonsBlur; -{}.forEach(function(item) {{ - var but = document.createElement("button"); - but.textContent = item; - but.onclick = function(el) {{ - switchTheme(currentTheme, mainTheme, item, true); - useSystemTheme(false); - }}; - but.onblur = handleThemeButtonsBlur; - themes.appendChild(but); -}});"#, - serde_json::to_string(&themes).unwrap() - ); - - write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?; - write_minify( - &cx.shared.fs, - cx.path("main.js"), - static_files::MAIN_JS, - options.enable_minification, - )?; - write_minify( - &cx.shared.fs, - cx.path("settings.js"), - static_files::SETTINGS_JS, - options.enable_minification, - )?; - if cx.shared.include_sources { - write_minify( - &cx.shared.fs, - cx.path("source-script.js"), - static_files::sidebar::SOURCE_SCRIPT, - options.enable_minification, - )?; - } - - { - write_minify( - &cx.shared.fs, - cx.path("storage.js"), - &format!( - "var resourcesSuffix = \"{}\";{}", - cx.shared.resource_suffix, - static_files::STORAGE_JS - ), - options.enable_minification, - )?; - } - - if let Some(ref css) = cx.shared.layout.css_file_extension { - let out = cx.path("theme.css"); - let buffer = try_err!(fs::read_to_string(css), css); - if !options.enable_minification { - cx.shared.fs.write(&out, &buffer)?; - } else { - write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?; - } - } - write_minify( - &cx.shared.fs, - cx.path("normalize.css"), - static_files::NORMALIZE_CSS, - options.enable_minification, - )?; - write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?; - write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?; - write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?; - write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?; - write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?; - write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?; - write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?; - write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?; - write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?; - write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?; - write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?; - write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?; - write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?; - write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?; - write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?; - - fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec, Vec)> { - let mut ret = Vec::new(); - let mut krates = Vec::new(); - - if path.exists() { - let prefix = format!(r#"{}["{}"]"#, key, krate); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with(key) { - continue; - } - if line.starts_with(&prefix) { - continue; - } - ret.push(line.to_string()); - krates.push( - line[key.len() + 2..] - .split('"') - .next() - .map(|s| s.to_owned()) - .unwrap_or_else(String::new), - ); - } - } - Ok((ret, krates)) - } - - fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> { - let mut ret = Vec::new(); - let mut krates = Vec::new(); - - if path.exists() { - let prefix = format!("\"{}\"", krate); - for line in BufReader::new(File::open(path)?).lines() { - let line = line?; - if !line.starts_with('"') { - continue; - } - if line.starts_with(&prefix) { - continue; - } - if line.ends_with(",\\") { - ret.push(line[..line.len() - 2].to_string()); - } else { - // Ends with "\\" (it's the case for the last added crate line) - ret.push(line[..line.len() - 1].to_string()); - } - krates.push( - line.split('"') - .find(|s| !s.is_empty()) - .map(|s| s.to_owned()) - .unwrap_or_else(String::new), - ); - } - } - Ok((ret, krates)) - } - - use std::ffi::OsString; - - #[derive(Debug)] - struct Hierarchy { - elem: OsString, - children: FxHashMap, - elems: FxHashSet, - } - - impl Hierarchy { - fn new(elem: OsString) -> Hierarchy { - Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() } - } - - fn to_json_string(&self) -> String { - let mut subs: Vec<&Hierarchy> = self.children.values().collect(); - subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem)); - let mut files = self - .elems - .iter() - .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion"))) - .collect::>(); - files.sort_unstable(); - let subs = subs.iter().map(|s| s.to_json_string()).collect::>().join(","); - let dirs = - if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) }; - let files = files.join(","); - let files = - if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) }; - format!( - "{{\"name\":\"{name}\"{dirs}{files}}}", - name = self.elem.to_str().expect("invalid osstring conversion"), - dirs = dirs, - files = files - ) - } - } - - if cx.shared.include_sources { - let mut hierarchy = Hierarchy::new(OsString::new()); - for source in cx - .shared - .local_sources - .iter() - .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok()) - { - let mut h = &mut hierarchy; - let mut elems = source - .components() - .filter_map(|s| match s { - Component::Normal(s) => Some(s.to_owned()), - _ => None, - }) - .peekable(); - loop { - let cur_elem = elems.next().expect("empty file path"); - if elems.peek().is_none() { - h.elems.insert(cur_elem); - break; - } else { - let e = cur_elem.clone(); - h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e)); - } - } - } - - let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix)); - let (mut all_sources, _krates) = - try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst); - all_sources.push(format!( - "sourcesIndex[\"{}\"] = {};", - &krate.name, - hierarchy.to_json_string() - )); - all_sources.sort(); - let v = format!( - "var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n", - all_sources.join("\n") - ); - cx.shared.fs.write(&dst, v.as_bytes())?; - } - - // Update the search index and crate list. - let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix)); - let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst); - all_indexes.push(search_index); - krates.push(krate.name.to_string()); - krates.sort(); - - // Sort the indexes by crate so the file will be generated identically even - // with rustdoc running in parallel. - all_indexes.sort(); - { - let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); - v.push_str(&all_indexes.join(",\\\n")); - v.push_str("\\\n}');\ninitSearch(searchIndex);"); - cx.shared.fs.write(&dst, &v)?; - } - - let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix)); - let crate_list = - format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(",")); - cx.shared.fs.write(&crate_list_dst, &crate_list)?; - - if options.enable_index_page { - if let Some(index_page) = options.index_page.clone() { - let mut md_opts = options.clone(); - md_opts.output = cx.dst.clone(); - md_opts.external_html = (*cx.shared).layout.external_html.clone(); - - crate::markdown::render(&index_page, md_opts, cx.shared.edition) - .map_err(|e| Error::new(e, &index_page))?; - } else { - let dst = cx.dst.join("index.html"); - let page = layout::Page { - title: "Index of crates", - css_class: "mod", - root_path: "./", - static_root_path: cx.shared.static_root_path.as_deref(), - description: "List of crates", - keywords: BASIC_KEYWORDS, - resource_suffix: &cx.shared.resource_suffix, - extra_scripts: &[], - static_extra_scripts: &[], - }; - - let content = format!( - "

\ - List of all crates\ -

    {}
", - krates - .iter() - .map(|s| { - format!( - "
  • {}
  • ", - ensure_trailing_slash(s), - s - ) - }) - .collect::() - ); - let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files); - cx.shared.fs.write(&dst, v.as_bytes())?; - } - } - - // Update the list of all implementors for traits - let dst = cx.dst.join("implementors"); - for (&did, imps) in &cx.cache.implementors { - // Private modules can leak through to this phase of rustdoc, which - // could contain implementations for otherwise private types. In some - // rare cases we could find an implementation for an item which wasn't - // indexed, so we just skip this step in that case. - // - // FIXME: this is a vague explanation for why this can't be a `get`, in - // theory it should be... - let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) { - Some(p) => p, - None => match cx.cache.external_paths.get(&did) { - Some(p) => p, - None => continue, - }, - }; - - #[derive(Serialize)] - struct Implementor { - text: String, - synthetic: bool, - types: Vec, - } - - let implementors = imps - .iter() - .filter_map(|imp| { - // If the trait and implementation are in the same crate, then - // there's no need to emit information about it (there's inlining - // going on). If they're in different crates then the crate defining - // the trait will be interested in our implementation. - // - // If the implementation is from another crate then that crate - // should add it. - if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() { - None - } else { - Some(Implementor { - text: imp.inner_impl().print(cx.cache(), false).to_string(), - synthetic: imp.inner_impl().synthetic, - types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()), - }) - } - }) - .collect::>(); - - // Only create a js file if we have impls to add to it. If the trait is - // documented locally though we always create the file to avoid dead - // links. - if implementors.is_empty() && !cx.cache.paths.contains_key(&did) { - continue; - } - - let implementors = format!( - r#"implementors["{}"] = {};"#, - krate.name, - serde_json::to_string(&implementors).unwrap() - ); - - let mut mydst = dst.clone(); - for part in &remote_path[..remote_path.len() - 1] { - mydst.push(part); - } - cx.shared.ensure_dir(&mydst)?; - mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1])); - - let (mut all_implementors, _) = - try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst); - all_implementors.push(implementors); - // Sort the implementors by crate so the file will be generated - // identically even with rustdoc running in parallel. - all_implementors.sort(); - - let mut v = String::from("(function() {var implementors = {};\n"); - for implementor in &all_implementors { - writeln!(v, "{}", *implementor).unwrap(); - } - v.push_str( - "if (window.register_implementors) {\ - window.register_implementors(implementors);\ - } else {\ - window.pending_implementors = implementors;\ - }", - ); - v.push_str("})()"); - cx.shared.fs.write(&mydst, &v)?; - } - Ok(()) -} - -fn write_minify( - fs: &DocFS, - dst: PathBuf, - contents: &str, - enable_minification: bool, -) -> Result<(), Error> { - if enable_minification { - if dst.extension() == Some(&OsStr::new("css")) { - let res = try_none!(minifier::css::minify(contents).ok(), &dst); - fs.write(dst, res.as_bytes()) - } else { - fs.write(dst, minifier::js::minify(contents).as_bytes()) - } - } else { - fs.write(dst, contents.as_bytes()) - } -} - fn write_srclink(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { if let Some(l) = cx.src_href(item) { write!(buf, "[src]", l) @@ -1541,390 +618,68 @@ fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result { - fn derive_id(&self, id: String) -> String { - let mut map = self.id_map.borrow_mut(); - map.derive(id) - } - - /// String representation of how to get back to the root path of the 'doc/' - /// folder in terms of a relative URL. - fn root_path(&self) -> String { - "../".repeat(self.current.len()) +fn document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) { + if let Some(ref name) = item.name { + info!("Documenting {}", name); } + document_item_info(w, cx, item, false, parent); + document_full(w, item, cx, "", false); +} - fn render_item(&self, it: &clean::Item, pushname: bool) -> String { - // A little unfortunate that this is done like this, but it sure - // does make formatting *a lot* nicer. - CURRENT_DEPTH.with(|slot| { - slot.set(self.current.len()); - }); +/// Render md_text as markdown. +fn render_markdown( + w: &mut Buffer, + cx: &Context<'_>, + md_text: &str, + links: Vec, + prefix: &str, + is_hidden: bool, +) { + let mut ids = cx.id_map.borrow_mut(); + write!( + w, + "
    {}{}
    ", + if is_hidden { " hidden" } else { "" }, + prefix, + Markdown( + md_text, + &links, + &mut ids, + cx.shared.codes, + cx.shared.edition, + &cx.shared.playground + ) + .into_string() + ) +} - let mut title = if it.is_primitive() || it.is_keyword() { - // No need to include the namespace for primitive types and keywords - String::new() - } else { - self.current.join("::") - }; - if pushname { - if !title.is_empty() { - title.push_str("::"); - } - title.push_str(&it.name.unwrap().as_str()); - } - title.push_str(" - Rust"); - let tyname = it.type_(); - let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc)); - let desc = if let Some(desc) = desc { - desc - } else if it.is_crate() { - format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) - } else { - format!( - "API documentation for the Rust `{}` {} in crate `{}`.", - it.name.as_ref().unwrap(), - tyname, - self.shared.layout.krate - ) - }; - let keywords = make_item_keywords(it); - let page = layout::Page { - css_class: tyname.as_str(), - root_path: &self.root_path(), - static_root_path: self.shared.static_root_path.as_deref(), - title: &title, - description: &desc, - keywords: &keywords, - resource_suffix: &self.shared.resource_suffix, - extra_scripts: &[], - static_extra_scripts: &[], - }; +/// Writes a documentation block containing only the first paragraph of the documentation. If the +/// docs are longer, a "Read more" link is appended to the end. +fn document_short( + w: &mut Buffer, + item: &clean::Item, + cx: &Context<'_>, + link: AssocItemLink<'_>, + prefix: &str, + is_hidden: bool, + parent: Option<&clean::Item>, + show_def_docs: bool, +) { + document_item_info(w, cx, item, is_hidden, parent); + if !show_def_docs { + return; + } + if let Some(s) = item.doc_value() { + let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string(); - { - self.id_map.borrow_mut().reset(); - self.id_map.borrow_mut().populate(&INITIAL_IDS); - } + if s.contains('\n') { + let link = + format!(r#" Read more"#, naive_assoc_href(item, link, cx.cache())); - if !self.render_redirect_pages { - layout::render( - &self.shared.layout, - &page, - |buf: &mut _| print_sidebar(self, it, buf), - |buf: &mut _| print_item(self, it, buf), - &self.shared.style_files, - ) - } else { - if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) { - let mut path = String::new(); - for name in &names[..names.len() - 1] { - path.push_str(name); - path.push('/'); - } - path.push_str(&item_path(ty, names.last().unwrap())); - match self.redirections { - Some(ref redirections) => { - let mut current_path = String::new(); - for name in &self.current { - current_path.push_str(name); - current_path.push('/'); - } - current_path.push_str(&item_path(ty, names.last().unwrap())); - redirections.borrow_mut().insert(current_path, path); - } - None => return layout::redirect(&format!("{}{}", self.root_path(), path)), - } - } - String::new() - } - } - - /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. - fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap> { - // BTreeMap instead of HashMap to get a sorted output - let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); - for item in &m.items { - if item.is_stripped() { - continue; - } - - let short = item.type_(); - let myname = match item.name { - None => continue, - Some(ref s) => s.to_string(), - }; - let short = short.to_string(); - map.entry(short).or_default().push(( - myname, - Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))), - )); - } - - if self.shared.sort_modules_alphabetically { - for items in map.values_mut() { - items.sort(); - } - } - map - } - - /// Generates a url appropriate for an `href` attribute back to the source of - /// this item. - /// - /// The url generated, when clicked, will redirect the browser back to the - /// original source code. - /// - /// If `None` is returned, then a source link couldn't be generated. This - /// may happen, for example, with externally inlined items where the source - /// of their crate documentation isn't known. - fn src_href(&self, item: &clean::Item) -> Option { - if item.source.is_dummy() { - return None; - } - let mut root = self.root_path(); - let mut path = String::new(); - let cnum = item.source.cnum(self.sess()); - - // We can safely ignore synthetic `SourceFile`s. - let file = match item.source.filename(self.sess()) { - FileName::Real(ref path) => path.local_path().to_path_buf(), - _ => return None, - }; - let file = &file; - - let symbol; - let (krate, path) = if cnum == LOCAL_CRATE { - if let Some(path) = self.shared.local_sources.get(file) { - (self.shared.layout.krate.as_str(), path) - } else { - return None; - } - } else { - let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? { - (name, ref src, ExternalLocation::Local) => (name, src), - (name, ref src, ExternalLocation::Remote(ref s)) => { - root = s.to_string(); - (name, src) - } - (_, _, ExternalLocation::Unknown) => return None, - }; - - sources::clean_path(&src_root, file, false, |component| { - path.push_str(&component.to_string_lossy()); - path.push('/'); - }); - let mut fname = file.file_name().expect("source has no filename").to_os_string(); - fname.push(".html"); - path.push_str(&fname.to_string_lossy()); - symbol = krate.as_str(); - (&*symbol, &path) - }; - - let loline = item.source.lo(self.sess()).line; - let hiline = item.source.hi(self.sess()).line; - let lines = - if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) }; - Some(format!( - "{root}src/{krate}/{path}#{lines}", - root = Escape(&root), - krate = krate, - path = path, - lines = lines - )) - } -} - -fn wrap_into_docblock(w: &mut Buffer, f: F) -where - F: FnOnce(&mut Buffer), -{ - w.write_str("
    "); - f(w); - w.write_str("
    ") -} - -fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { - debug_assert!(!item.is_stripped()); - // Write the breadcrumb trail header for the top - buf.write_str("

    "); - let name = match *item.kind { - clean::ModuleItem(ref m) => { - if m.is_crate { - "Crate " - } else { - "Module " - } - } - clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ", - clean::TraitItem(..) => "Trait ", - clean::StructItem(..) => "Struct ", - clean::UnionItem(..) => "Union ", - clean::EnumItem(..) => "Enum ", - clean::TypedefItem(..) => "Type Definition ", - clean::MacroItem(..) => "Macro ", - clean::ProcMacroItem(ref mac) => match mac.kind { - MacroKind::Bang => "Macro ", - MacroKind::Attr => "Attribute Macro ", - MacroKind::Derive => "Derive Macro ", - }, - clean::PrimitiveItem(..) => "Primitive Type ", - clean::StaticItem(..) | clean::ForeignStaticItem(..) => "Static ", - clean::ConstantItem(..) => "Constant ", - clean::ForeignTypeItem => "Foreign Type ", - clean::KeywordItem(..) => "Keyword ", - clean::OpaqueTyItem(..) => "Opaque Type ", - clean::TraitAliasItem(..) => "Trait Alias ", - _ => { - // We don't generate pages for any other type. - unreachable!(); - } - }; - buf.write_str(name); - if !item.is_primitive() && !item.is_keyword() { - let cur = &cx.current; - let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() }; - for (i, component) in cur.iter().enumerate().take(amt) { - write!( - buf, - "{}::", - "../".repeat(cur.len() - i - 1), - component - ); - } - } - write!(buf, "{}", item.type_(), item.name.as_ref().unwrap()); - - buf.write_str(""); // in-band - buf.write_str(""); - render_stability_since_raw( - buf, - item.stable_since(cx.tcx()).as_deref(), - item.const_stable_since(cx.tcx()).as_deref(), - None, - None, - ); - buf.write_str( - "\ - \ - []\ - \ - ", - ); - - // Write `src` tag - // - // When this item is part of a `crate use` in a downstream crate, the - // [src] link in the downstream documentation will actually come back to - // this page, and this link will be auto-clicked. The `id` attribute is - // used to find the link to auto-click. - if cx.shared.include_sources && !item.is_primitive() { - write_srclink(cx, item, buf); - } - - buf.write_str("

    "); // out-of-band - - match *item.kind { - clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items), - clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => { - item_function(buf, cx, item, f) - } - clean::TraitItem(ref t) => item_trait(buf, cx, item, t), - clean::StructItem(ref s) => item_struct(buf, cx, item, s), - clean::UnionItem(ref s) => item_union(buf, cx, item, s), - clean::EnumItem(ref e) => item_enum(buf, cx, item, e), - clean::TypedefItem(ref t, _) => item_typedef(buf, cx, item, t), - clean::MacroItem(ref m) => item_macro(buf, cx, item, m), - clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m), - clean::PrimitiveItem(_) => item_primitive(buf, cx, item), - clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => item_static(buf, cx, item, i), - clean::ConstantItem(ref c) => item_constant(buf, cx, item, c), - clean::ForeignTypeItem => item_foreign_type(buf, cx, item), - clean::KeywordItem(_) => item_keyword(buf, cx, item), - clean::OpaqueTyItem(ref e) => item_opaque_ty(buf, cx, item, e), - clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta), - _ => { - // We don't generate pages for any other type. - unreachable!(); - } - } -} - -fn item_path(ty: ItemType, name: &str) -> String { - match ty { - ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)), - _ => format!("{}.{}.html", ty, name), - } -} - -fn full_path(cx: &Context<'_>, item: &clean::Item) -> String { - let mut s = cx.current.join("::"); - s.push_str("::"); - s.push_str(&item.name.unwrap().as_str()); - s -} - -fn document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) { - if let Some(ref name) = item.name { - info!("Documenting {}", name); - } - document_item_info(w, cx, item, false, parent); - document_full(w, item, cx, "", false); -} - -/// Render md_text as markdown. -fn render_markdown( - w: &mut Buffer, - cx: &Context<'_>, - md_text: &str, - links: Vec, - prefix: &str, - is_hidden: bool, -) { - let mut ids = cx.id_map.borrow_mut(); - write!( - w, - "
    {}{}
    ", - if is_hidden { " hidden" } else { "" }, - prefix, - Markdown( - md_text, - &links, - &mut ids, - cx.shared.codes, - cx.shared.edition, - &cx.shared.playground - ) - .into_string() - ) -} - -/// Writes a documentation block containing only the first paragraph of the documentation. If the -/// docs are longer, a "Read more" link is appended to the end. -fn document_short( - w: &mut Buffer, - item: &clean::Item, - cx: &Context<'_>, - link: AssocItemLink<'_>, - prefix: &str, - is_hidden: bool, - parent: Option<&clean::Item>, - show_def_docs: bool, -) { - document_item_info(w, cx, item, is_hidden, parent); - if !show_def_docs { - return; - } - if let Some(s) = item.doc_value() { - let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string(); - - if s.contains('\n') { - let link = - format!(r#" Read more"#, naive_assoc_href(item, link, cx.cache())); - - if let Some(idx) = summary_html.rfind("

    ") { - summary_html.insert_str(idx, &link); - } else { - summary_html.push_str(&link); + if let Some(idx) = summary_html.rfind("

    ") { + summary_html.insert_str(idx, &link); + } else { + summary_html.push_str(&link); } } @@ -1992,324 +747,6 @@ fn document_item_info( } } -fn document_non_exhaustive_header(item: &clean::Item) -> &str { - if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" } -} - -fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) { - if item.is_non_exhaustive() { - write!(w, "
    ", { - if item.is_struct() { - "struct" - } else if item.is_enum() { - "enum" - } else if item.is_variant() { - "variant" - } else { - "type" - } - }); - - if item.is_struct() { - w.write_str( - "Non-exhaustive structs could have additional fields added in future. \ - Therefore, non-exhaustive structs cannot be constructed in external crates \ - using the traditional Struct {{ .. }} syntax; cannot be \ - matched against without a wildcard ..; and \ - struct update syntax will not work.", - ); - } else if item.is_enum() { - w.write_str( - "Non-exhaustive enums could have additional variants added in future. \ - Therefore, when matching against variants of non-exhaustive enums, an \ - extra wildcard arm must be added to account for any future variants.", - ); - } else if item.is_variant() { - w.write_str( - "Non-exhaustive enum variants could have additional fields added in future. \ - Therefore, non-exhaustive enum variants cannot be constructed in external \ - crates and cannot be matched against.", - ); - } else { - w.write_str( - "This type will require a wildcard arm in any match statements or constructors.", - ); - } - - w.write_str("
    "); - } -} - -/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). -crate fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering { - /// Takes a non-numeric and a numeric part from the given &str. - fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) { - let i = s.find(|c: char| c.is_ascii_digit()); - let (a, b) = s.split_at(i.unwrap_or(s.len())); - let i = b.find(|c: char| !c.is_ascii_digit()); - let (b, c) = b.split_at(i.unwrap_or(b.len())); - *s = c; - (a, b) - } - - while !lhs.is_empty() || !rhs.is_empty() { - let (la, lb) = take_parts(&mut lhs); - let (ra, rb) = take_parts(&mut rhs); - // First process the non-numeric part. - match la.cmp(ra) { - Ordering::Equal => (), - x => return x, - } - // Then process the numeric part, if both sides have one (and they fit in a u64). - if let (Ok(ln), Ok(rn)) = (lb.parse::(), rb.parse::()) { - match ln.cmp(&rn) { - Ordering::Equal => (), - x => return x, - } - } - // Then process the numeric part again, but this time as strings. - match lb.cmp(rb) { - Ordering::Equal => (), - x => return x, - } - } - - Ordering::Equal -} - -fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { - document(w, cx, item, None); - - let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::>(); - - // the order of item types in the listing - fn reorder(ty: ItemType) -> u8 { - match ty { - ItemType::ExternCrate => 0, - ItemType::Import => 1, - ItemType::Primitive => 2, - ItemType::Module => 3, - ItemType::Macro => 4, - ItemType::Struct => 5, - ItemType::Enum => 6, - ItemType::Constant => 7, - ItemType::Static => 8, - ItemType::Trait => 9, - ItemType::Function => 10, - ItemType::Typedef => 12, - ItemType::Union => 13, - _ => 14 + ty as u8, - } - } - - fn cmp( - i1: &clean::Item, - i2: &clean::Item, - idx1: usize, - idx2: usize, - tcx: TyCtxt<'_>, - ) -> Ordering { - let ty1 = i1.type_(); - let ty2 = i2.type_(); - if ty1 != ty2 { - return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)); - } - let s1 = i1.stability(tcx).as_ref().map(|s| s.level); - let s2 = i2.stability(tcx).as_ref().map(|s| s.level); - if let (Some(a), Some(b)) = (s1, s2) { - match (a.is_stable(), b.is_stable()) { - (true, true) | (false, false) => {} - (false, true) => return Ordering::Less, - (true, false) => return Ordering::Greater, - } - } - let lhs = i1.name.unwrap_or(kw::Empty).as_str(); - let rhs = i2.name.unwrap_or(kw::Empty).as_str(); - compare_names(&lhs, &rhs) - } - - if cx.shared.sort_modules_alphabetically { - indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2, cx.tcx())); - } - // This call is to remove re-export duplicates in cases such as: - // - // ``` - // crate mod foo { - // crate mod bar { - // crate trait Double { fn foo(); } - // } - // } - // - // crate use foo::bar::*; - // crate use foo::*; - // ``` - // - // `Double` will appear twice in the generated docs. - // - // FIXME: This code is quite ugly and could be improved. Small issue: DefId - // can be identical even if the elements are different (mostly in imports). - // So in case this is an import, we keep everything by adding a "unique id" - // (which is the position in the vector). - indices.dedup_by_key(|i| { - ( - items[*i].def_id, - if items[*i].name.as_ref().is_some() { Some(full_path(cx, &items[*i])) } else { None }, - items[*i].type_(), - if items[*i].is_import() { *i } else { 0 }, - ) - }); - - debug!("{:?}", indices); - let mut curty = None; - for &idx in &indices { - let myitem = &items[idx]; - if myitem.is_stripped() { - continue; - } - - let myty = Some(myitem.type_()); - if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) { - // Put `extern crate` and `use` re-exports in the same section. - curty = myty; - } else if myty != curty { - if curty.is_some() { - w.write_str(""); - } - curty = myty; - let (short, name) = item_ty_to_strs(&myty.unwrap()); - write!( - w, - "

    \ - {name}

    \n", - id = cx.derive_id(short.to_owned()), - name = name - ); - } - - match *myitem.kind { - clean::ExternCrateItem(ref name, ref src) => { - use crate::html::format::anchor; - - match *src { - Some(ref src) => write!( - w, - ""); - } - - clean::ImportItem(ref import) => { - write!( - w, - "", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - import.print(cx.cache()) - ); - } - - _ => { - if myitem.name.is_none() { - continue; - } - - let unsafety_flag = match *myitem.kind { - clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func) - if func.header.unsafety == hir::Unsafety::Unsafe => - { - "âš " - } - _ => "", - }; - - let stab = myitem.stability_class(cx.tcx()); - let add = if stab.is_some() { " " } else { "" }; - - let doc_value = myitem.doc_value().unwrap_or_default(); - write!( - w, - "\ - \ - \ - ", - name = *myitem.name.as_ref().unwrap(), - stab_tags = extra_info_tags(myitem, item, cx.tcx()), - docs = MarkdownSummaryLine(&doc_value, &myitem.links(&cx.cache)).into_string(), - class = myitem.type_(), - add = add, - stab = stab.unwrap_or_else(String::new), - unsafety_flag = unsafety_flag, - href = item_path(myitem.type_(), &myitem.name.unwrap().as_str()), - title = [full_path(cx, myitem), myitem.type_().to_string()] - .iter() - .filter_map(|s| if !s.is_empty() { Some(s.as_str()) } else { None }) - .collect::>() - .join(" "), - ); - } - } - } - - if curty.is_some() { - w.write_str("
    {}extern crate {} as {};", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - anchor(myitem.def_id, &*src.as_str(), cx.cache()), - name - ), - None => write!( - w, - "
    {}extern crate {};", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - anchor(myitem.def_id, &*name.as_str(), cx.cache()) - ), - } - w.write_str("
    {}{}
    {name}{unsafety_flag}{stab_tags}{docs}
    "); - } -} - -/// Render the stability, deprecation and portability tags that are displayed in the item's summary -/// at the module level. -fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> String { - let mut tags = String::new(); - - fn tag_html(class: &str, title: &str, contents: &str) -> String { - format!(r#"{}"#, class, Escape(title), contents) - } - - // The trailing space after each tag is to space it properly against the rest of the docs. - if let Some(depr) = &item.deprecation(tcx) { - let mut message = "Deprecated"; - if !stability::deprecation_in_effect( - depr.is_since_rustc_version, - depr.since.map(|s| s.as_str()).as_deref(), - ) { - message = "Deprecation planned"; - } - tags += &tag_html("deprecated", "", message); - } - - // The "rustc_private" crates are permanently unstable so it makes no sense - // to render "unstable" everywhere. - if item - .stability(tcx) - .as_ref() - .map(|s| s.level.is_unstable() && s.feature != sym::rustc_private) - == Some(true) - { - tags += &tag_html("unstable", "", "Experimental"); - } - - let cfg = match (&item.attrs.cfg, parent.attrs.cfg.as_ref()) { - (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), - (cfg, _) => cfg.as_deref().cloned(), - }; - - debug!("Portability {:?} - {:?} = {:?}", item.attrs.cfg, parent.attrs.cfg, cfg); - if let Some(ref cfg) = cfg { - tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html()); - } - - tags -} - fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option { let cfg = match (&item.attrs.cfg, parent.and_then(|p| p.attrs.cfg.as_ref())) { (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), @@ -2394,504 +831,66 @@ fn short_item_info( )); } - message.push_str(&format!(" ({})", feature)); - - if let Some(unstable_reason) = reason { - let mut ids = cx.id_map.borrow_mut(); - message = format!( - "
    {}{}
    ", - message, - MarkdownHtml( - &unstable_reason.as_str(), - &mut ids, - error_codes, - cx.shared.edition, - &cx.shared.playground, - ) - .into_string() - ); - } - - extra_info.push(format!("
    {}
    ", message)); - } - - if let Some(portability) = portability(item, parent) { - extra_info.push(portability); - } - - extra_info -} - -fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::Constant) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -
    -    write!(
    -        w,
    -        "{vis}const {name}: {typ}",
    -        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    -        name = it.name.as_ref().unwrap(),
    -        typ = c.type_.print(cx.cache()),
    -    );
    -
    -    if c.value.is_some() || c.is_literal {
    -        write!(w, " = {expr};", expr = Escape(&c.expr));
    -    } else {
    -        w.write_str(";");
    -    }
    -
    -    if let Some(value) = &c.value {
    -        if !c.is_literal {
    -            let value_lowercase = value.to_lowercase();
    -            let expr_lowercase = c.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("
    "); - document(w, cx, it, None) -} - -fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Static) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "{vis}static {mutability}{name}: {typ}
    ", - vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - mutability = s.mutability.print_with_space(), - name = it.name.as_ref().unwrap(), - typ = s.type_.print(cx.cache()) - ); - document(w, cx, it, None) -} - -fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { - let header_len = format!( - "{}{}{}{}{:#}fn {}{:#}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - f.header.constness.print_with_space(), - f.header.asyncness.print_with_space(), - f.header.unsafety.print_with_space(), - print_abi_with_space(f.header.abi), - it.name.as_ref().unwrap(), - f.generics.print(cx.cache()) - ) - .len(); - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "{vis}{constness}{asyncness}{unsafety}{abi}fn \
    -         {name}{generics}{decl}{spotlight}{where_clause}
    ", - vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - constness = f.header.constness.print_with_space(), - asyncness = f.header.asyncness.print_with_space(), - unsafety = f.header.unsafety.print_with_space(), - abi = print_abi_with_space(f.header.abi), - name = it.name.as_ref().unwrap(), - generics = f.generics.print(cx.cache()), - where_clause = - WhereClause { gens: &f.generics, indent: 0, end_newline: true }.print(cx.cache()), - decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness } - .print(cx.cache()), - spotlight = spotlight_decl(&f.decl, cx.cache()), - ); - document(w, cx, it, None) -} - -fn render_implementor( - cx: &Context<'_>, - implementor: &Impl, - trait_: &clean::Item, - w: &mut Buffer, - implementor_dups: &FxHashMap, - aliases: &[String], -) { - // If there's already another implementor that has the same abbridged name, use the - // full path, for example in `std::iter::ExactSizeIterator` - let use_absolute = match implementor.inner_impl().for_ { - clean::ResolvedPath { ref path, is_generic: false, .. } - | clean::BorrowedRef { - type_: box clean::ResolvedPath { ref path, is_generic: false, .. }, - .. - } => implementor_dups[&path.last()].1, - _ => false, - }; - render_impl( - w, - cx, - implementor, - trait_, - AssocItemLink::Anchor(None), - RenderMode::Normal, - trait_.stable_since(cx.tcx()).as_deref(), - trait_.const_stable_since(cx.tcx()).as_deref(), - false, - Some(use_absolute), - false, - false, - aliases, - ); -} - -fn render_impls( - cx: &Context<'_>, - w: &mut Buffer, - traits: &[&&Impl], - containing_item: &clean::Item, -) { - let mut impls = traits - .iter() - .map(|i| { - let did = i.trait_did_full(cx.cache()).unwrap(); - let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods); - let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() }; - render_impl( - &mut buffer, - cx, - i, - containing_item, - assoc_link, - RenderMode::Normal, - containing_item.stable_since(cx.tcx()).as_deref(), - containing_item.const_stable_since(cx.tcx()).as_deref(), - true, - None, - false, - true, - &[], - ); - buffer.into_inner() - }) - .collect::>(); - impls.sort(); - w.write_str(&impls.join("")); -} - -fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cache: &Cache) -> String { - let mut bounds = String::new(); - if !t_bounds.is_empty() { - if !trait_alias { - bounds.push_str(": "); - } - for (i, p) in t_bounds.iter().enumerate() { - if i > 0 { - bounds.push_str(" + "); - } - bounds.push_str(&p.print(cache).to_string()); - } - } - bounds -} - -fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cache: &Cache) -> Ordering { - let lhs = format!("{}", lhs.inner_impl().print(cache, false)); - let rhs = format!("{}", rhs.inner_impl().print(cache, false)); - - // lhs and rhs are formatted as HTML, which may be unnecessary - compare_names(&lhs, &rhs) -} - -fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { - let bounds = bounds(&t.bounds, false, cx.cache()); - let types = t.items.iter().filter(|m| m.is_associated_type()).collect::>(); - let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::>(); - let required = t.items.iter().filter(|m| m.is_ty_method()).collect::>(); - let provided = t.items.iter().filter(|m| m.is_method()).collect::>(); - - // Output the trait definition - wrap_into_docblock(w, |w| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        write!(
    -            w,
    -            "{}{}{}trait {}{}{}",
    -            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    -            t.unsafety.print_with_space(),
    -            if t.is_auto { "auto " } else { "" },
    -            it.name.as_ref().unwrap(),
    -            t.generics.print(cx.cache()),
    -            bounds
    -        );
    -
    -        if !t.generics.where_predicates.is_empty() {
    -            let where_ = WhereClause { gens: &t.generics, indent: 0, end_newline: true };
    -            write!(w, "{}", where_.print(cx.cache()));
    -        } else {
    -            w.write_str(" ");
    -        }
    -
    -        if t.items.is_empty() {
    -            w.write_str("{ }");
    -        } else {
    -            // FIXME: we should be using a derived_id for the Anchors here
    -            w.write_str("{\n");
    -            for t in &types {
    -                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    -                w.write_str(";\n");
    -            }
    -            if !types.is_empty() && !consts.is_empty() {
    -                w.write_str("\n");
    -            }
    -            for t in &consts {
    -                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    -                w.write_str(";\n");
    -            }
    -            if !consts.is_empty() && !required.is_empty() {
    -                w.write_str("\n");
    -            }
    -            for (pos, m) in required.iter().enumerate() {
    -                render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    -                w.write_str(";\n");
    -
    -                if pos < required.len() - 1 {
    -                    w.write_str("
    "); - } - } - if !required.is_empty() && !provided.is_empty() { - w.write_str("\n"); - } - for (pos, m) in provided.iter().enumerate() { - render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx); - match *m.kind { - clean::MethodItem(ref inner, _) - if !inner.generics.where_predicates.is_empty() => - { - w.write_str(",\n { ... }\n"); - } - _ => { - w.write_str(" { ... }\n"); - } - } - if pos < provided.len() - 1 { - w.write_str("
    "); - } - } - w.write_str("}"); - } - w.write_str("
    ") - }); - - // Trait documentation - document(w, cx, it, None); - - fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { - write!( - w, - "

    \ - {1}\ -

    {2}", - id, title, extra_content - ) - } - - fn write_loading_content(w: &mut Buffer, extra_content: &str) { - write!(w, "{}Loading content...", extra_content) - } - - fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) { - let name = m.name.as_ref().unwrap(); - info!("Documenting {} on {:?}", name, t.name); - let item_type = m.type_(); - let id = cx.derive_id(format!("{}.{}", item_type, name)); - write!(w, "

    ", id = id,); - render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl, cx); - w.write_str(""); - render_stability_since(w, m, t, cx.tcx()); - write_srclink(cx, m, w); - w.write_str("

    "); - document(w, cx, m, Some(t)); - } - - if !types.is_empty() { - write_small_section_header( - w, - "associated-types", - "Associated Types", - "
    ", - ); - for t in types { - trait_item(w, cx, t, it); - } - write_loading_content(w, "
    "); - } - - if !consts.is_empty() { - write_small_section_header( - w, - "associated-const", - "Associated Constants", - "
    ", - ); - for t in consts { - trait_item(w, cx, t, it); - } - write_loading_content(w, "
    "); - } - - // Output the documentation for each function individually - if !required.is_empty() { - write_small_section_header( - w, - "required-methods", - "Required methods", - "
    ", - ); - for m in required { - trait_item(w, cx, m, it); - } - write_loading_content(w, "
    "); - } - if !provided.is_empty() { - write_small_section_header( - w, - "provided-methods", - "Provided methods", - "
    ", - ); - for m in provided { - trait_item(w, cx, m, it); - } - write_loading_content(w, "
    "); - } - - // If there are methods directly on this trait object, render them here. - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All); - - if let Some(implementors) = cx.cache.implementors.get(&it.def_id) { - // The DefId is for the first Type found with that name. The bool is - // if any Types with the same name but different DefId have been found. - let mut implementor_dups: FxHashMap = FxHashMap::default(); - for implementor in implementors { - match implementor.inner_impl().for_ { - clean::ResolvedPath { ref path, did, is_generic: false, .. } - | clean::BorrowedRef { - type_: box clean::ResolvedPath { ref path, did, is_generic: false, .. }, - .. - } => { - let &mut (prev_did, ref mut has_duplicates) = - implementor_dups.entry(path.last()).or_insert((did, false)); - if prev_did != did { - *has_duplicates = true; - } - } - _ => {} - } - } - - let (local, foreign) = implementors.iter().partition::, _>(|i| { - i.inner_impl() - .for_ - .def_id_full(cx.cache()) - .map_or(true, |d| cx.cache.paths.contains_key(&d)) - }); - - let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) = - local.iter().partition(|i| i.inner_impl().synthetic); - - synthetic.sort_by(|a, b| compare_impl(a, b, cx.cache())); - concrete.sort_by(|a, b| compare_impl(a, b, cx.cache())); - - if !foreign.is_empty() { - write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", ""); - - for implementor in foreign { - let assoc_link = AssocItemLink::GotoSource( - implementor.impl_item.def_id, - &implementor.inner_impl().provided_trait_methods, - ); - render_impl( - w, - cx, - &implementor, - it, - assoc_link, - RenderMode::Normal, - implementor.impl_item.stable_since(cx.tcx()).as_deref(), - implementor.impl_item.const_stable_since(cx.tcx()).as_deref(), - false, - None, - true, - false, - &[], - ); - } - write_loading_content(w, ""); - } - - write_small_section_header( - w, - "implementors", - "Implementors", - "
    ", - ); - for implementor in concrete { - render_implementor(cx, implementor, it, w, &implementor_dups, &[]); - } - write_loading_content(w, "
    "); - - if t.is_auto { - write_small_section_header( - w, - "synthetic-implementors", - "Auto implementors", - "
    ", - ); - for implementor in synthetic { - render_implementor( - cx, - implementor, - it, - w, - &implementor_dups, - &collect_paths_for_type(implementor.inner_impl().for_.clone(), &cx.cache), - ); - } - write_loading_content(w, "
    "); - } - } else { - // even without any implementations to write in, we still want the heading and list, so the - // implementors javascript file pulled in below has somewhere to write the impls into - write_small_section_header( - w, - "implementors", - "Implementors", - "
    ", - ); - write_loading_content(w, "
    "); + message.push_str(&format!(" ({})", feature)); - if t.is_auto { - write_small_section_header( - w, - "synthetic-implementors", - "Auto implementors", - "
    ", + if let Some(unstable_reason) = reason { + let mut ids = cx.id_map.borrow_mut(); + message = format!( + "
    {}{}
    ", + message, + MarkdownHtml( + &unstable_reason.as_str(), + &mut ids, + error_codes, + cx.shared.edition, + &cx.shared.playground, + ) + .into_string() ); - write_loading_content(w, "
    "); } + + extra_info.push(format!("
    {}
    ", message)); } - write!( - w, - "", - root_path = vec![".."; cx.current.len()].join("/"), - path = if it.def_id.is_local() { - cx.current.join("/") - } else { - let (ref path, _) = cx.cache.external_paths[&it.def_id]; - path[..path.len() - 1].join("/") - }, - ty = it.type_(), - name = *it.name.as_ref().unwrap() - ); + if let Some(portability) = portability(item, parent) { + extra_info.push(portability); + } + + extra_info +} + +fn render_impls( + cx: &Context<'_>, + w: &mut Buffer, + traits: &[&&Impl], + containing_item: &clean::Item, +) { + let mut impls = traits + .iter() + .map(|i| { + let did = i.trait_did_full(cx.cache()).unwrap(); + let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods); + let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() }; + render_impl( + &mut buffer, + cx, + i, + containing_item, + assoc_link, + RenderMode::Normal, + containing_item.stable_since(cx.tcx()).as_deref(), + containing_item.const_stable_since(cx.tcx()).as_deref(), + true, + None, + false, + true, + &[], + ); + buffer.into_inner() + }) + .collect::>(); + impls.sort(); + w.write_str(&impls.join("")); } fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cache: &Cache) -> String { @@ -2986,21 +985,6 @@ fn render_stability_since_raw( } } -fn render_stability_since( - w: &mut Buffer, - item: &clean::Item, - containing_item: &clean::Item, - tcx: TyCtxt<'_>, -) { - render_stability_since_raw( - w, - item.stable_since(tcx).as_deref(), - item.const_stable_since(tcx).as_deref(), - containing_item.stable_since(tcx).as_deref(), - containing_item.const_stable_since(tcx).as_deref(), - ) -} - fn render_assoc_item( w: &mut Buffer, item: &clean::Item, @@ -3104,227 +1088,6 @@ fn render_assoc_item( } } -fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) { - wrap_into_docblock(w, |w| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
    -        w.write_str("
    ") - }); - - document(w, cx, it, None); - let mut fields = s - .fields - .iter() - .filter_map(|f| match *f.kind { - clean::StructFieldItem(ref ty) => Some((f, ty)), - _ => None, - }) - .peekable(); - if let CtorKind::Fictive = s.struct_type { - if fields.peek().is_some() { - write!( - w, - "

    - Fields{}

    ", - document_non_exhaustive_header(it) - ); - document_non_exhaustive(w, it); - for (field, ty) in fields { - let id = cx.derive_id(format!( - "{}.{}", - ItemType::StructField, - field.name.as_ref().unwrap() - )); - write!( - w, - "\ - \ - {name}: {ty}\ - ", - item_type = ItemType::StructField, - id = id, - name = field.name.as_ref().unwrap(), - ty = ty.print(cx.cache()) - ); - document(w, cx, field, Some(it)); - } - } - } - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) { - wrap_into_docblock(w, |w| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        render_union(w, it, Some(&s.generics), &s.fields, "", true, cx);
    -        w.write_str("
    ") - }); - - document(w, cx, it, None); - let mut fields = s - .fields - .iter() - .filter_map(|f| match *f.kind { - clean::StructFieldItem(ref ty) => Some((f, ty)), - _ => None, - }) - .peekable(); - if fields.peek().is_some() { - write!( - w, - "

    - Fields

    " - ); - for (field, ty) in fields { - let name = field.name.as_ref().expect("union field name"); - let id = format!("{}.{}", ItemType::StructField, name); - write!( - w, - "\ - \ - {name}: {ty}\ - ", - id = id, - name = name, - shortty = ItemType::StructField, - ty = ty.print(cx.cache()) - ); - if let Some(stability_class) = field.stability_class(cx.tcx()) { - write!(w, "", stab = stability_class); - } - document(w, cx, field, Some(it)); - } - } - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) { - wrap_into_docblock(w, |w| { - w.write_str("
    ");
    -        render_attributes(w, it, true);
    -        write!(
    -            w,
    -            "{}enum {}{}{}",
    -            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    -            it.name.as_ref().unwrap(),
    -            e.generics.print(cx.cache()),
    -            WhereClause { gens: &e.generics, indent: 0, end_newline: true }.print(cx.cache())
    -        );
    -        if e.variants.is_empty() && !e.variants_stripped {
    -            w.write_str(" {}");
    -        } else {
    -            w.write_str(" {\n");
    -            for v in &e.variants {
    -                w.write_str("    ");
    -                let name = v.name.as_ref().unwrap();
    -                match *v.kind {
    -                    clean::VariantItem(ref var) => match var {
    -                        clean::Variant::CLike => write!(w, "{}", name),
    -                        clean::Variant::Tuple(ref tys) => {
    -                            write!(w, "{}(", name);
    -                            for (i, ty) in tys.iter().enumerate() {
    -                                if i > 0 {
    -                                    w.write_str(", ")
    -                                }
    -                                write!(w, "{}", ty.print(cx.cache()));
    -                            }
    -                            w.write_str(")");
    -                        }
    -                        clean::Variant::Struct(ref s) => {
    -                            render_struct(w, v, None, s.struct_type, &s.fields, "    ", false, cx);
    -                        }
    -                    },
    -                    _ => unreachable!(),
    -                }
    -                w.write_str(",\n");
    -            }
    -
    -            if e.variants_stripped {
    -                w.write_str("    // some variants omitted\n");
    -            }
    -            w.write_str("}");
    -        }
    -        w.write_str("
    ") - }); - - document(w, cx, it, None); - if !e.variants.is_empty() { - write!( - w, - "

    - Variants{}

    \n", - document_non_exhaustive_header(it) - ); - document_non_exhaustive(w, it); - for variant in &e.variants { - let id = - cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.as_ref().unwrap())); - write!( - w, - "
    \ - \ - {name}", - id = id, - name = variant.name.as_ref().unwrap() - ); - if let clean::VariantItem(clean::Variant::Tuple(ref tys)) = *variant.kind { - w.write_str("("); - for (i, ty) in tys.iter().enumerate() { - if i > 0 { - w.write_str(", "); - } - write!(w, "{}", ty.print(cx.cache())); - } - w.write_str(")"); - } - w.write_str("
    "); - document(w, cx, variant, Some(it)); - document_non_exhaustive(w, variant); - - use crate::clean::Variant; - if let clean::VariantItem(Variant::Struct(ref s)) = *variant.kind { - let variant_id = cx.derive_id(format!( - "{}.{}.fields", - ItemType::Variant, - variant.name.as_ref().unwrap() - )); - write!(w, "
    ", id = variant_id); - write!( - w, - "

    Fields of {name}

    ", - name = variant.name.as_ref().unwrap() - ); - for field in &s.fields { - use crate::clean::StructFieldItem; - if let StructFieldItem(ref ty) = *field.kind { - let id = cx.derive_id(format!( - "variant.{}.field.{}", - variant.name.as_ref().unwrap(), - field.name.as_ref().unwrap() - )); - write!( - w, - "\ - \ - {f}: {t}\ - ", - id = id, - f = field.name.as_ref().unwrap(), - t = ty.print(cx.cache()) - ); - document(w, cx, field, Some(variant)); - } - } - w.write_str("
    "); - } - render_stability_since(w, variant, it, cx.tcx()); - } - } - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - const ALLOWED_ATTRIBUTES: &[Symbol] = &[ sym::export_name, sym::lang, @@ -3367,147 +1130,6 @@ fn render_attributes(w: &mut Buffer, it: &clean::Item, top: bool) { } } -fn render_struct( - w: &mut Buffer, - it: &clean::Item, - g: Option<&clean::Generics>, - ty: CtorKind, - fields: &[clean::Item], - tab: &str, - structhead: bool, - cx: &Context<'_>, -) { - write!( - w, - "{}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - if structhead { "struct " } else { "" }, - it.name.as_ref().unwrap() - ); - if let Some(g) = g { - write!(w, "{}", g.print(cx.cache())) - } - match ty { - CtorKind::Fictive => { - if let Some(g) = g { - write!( - w, - "{}", - WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache()) - ) - } - let mut has_visible_fields = false; - w.write_str(" {"); - for field in fields { - if let clean::StructFieldItem(ref ty) = *field.kind { - write!( - w, - "\n{} {}{}: {},", - tab, - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), - field.name.as_ref().unwrap(), - ty.print(cx.cache()) - ); - has_visible_fields = true; - } - } - - if has_visible_fields { - if it.has_stripped_fields().unwrap() { - write!(w, "\n{} // some fields omitted", tab); - } - write!(w, "\n{}", tab); - } else if it.has_stripped_fields().unwrap() { - // If there are no visible fields we can just display - // `{ /* fields omitted */ }` to save space. - write!(w, " /* fields omitted */ "); - } - w.write_str("}"); - } - CtorKind::Fn => { - w.write_str("("); - for (i, field) in fields.iter().enumerate() { - if i > 0 { - w.write_str(", "); - } - match *field.kind { - clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), - clean::StructFieldItem(ref ty) => { - write!( - w, - "{}{}", - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), - ty.print(cx.cache()) - ) - } - _ => unreachable!(), - } - } - w.write_str(")"); - if let Some(g) = g { - write!( - w, - "{}", - WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) - ) - } - w.write_str(";"); - } - CtorKind::Const => { - // Needed for PhantomData. - if let Some(g) = g { - write!( - w, - "{}", - WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) - ) - } - w.write_str(";"); - } - } -} - -fn render_union( - w: &mut Buffer, - it: &clean::Item, - g: Option<&clean::Generics>, - fields: &[clean::Item], - tab: &str, - structhead: bool, - cx: &Context<'_>, -) { - write!( - w, - "{}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - if structhead { "union " } else { "" }, - it.name.as_ref().unwrap() - ); - if let Some(g) = g { - write!(w, "{}", g.print(cx.cache())); - write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache())); - } - - write!(w, " {{\n{}", tab); - for field in fields { - if let clean::StructFieldItem(ref ty) = *field.kind { - write!( - w, - " {}{}: {},\n{}", - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), - field.name.as_ref().unwrap(), - ty.print(cx.cache()), - tab - ); - } - } - - if it.has_stripped_fields().unwrap() { - write!(w, " // some fields omitted\n{}", tab); - } - w.write_str("}"); -} - #[derive(Copy, Clone)] enum AssocItemLink<'a> { Anchor(Option<&'a str>), @@ -4078,86 +1700,6 @@ fn render_impl( w.write_str(""); } -fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "type {}{}{where_clause} = impl {bounds};
    ", - it.name.as_ref().unwrap(), - t.generics.print(cx.cache()), - where_clause = - WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), - bounds = bounds(&t.bounds, false, cx.cache()) - ); - - document(w, cx, it, None); - - // Render any items associated directly to this alias, as otherwise they - // won't be visible anywhere in the docs. It would be nice to also show - // associated items from the aliased type (see discussion in #32077), but - // we need #14072 to make sense of the generics. - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TraitAlias) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "trait {}{}{} = {};
    ", - it.name.as_ref().unwrap(), - t.generics.print(cx.cache()), - WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), - bounds(&t.bounds, true, cx.cache()) - ); - - document(w, cx, it, None); - - // Render any items associated directly to this alias, as otherwise they - // won't be visible anywhere in the docs. It would be nice to also show - // associated items from the aliased type (see discussion in #32077), but - // we need #14072 to make sense of the generics. - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -fn item_typedef(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) { - w.write_str("
    ");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "type {}{}{where_clause} = {type_};
    ", - it.name.as_ref().unwrap(), - t.generics.print(cx.cache()), - where_clause = - WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), - type_ = t.type_.print(cx.cache()) - ); - - document(w, cx, it, None); - - // Render any items associated directly to this alias, as otherwise they - // won't be visible anywhere in the docs. It would be nice to also show - // associated items from the aliased type (see discussion in #32077), but - // we need #14072 to make sense of the generics. - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { - w.write_str("
    extern {\n");
    -    render_attributes(w, it, false);
    -    write!(
    -        w,
    -        "    {}type {};\n}}
    ", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), - it.name.as_ref().unwrap(), - ); - - document(w, cx, it, None); - - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { let parentlen = cx.current.len() - if it.is_mod() { 1 } else { 0 }; @@ -4850,65 +2392,8 @@ fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { } } -fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) { - wrap_into_docblock(w, |w| { - highlight::render_with_highlighting( - &t.source, - w, - Some("macro"), - None, - None, - it.source.span().edition(), - ); - }); - document(w, cx, it, None) -} - -fn item_proc_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, m: &clean::ProcMacro) { - let name = it.name.as_ref().expect("proc-macros always have names"); - match m.kind { - MacroKind::Bang => { - w.push_str("
    ");
    -            write!(w, "{}!() {{ /* proc-macro */ }}", name);
    -            w.push_str("
    "); - } - MacroKind::Attr => { - w.push_str("
    ");
    -            write!(w, "#[{}]", name);
    -            w.push_str("
    "); - } - MacroKind::Derive => { - w.push_str("
    ");
    -            write!(w, "#[derive({})]", name);
    -            if !m.helpers.is_empty() {
    -                w.push_str("\n{\n");
    -                w.push_str("    // Attributes available to this derive:\n");
    -                for attr in &m.helpers {
    -                    writeln!(w, "    #[{}]", attr);
    -                }
    -                w.push_str("}\n");
    -            }
    -            w.push_str("
    "); - } - } - document(w, cx, it, None) -} - -fn item_primitive(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { - document(w, cx, it, None); - render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) -} - -fn item_keyword(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { - document(w, cx, it, None) -} - crate const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang"; -fn make_item_keywords(it: &clean::Item) -> String { - format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap()) -} - /// Returns a list of all paths used in the type. /// This is used to help deduplicate imported impls /// for reexported types. If any of the contained diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs new file mode 100644 index 0000000000000..6d61248f28ef0 --- /dev/null +++ b/src/librustdoc/html/render/print_item.rs @@ -0,0 +1,1420 @@ +use std::cmp::Ordering; + +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::def::CtorKind; +use rustc_hir::def_id::DefId; +use rustc_middle::middle::stability; +use rustc_middle::ty::TyCtxt; +use rustc_span::hygiene::MacroKind; +use rustc_span::symbol::{kw, sym, Symbol}; + +use super::{ + collect_paths_for_type, document, ensure_trailing_slash, item_ty_to_strs, render_assoc_item, + render_assoc_items, render_attributes, render_impl, render_stability_since_raw, spotlight_decl, + write_srclink, AssocItemLink, Context, +}; +use crate::clean::{self, GetDefId}; +use crate::formats::cache::Cache; +use crate::formats::item_type::ItemType; +use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode}; +use crate::html::escape::Escape; +use crate::html::format::{print_abi_with_space, Buffer, Function, PrintWithSpace, WhereClause}; +use crate::html::highlight; +use crate::html::markdown::MarkdownSummaryLine; + +pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { + debug_assert!(!item.is_stripped()); + // Write the breadcrumb trail header for the top + buf.write_str("

    "); + let name = match *item.kind { + clean::ModuleItem(ref m) => { + if m.is_crate { + "Crate " + } else { + "Module " + } + } + clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ", + clean::TraitItem(..) => "Trait ", + clean::StructItem(..) => "Struct ", + clean::UnionItem(..) => "Union ", + clean::EnumItem(..) => "Enum ", + clean::TypedefItem(..) => "Type Definition ", + clean::MacroItem(..) => "Macro ", + clean::ProcMacroItem(ref mac) => match mac.kind { + MacroKind::Bang => "Macro ", + MacroKind::Attr => "Attribute Macro ", + MacroKind::Derive => "Derive Macro ", + }, + clean::PrimitiveItem(..) => "Primitive Type ", + clean::StaticItem(..) | clean::ForeignStaticItem(..) => "Static ", + clean::ConstantItem(..) => "Constant ", + clean::ForeignTypeItem => "Foreign Type ", + clean::KeywordItem(..) => "Keyword ", + clean::OpaqueTyItem(..) => "Opaque Type ", + clean::TraitAliasItem(..) => "Trait Alias ", + _ => { + // We don't generate pages for any other type. + unreachable!(); + } + }; + buf.write_str(name); + if !item.is_primitive() && !item.is_keyword() { + let cur = &cx.current; + let amt = if item.is_mod() { cur.len() - 1 } else { cur.len() }; + for (i, component) in cur.iter().enumerate().take(amt) { + write!( + buf, + "{}::", + "../".repeat(cur.len() - i - 1), + component + ); + } + } + write!(buf, "{}", item.type_(), item.name.as_ref().unwrap()); + + buf.write_str(""); // in-band + buf.write_str(""); + render_stability_since_raw( + buf, + item.stable_since(cx.tcx()).as_deref(), + item.const_stable_since(cx.tcx()).as_deref(), + None, + None, + ); + buf.write_str( + "\ + \ + []\ + \ + ", + ); + + // Write `src` tag + // + // When this item is part of a `crate use` in a downstream crate, the + // [src] link in the downstream documentation will actually come back to + // this page, and this link will be auto-clicked. The `id` attribute is + // used to find the link to auto-click. + if cx.shared.include_sources && !item.is_primitive() { + write_srclink(cx, item, buf); + } + + buf.write_str("

    "); // out-of-band + + match *item.kind { + clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items), + clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => { + item_function(buf, cx, item, f) + } + clean::TraitItem(ref t) => item_trait(buf, cx, item, t), + clean::StructItem(ref s) => item_struct(buf, cx, item, s), + clean::UnionItem(ref s) => item_union(buf, cx, item, s), + clean::EnumItem(ref e) => item_enum(buf, cx, item, e), + clean::TypedefItem(ref t, _) => item_typedef(buf, cx, item, t), + clean::MacroItem(ref m) => item_macro(buf, cx, item, m), + clean::ProcMacroItem(ref m) => item_proc_macro(buf, cx, item, m), + clean::PrimitiveItem(_) => item_primitive(buf, cx, item), + clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => item_static(buf, cx, item, i), + clean::ConstantItem(ref c) => item_constant(buf, cx, item, c), + clean::ForeignTypeItem => item_foreign_type(buf, cx, item), + clean::KeywordItem(_) => item_keyword(buf, cx, item), + clean::OpaqueTyItem(ref e) => item_opaque_ty(buf, cx, item, e), + clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta), + _ => { + // We don't generate pages for any other type. + unreachable!(); + } + } +} + +fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { + document(w, cx, item, None); + + let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::>(); + + // the order of item types in the listing + fn reorder(ty: ItemType) -> u8 { + match ty { + ItemType::ExternCrate => 0, + ItemType::Import => 1, + ItemType::Primitive => 2, + ItemType::Module => 3, + ItemType::Macro => 4, + ItemType::Struct => 5, + ItemType::Enum => 6, + ItemType::Constant => 7, + ItemType::Static => 8, + ItemType::Trait => 9, + ItemType::Function => 10, + ItemType::Typedef => 12, + ItemType::Union => 13, + _ => 14 + ty as u8, + } + } + + fn cmp( + i1: &clean::Item, + i2: &clean::Item, + idx1: usize, + idx2: usize, + tcx: TyCtxt<'_>, + ) -> Ordering { + let ty1 = i1.type_(); + let ty2 = i2.type_(); + if ty1 != ty2 { + return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)); + } + let s1 = i1.stability(tcx).as_ref().map(|s| s.level); + let s2 = i2.stability(tcx).as_ref().map(|s| s.level); + if let (Some(a), Some(b)) = (s1, s2) { + match (a.is_stable(), b.is_stable()) { + (true, true) | (false, false) => {} + (false, true) => return Ordering::Less, + (true, false) => return Ordering::Greater, + } + } + let lhs = i1.name.unwrap_or(kw::Empty).as_str(); + let rhs = i2.name.unwrap_or(kw::Empty).as_str(); + compare_names(&lhs, &rhs) + } + + if cx.shared.sort_modules_alphabetically { + indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2, cx.tcx())); + } + // This call is to remove re-export duplicates in cases such as: + // + // ``` + // crate mod foo { + // crate mod bar { + // crate trait Double { fn foo(); } + // } + // } + // + // crate use foo::bar::*; + // crate use foo::*; + // ``` + // + // `Double` will appear twice in the generated docs. + // + // FIXME: This code is quite ugly and could be improved. Small issue: DefId + // can be identical even if the elements are different (mostly in imports). + // So in case this is an import, we keep everything by adding a "unique id" + // (which is the position in the vector). + indices.dedup_by_key(|i| { + ( + items[*i].def_id, + if items[*i].name.as_ref().is_some() { Some(full_path(cx, &items[*i])) } else { None }, + items[*i].type_(), + if items[*i].is_import() { *i } else { 0 }, + ) + }); + + debug!("{:?}", indices); + let mut curty = None; + for &idx in &indices { + let myitem = &items[idx]; + if myitem.is_stripped() { + continue; + } + + let myty = Some(myitem.type_()); + if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) { + // Put `extern crate` and `use` re-exports in the same section. + curty = myty; + } else if myty != curty { + if curty.is_some() { + w.write_str(""); + } + curty = myty; + let (short, name) = item_ty_to_strs(&myty.unwrap()); + write!( + w, + "

    \ + {name}

    \n", + id = cx.derive_id(short.to_owned()), + name = name + ); + } + + match *myitem.kind { + clean::ExternCrateItem(ref name, ref src) => { + use crate::html::format::anchor; + + match *src { + Some(ref src) => write!( + w, + ""); + } + + clean::ImportItem(ref import) => { + write!( + w, + "", + myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), + import.print(cx.cache()) + ); + } + + _ => { + if myitem.name.is_none() { + continue; + } + + let unsafety_flag = match *myitem.kind { + clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func) + if func.header.unsafety == hir::Unsafety::Unsafe => + { + "âš " + } + _ => "", + }; + + let stab = myitem.stability_class(cx.tcx()); + let add = if stab.is_some() { " " } else { "" }; + + let doc_value = myitem.doc_value().unwrap_or_default(); + write!( + w, + "\ + \ + \ + ", + name = *myitem.name.as_ref().unwrap(), + stab_tags = extra_info_tags(myitem, item, cx.tcx()), + docs = MarkdownSummaryLine(&doc_value, &myitem.links(&cx.cache)).into_string(), + class = myitem.type_(), + add = add, + stab = stab.unwrap_or_else(String::new), + unsafety_flag = unsafety_flag, + href = item_path(myitem.type_(), &myitem.name.unwrap().as_str()), + title = [full_path(cx, myitem), myitem.type_().to_string()] + .iter() + .filter_map(|s| if !s.is_empty() { Some(s.as_str()) } else { None }) + .collect::>() + .join(" "), + ); + } + } + } + + if curty.is_some() { + w.write_str("
    {}extern crate {} as {};", + myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), + anchor(myitem.def_id, &*src.as_str(), cx.cache()), + name + ), + None => write!( + w, + "
    {}extern crate {};", + myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), + anchor(myitem.def_id, &*name.as_str(), cx.cache()) + ), + } + w.write_str("
    {}{}
    {name}{unsafety_flag}{stab_tags}{docs}
    "); + } +} + +/// Render the stability, deprecation and portability tags that are displayed in the item's summary +/// at the module level. +fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> String { + let mut tags = String::new(); + + fn tag_html(class: &str, title: &str, contents: &str) -> String { + format!(r#"{}"#, class, Escape(title), contents) + } + + // The trailing space after each tag is to space it properly against the rest of the docs. + if let Some(depr) = &item.deprecation(tcx) { + let mut message = "Deprecated"; + if !stability::deprecation_in_effect( + depr.is_since_rustc_version, + depr.since.map(|s| s.as_str()).as_deref(), + ) { + message = "Deprecation planned"; + } + tags += &tag_html("deprecated", "", message); + } + + // The "rustc_private" crates are permanently unstable so it makes no sense + // to render "unstable" everywhere. + if item + .stability(tcx) + .as_ref() + .map(|s| s.level.is_unstable() && s.feature != sym::rustc_private) + == Some(true) + { + tags += &tag_html("unstable", "", "Experimental"); + } + + let cfg = match (&item.attrs.cfg, parent.attrs.cfg.as_ref()) { + (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg), + (cfg, _) => cfg.as_deref().cloned(), + }; + + debug!("Portability {:?} - {:?} = {:?}", item.attrs.cfg, parent.attrs.cfg, cfg); + if let Some(ref cfg) = cfg { + tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html()); + } + + tags +} + +fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { + let header_len = format!( + "{}{}{}{}{:#}fn {}{:#}", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + f.header.constness.print_with_space(), + f.header.asyncness.print_with_space(), + f.header.unsafety.print_with_space(), + print_abi_with_space(f.header.abi), + it.name.as_ref().unwrap(), + f.generics.print(cx.cache()) + ) + .len(); + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "{vis}{constness}{asyncness}{unsafety}{abi}fn \
    +         {name}{generics}{decl}{spotlight}{where_clause}
    ", + vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + constness = f.header.constness.print_with_space(), + asyncness = f.header.asyncness.print_with_space(), + unsafety = f.header.unsafety.print_with_space(), + abi = print_abi_with_space(f.header.abi), + name = it.name.as_ref().unwrap(), + generics = f.generics.print(cx.cache()), + where_clause = + WhereClause { gens: &f.generics, indent: 0, end_newline: true }.print(cx.cache()), + decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness } + .print(cx.cache()), + spotlight = spotlight_decl(&f.decl, cx.cache()), + ); + document(w, cx, it, None) +} + +fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { + let bounds = bounds(&t.bounds, false, cx.cache()); + let types = t.items.iter().filter(|m| m.is_associated_type()).collect::>(); + let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::>(); + let required = t.items.iter().filter(|m| m.is_ty_method()).collect::>(); + let provided = t.items.iter().filter(|m| m.is_method()).collect::>(); + + // Output the trait definition + wrap_into_docblock(w, |w| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        write!(
    +            w,
    +            "{}{}{}trait {}{}{}",
    +            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    +            t.unsafety.print_with_space(),
    +            if t.is_auto { "auto " } else { "" },
    +            it.name.as_ref().unwrap(),
    +            t.generics.print(cx.cache()),
    +            bounds
    +        );
    +
    +        if !t.generics.where_predicates.is_empty() {
    +            let where_ = WhereClause { gens: &t.generics, indent: 0, end_newline: true };
    +            write!(w, "{}", where_.print(cx.cache()));
    +        } else {
    +            w.write_str(" ");
    +        }
    +
    +        if t.items.is_empty() {
    +            w.write_str("{ }");
    +        } else {
    +            // FIXME: we should be using a derived_id for the Anchors here
    +            w.write_str("{\n");
    +            for t in &types {
    +                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    +                w.write_str(";\n");
    +            }
    +            if !types.is_empty() && !consts.is_empty() {
    +                w.write_str("\n");
    +            }
    +            for t in &consts {
    +                render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    +                w.write_str(";\n");
    +            }
    +            if !consts.is_empty() && !required.is_empty() {
    +                w.write_str("\n");
    +            }
    +            for (pos, m) in required.iter().enumerate() {
    +                render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx);
    +                w.write_str(";\n");
    +
    +                if pos < required.len() - 1 {
    +                    w.write_str("
    "); + } + } + if !required.is_empty() && !provided.is_empty() { + w.write_str("\n"); + } + for (pos, m) in provided.iter().enumerate() { + render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait, cx); + match *m.kind { + clean::MethodItem(ref inner, _) + if !inner.generics.where_predicates.is_empty() => + { + w.write_str(",\n { ... }\n"); + } + _ => { + w.write_str(" { ... }\n"); + } + } + if pos < provided.len() - 1 { + w.write_str("
    "); + } + } + w.write_str("}"); + } + w.write_str("
    ") + }); + + // Trait documentation + document(w, cx, it, None); + + fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { + write!( + w, + "

    \ + {1}\ +

    {2}", + id, title, extra_content + ) + } + + fn write_loading_content(w: &mut Buffer, extra_content: &str) { + write!(w, "{}Loading content...", extra_content) + } + + fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) { + let name = m.name.as_ref().unwrap(); + info!("Documenting {} on {:?}", name, t.name); + let item_type = m.type_(); + let id = cx.derive_id(format!("{}.{}", item_type, name)); + write!(w, "

    ", id = id,); + render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl, cx); + w.write_str(""); + render_stability_since(w, m, t, cx.tcx()); + write_srclink(cx, m, w); + w.write_str("

    "); + document(w, cx, m, Some(t)); + } + + if !types.is_empty() { + write_small_section_header( + w, + "associated-types", + "Associated Types", + "
    ", + ); + for t in types { + trait_item(w, cx, t, it); + } + write_loading_content(w, "
    "); + } + + if !consts.is_empty() { + write_small_section_header( + w, + "associated-const", + "Associated Constants", + "
    ", + ); + for t in consts { + trait_item(w, cx, t, it); + } + write_loading_content(w, "
    "); + } + + // Output the documentation for each function individually + if !required.is_empty() { + write_small_section_header( + w, + "required-methods", + "Required methods", + "
    ", + ); + for m in required { + trait_item(w, cx, m, it); + } + write_loading_content(w, "
    "); + } + if !provided.is_empty() { + write_small_section_header( + w, + "provided-methods", + "Provided methods", + "
    ", + ); + for m in provided { + trait_item(w, cx, m, it); + } + write_loading_content(w, "
    "); + } + + // If there are methods directly on this trait object, render them here. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All); + + if let Some(implementors) = cx.cache.implementors.get(&it.def_id) { + // The DefId is for the first Type found with that name. The bool is + // if any Types with the same name but different DefId have been found. + let mut implementor_dups: FxHashMap = FxHashMap::default(); + for implementor in implementors { + match implementor.inner_impl().for_ { + clean::ResolvedPath { ref path, did, is_generic: false, .. } + | clean::BorrowedRef { + type_: box clean::ResolvedPath { ref path, did, is_generic: false, .. }, + .. + } => { + let &mut (prev_did, ref mut has_duplicates) = + implementor_dups.entry(path.last()).or_insert((did, false)); + if prev_did != did { + *has_duplicates = true; + } + } + _ => {} + } + } + + let (local, foreign) = implementors.iter().partition::, _>(|i| { + i.inner_impl() + .for_ + .def_id_full(cx.cache()) + .map_or(true, |d| cx.cache.paths.contains_key(&d)) + }); + + let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) = + local.iter().partition(|i| i.inner_impl().synthetic); + + synthetic.sort_by(|a, b| compare_impl(a, b, cx.cache())); + concrete.sort_by(|a, b| compare_impl(a, b, cx.cache())); + + if !foreign.is_empty() { + write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", ""); + + for implementor in foreign { + let assoc_link = AssocItemLink::GotoSource( + implementor.impl_item.def_id, + &implementor.inner_impl().provided_trait_methods, + ); + render_impl( + w, + cx, + &implementor, + it, + assoc_link, + RenderMode::Normal, + implementor.impl_item.stable_since(cx.tcx()).as_deref(), + implementor.impl_item.const_stable_since(cx.tcx()).as_deref(), + false, + None, + true, + false, + &[], + ); + } + write_loading_content(w, ""); + } + + write_small_section_header( + w, + "implementors", + "Implementors", + "
    ", + ); + for implementor in concrete { + render_implementor(cx, implementor, it, w, &implementor_dups, &[]); + } + write_loading_content(w, "
    "); + + if t.is_auto { + write_small_section_header( + w, + "synthetic-implementors", + "Auto implementors", + "
    ", + ); + for implementor in synthetic { + render_implementor( + cx, + implementor, + it, + w, + &implementor_dups, + &collect_paths_for_type(implementor.inner_impl().for_.clone(), &cx.cache), + ); + } + write_loading_content(w, "
    "); + } + } else { + // even without any implementations to write in, we still want the heading and list, so the + // implementors javascript file pulled in below has somewhere to write the impls into + write_small_section_header( + w, + "implementors", + "Implementors", + "
    ", + ); + write_loading_content(w, "
    "); + + if t.is_auto { + write_small_section_header( + w, + "synthetic-implementors", + "Auto implementors", + "
    ", + ); + write_loading_content(w, "
    "); + } + } + + write!( + w, + "", + root_path = vec![".."; cx.current.len()].join("/"), + path = if it.def_id.is_local() { + cx.current.join("/") + } else { + let (ref path, _) = cx.cache.external_paths[&it.def_id]; + path[..path.len() - 1].join("/") + }, + ty = it.type_(), + name = *it.name.as_ref().unwrap() + ); +} + +fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TraitAlias) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "trait {}{}{} = {};
    ", + it.name.as_ref().unwrap(), + t.generics.print(cx.cache()), + WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), + bounds(&t.bounds, true, cx.cache()) + ); + + document(w, cx, it, None); + + // Render any items associated directly to this alias, as otherwise they + // won't be visible anywhere in the docs. It would be nice to also show + // associated items from the aliased type (see discussion in #32077), but + // we need #14072 to make sense of the generics. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "type {}{}{where_clause} = impl {bounds};
    ", + it.name.as_ref().unwrap(), + t.generics.print(cx.cache()), + where_clause = + WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), + bounds = bounds(&t.bounds, false, cx.cache()) + ); + + document(w, cx, it, None); + + // Render any items associated directly to this alias, as otherwise they + // won't be visible anywhere in the docs. It would be nice to also show + // associated items from the aliased type (see discussion in #32077), but + // we need #14072 to make sense of the generics. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_typedef(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "type {}{}{where_clause} = {type_};
    ", + it.name.as_ref().unwrap(), + t.generics.print(cx.cache()), + where_clause = + WhereClause { gens: &t.generics, indent: 0, end_newline: true }.print(cx.cache()), + type_ = t.type_.print(cx.cache()) + ); + + document(w, cx, it, None); + + // Render any items associated directly to this alias, as otherwise they + // won't be visible anywhere in the docs. It would be nice to also show + // associated items from the aliased type (see discussion in #32077), but + // we need #14072 to make sense of the generics. + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) { + wrap_into_docblock(w, |w| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        render_union(w, it, Some(&s.generics), &s.fields, "", true, cx);
    +        w.write_str("
    ") + }); + + document(w, cx, it, None); + let mut fields = s + .fields + .iter() + .filter_map(|f| match *f.kind { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable(); + if fields.peek().is_some() { + write!( + w, + "

    + Fields

    " + ); + for (field, ty) in fields { + let name = field.name.as_ref().expect("union field name"); + let id = format!("{}.{}", ItemType::StructField, name); + write!( + w, + "\ + \ + {name}: {ty}\ + ", + id = id, + name = name, + shortty = ItemType::StructField, + ty = ty.print(cx.cache()) + ); + if let Some(stability_class) = field.stability_class(cx.tcx()) { + write!(w, "", stab = stability_class); + } + document(w, cx, field, Some(it)); + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) { + wrap_into_docblock(w, |w| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        write!(
    +            w,
    +            "{}enum {}{}{}",
    +            it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    +            it.name.as_ref().unwrap(),
    +            e.generics.print(cx.cache()),
    +            WhereClause { gens: &e.generics, indent: 0, end_newline: true }.print(cx.cache())
    +        );
    +        if e.variants.is_empty() && !e.variants_stripped {
    +            w.write_str(" {}");
    +        } else {
    +            w.write_str(" {\n");
    +            for v in &e.variants {
    +                w.write_str("    ");
    +                let name = v.name.as_ref().unwrap();
    +                match *v.kind {
    +                    clean::VariantItem(ref var) => match var {
    +                        clean::Variant::CLike => write!(w, "{}", name),
    +                        clean::Variant::Tuple(ref tys) => {
    +                            write!(w, "{}(", name);
    +                            for (i, ty) in tys.iter().enumerate() {
    +                                if i > 0 {
    +                                    w.write_str(", ")
    +                                }
    +                                write!(w, "{}", ty.print(cx.cache()));
    +                            }
    +                            w.write_str(")");
    +                        }
    +                        clean::Variant::Struct(ref s) => {
    +                            render_struct(w, v, None, s.struct_type, &s.fields, "    ", false, cx);
    +                        }
    +                    },
    +                    _ => unreachable!(),
    +                }
    +                w.write_str(",\n");
    +            }
    +
    +            if e.variants_stripped {
    +                w.write_str("    // some variants omitted\n");
    +            }
    +            w.write_str("}");
    +        }
    +        w.write_str("
    ") + }); + + document(w, cx, it, None); + if !e.variants.is_empty() { + write!( + w, + "

    + Variants{}

    \n", + document_non_exhaustive_header(it) + ); + document_non_exhaustive(w, it); + for variant in &e.variants { + let id = + cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.as_ref().unwrap())); + write!( + w, + "
    \ + \ + {name}", + id = id, + name = variant.name.as_ref().unwrap() + ); + if let clean::VariantItem(clean::Variant::Tuple(ref tys)) = *variant.kind { + w.write_str("("); + for (i, ty) in tys.iter().enumerate() { + if i > 0 { + w.write_str(", "); + } + write!(w, "{}", ty.print(cx.cache())); + } + w.write_str(")"); + } + w.write_str("
    "); + document(w, cx, variant, Some(it)); + document_non_exhaustive(w, variant); + + use crate::clean::Variant; + if let clean::VariantItem(Variant::Struct(ref s)) = *variant.kind { + let variant_id = cx.derive_id(format!( + "{}.{}.fields", + ItemType::Variant, + variant.name.as_ref().unwrap() + )); + write!(w, "
    ", id = variant_id); + write!( + w, + "

    Fields of {name}

    ", + name = variant.name.as_ref().unwrap() + ); + for field in &s.fields { + use crate::clean::StructFieldItem; + if let StructFieldItem(ref ty) = *field.kind { + let id = cx.derive_id(format!( + "variant.{}.field.{}", + variant.name.as_ref().unwrap(), + field.name.as_ref().unwrap() + )); + write!( + w, + "\ + \ + {f}: {t}\ + ", + id = id, + f = field.name.as_ref().unwrap(), + t = ty.print(cx.cache()) + ); + document(w, cx, field, Some(variant)); + } + } + w.write_str("
    "); + } + render_stability_since(w, variant, it, cx.tcx()); + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) { + wrap_into_docblock(w, |w| { + highlight::render_with_highlighting( + &t.source, + w, + Some("macro"), + None, + None, + it.source.span().edition(), + ); + }); + document(w, cx, it, None) +} + +fn item_proc_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, m: &clean::ProcMacro) { + let name = it.name.as_ref().expect("proc-macros always have names"); + match m.kind { + MacroKind::Bang => { + w.push_str("
    ");
    +            write!(w, "{}!() {{ /* proc-macro */ }}", name);
    +            w.push_str("
    "); + } + MacroKind::Attr => { + w.push_str("
    ");
    +            write!(w, "#[{}]", name);
    +            w.push_str("
    "); + } + MacroKind::Derive => { + w.push_str("
    ");
    +            write!(w, "#[derive({})]", name);
    +            if !m.helpers.is_empty() {
    +                w.push_str("\n{\n");
    +                w.push_str("    // Attributes available to this derive:\n");
    +                for attr in &m.helpers {
    +                    writeln!(w, "    #[{}]", attr);
    +                }
    +                w.push_str("}\n");
    +            }
    +            w.push_str("
    "); + } + } + document(w, cx, it, None) +} + +fn item_primitive(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { + document(w, cx, it, None); + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::Constant) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +
    +    write!(
    +        w,
    +        "{vis}const {name}: {typ}",
    +        vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()),
    +        name = it.name.as_ref().unwrap(),
    +        typ = c.type_.print(cx.cache()),
    +    );
    +
    +    if c.value.is_some() || c.is_literal {
    +        write!(w, " = {expr};", expr = Escape(&c.expr));
    +    } else {
    +        w.write_str(";");
    +    }
    +
    +    if let Some(value) = &c.value {
    +        if !c.is_literal {
    +            let value_lowercase = value.to_lowercase();
    +            let expr_lowercase = c.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("
    "); + document(w, cx, it, None) +} + +fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) { + wrap_into_docblock(w, |w| { + w.write_str("
    ");
    +        render_attributes(w, it, true);
    +        render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
    +        w.write_str("
    ") + }); + + document(w, cx, it, None); + let mut fields = s + .fields + .iter() + .filter_map(|f| match *f.kind { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, + }) + .peekable(); + if let CtorKind::Fictive = s.struct_type { + if fields.peek().is_some() { + write!( + w, + "

    + Fields{}

    ", + document_non_exhaustive_header(it) + ); + document_non_exhaustive(w, it); + for (field, ty) in fields { + let id = cx.derive_id(format!( + "{}.{}", + ItemType::StructField, + field.name.as_ref().unwrap() + )); + write!( + w, + "\ + \ + {name}: {ty}\ + ", + item_type = ItemType::StructField, + id = id, + name = field.name.as_ref().unwrap(), + ty = ty.print(cx.cache()) + ); + document(w, cx, field, Some(it)); + } + } + } + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Static) { + w.write_str("
    ");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "{vis}static {mutability}{name}: {typ}
    ", + vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + mutability = s.mutability.print_with_space(), + name = it.name.as_ref().unwrap(), + typ = s.type_.print(cx.cache()) + ); + document(w, cx, it, None) +} + +fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { + w.write_str("
    extern {\n");
    +    render_attributes(w, it, false);
    +    write!(
    +        w,
    +        "    {}type {};\n}}
    ", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.name.as_ref().unwrap(), + ); + + document(w, cx, it, None); + + render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) +} + +fn item_keyword(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { + document(w, cx, it, None) +} + +/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). +crate fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering { + /// Takes a non-numeric and a numeric part from the given &str. + fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) { + let i = s.find(|c: char| c.is_ascii_digit()); + let (a, b) = s.split_at(i.unwrap_or(s.len())); + let i = b.find(|c: char| !c.is_ascii_digit()); + let (b, c) = b.split_at(i.unwrap_or(b.len())); + *s = c; + (a, b) + } + + while !lhs.is_empty() || !rhs.is_empty() { + let (la, lb) = take_parts(&mut lhs); + let (ra, rb) = take_parts(&mut rhs); + // First process the non-numeric part. + match la.cmp(ra) { + Ordering::Equal => (), + x => return x, + } + // Then process the numeric part, if both sides have one (and they fit in a u64). + if let (Ok(ln), Ok(rn)) = (lb.parse::(), rb.parse::()) { + match ln.cmp(&rn) { + Ordering::Equal => (), + x => return x, + } + } + // Then process the numeric part again, but this time as strings. + match lb.cmp(rb) { + Ordering::Equal => (), + x => return x, + } + } + + Ordering::Equal +} + +pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String { + let mut s = cx.current.join("::"); + s.push_str("::"); + s.push_str(&item.name.unwrap().as_str()); + s +} + +pub(super) fn item_path(ty: ItemType, name: &str) -> String { + match ty { + ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)), + _ => format!("{}.{}.html", ty, name), + } +} + +fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cache: &Cache) -> String { + let mut bounds = String::new(); + if !t_bounds.is_empty() { + if !trait_alias { + bounds.push_str(": "); + } + for (i, p) in t_bounds.iter().enumerate() { + if i > 0 { + bounds.push_str(" + "); + } + bounds.push_str(&p.print(cache).to_string()); + } + } + bounds +} + +fn wrap_into_docblock(w: &mut Buffer, f: F) +where + F: FnOnce(&mut Buffer), +{ + w.write_str("
    "); + f(w); + w.write_str("
    ") +} + +fn render_stability_since( + w: &mut Buffer, + item: &clean::Item, + containing_item: &clean::Item, + tcx: TyCtxt<'_>, +) { + render_stability_since_raw( + w, + item.stable_since(tcx).as_deref(), + item.const_stable_since(tcx).as_deref(), + containing_item.stable_since(tcx).as_deref(), + containing_item.const_stable_since(tcx).as_deref(), + ) +} + +fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cache: &Cache) -> Ordering { + let lhs = format!("{}", lhs.inner_impl().print(cache, false)); + let rhs = format!("{}", rhs.inner_impl().print(cache, false)); + + // lhs and rhs are formatted as HTML, which may be unnecessary + compare_names(&lhs, &rhs) +} + +fn render_implementor( + cx: &Context<'_>, + implementor: &Impl, + trait_: &clean::Item, + w: &mut Buffer, + implementor_dups: &FxHashMap, + aliases: &[String], +) { + // If there's already another implementor that has the same abbridged name, use the + // full path, for example in `std::iter::ExactSizeIterator` + let use_absolute = match implementor.inner_impl().for_ { + clean::ResolvedPath { ref path, is_generic: false, .. } + | clean::BorrowedRef { + type_: box clean::ResolvedPath { ref path, is_generic: false, .. }, + .. + } => implementor_dups[&path.last()].1, + _ => false, + }; + render_impl( + w, + cx, + implementor, + trait_, + AssocItemLink::Anchor(None), + RenderMode::Normal, + trait_.stable_since(cx.tcx()).as_deref(), + trait_.const_stable_since(cx.tcx()).as_deref(), + false, + Some(use_absolute), + false, + false, + aliases, + ); +} + +fn render_union( + w: &mut Buffer, + it: &clean::Item, + g: Option<&clean::Generics>, + fields: &[clean::Item], + tab: &str, + structhead: bool, + cx: &Context<'_>, +) { + write!( + w, + "{}{}{}", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + if structhead { "union " } else { "" }, + it.name.as_ref().unwrap() + ); + if let Some(g) = g { + write!(w, "{}", g.print(cx.cache())); + write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache())); + } + + write!(w, " {{\n{}", tab); + for field in fields { + if let clean::StructFieldItem(ref ty) = *field.kind { + write!( + w, + " {}{}: {},\n{}", + field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + field.name.as_ref().unwrap(), + ty.print(cx.cache()), + tab + ); + } + } + + if it.has_stripped_fields().unwrap() { + write!(w, " // some fields omitted\n{}", tab); + } + w.write_str("}"); +} + +fn render_struct( + w: &mut Buffer, + it: &clean::Item, + g: Option<&clean::Generics>, + ty: CtorKind, + fields: &[clean::Item], + tab: &str, + structhead: bool, + cx: &Context<'_>, +) { + write!( + w, + "{}{}{}", + it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + if structhead { "struct " } else { "" }, + it.name.as_ref().unwrap() + ); + if let Some(g) = g { + write!(w, "{}", g.print(cx.cache())) + } + match ty { + CtorKind::Fictive => { + if let Some(g) = g { + write!( + w, + "{}", + WhereClause { gens: g, indent: 0, end_newline: true }.print(cx.cache()) + ) + } + let mut has_visible_fields = false; + w.write_str(" {"); + for field in fields { + if let clean::StructFieldItem(ref ty) = *field.kind { + write!( + w, + "\n{} {}{}: {},", + tab, + field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + field.name.as_ref().unwrap(), + ty.print(cx.cache()) + ); + has_visible_fields = true; + } + } + + if has_visible_fields { + if it.has_stripped_fields().unwrap() { + write!(w, "\n{} // some fields omitted", tab); + } + write!(w, "\n{}", tab); + } else if it.has_stripped_fields().unwrap() { + // If there are no visible fields we can just display + // `{ /* fields omitted */ }` to save space. + write!(w, " /* fields omitted */ "); + } + w.write_str("}"); + } + CtorKind::Fn => { + w.write_str("("); + for (i, field) in fields.iter().enumerate() { + if i > 0 { + w.write_str(", "); + } + match *field.kind { + clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), + clean::StructFieldItem(ref ty) => { + write!( + w, + "{}{}", + field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + ty.print(cx.cache()) + ) + } + _ => unreachable!(), + } + } + w.write_str(")"); + if let Some(g) = g { + write!( + w, + "{}", + WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) + ) + } + w.write_str(";"); + } + CtorKind::Const => { + // Needed for PhantomData. + if let Some(g) = g { + write!( + w, + "{}", + WhereClause { gens: g, indent: 0, end_newline: false }.print(cx.cache()) + ) + } + w.write_str(";"); + } + } +} + +fn document_non_exhaustive_header(item: &clean::Item) -> &str { + if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" } +} + +fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) { + if item.is_non_exhaustive() { + write!(w, "
    ", { + if item.is_struct() { + "struct" + } else if item.is_enum() { + "enum" + } else if item.is_variant() { + "variant" + } else { + "type" + } + }); + + if item.is_struct() { + w.write_str( + "Non-exhaustive structs could have additional fields added in future. \ + Therefore, non-exhaustive structs cannot be constructed in external crates \ + using the traditional Struct {{ .. }} syntax; cannot be \ + matched against without a wildcard ..; and \ + struct update syntax will not work.", + ); + } else if item.is_enum() { + w.write_str( + "Non-exhaustive enums could have additional variants added in future. \ + Therefore, when matching against variants of non-exhaustive enums, an \ + extra wildcard arm must be added to account for any future variants.", + ); + } else if item.is_variant() { + w.write_str( + "Non-exhaustive enum variants could have additional fields added in future. \ + Therefore, non-exhaustive enum variants cannot be constructed in external \ + crates and cannot be matched against.", + ); + } else { + w.write_str( + "This type will require a wildcard arm in any match statements or constructors.", + ); + } + + w.write_str("
    "); + } +} diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index 224c794fb3b4a..3175fbe5666d8 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -1,4 +1,7 @@ -use super::*; +use std::cmp::Ordering; + +use super::print_item::compare_names; +use super::{AllTypes, Buffer}; #[test] fn test_compare_names() { diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs new file mode 100644 index 0000000000000..cbf0f9a4927c6 --- /dev/null +++ b/src/librustdoc/html/render/write_shared.rs @@ -0,0 +1,542 @@ +use std::ffi::OsStr; +use std::fmt::Write; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::io::{self, BufReader}; +use std::path::{Component, Path, PathBuf}; + +use itertools::Itertools; +use rustc_data_structures::flock; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use serde::Serialize; + +use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS}; +use crate::clean::Crate; +use crate::config::RenderOptions; +use crate::docfs::{DocFS, PathError}; +use crate::error::Error; +use crate::formats::FormatRenderer; +use crate::html::{layout, static_files}; + +pub(super) fn write_shared( + cx: &Context<'_>, + krate: &Crate, + search_index: String, + options: &RenderOptions, +) -> Result<(), Error> { + // Write out the shared files. Note that these are shared among all rustdoc + // docs placed in the output directory, so this needs to be a synchronized + // operation with respect to all other rustdocs running around. + let lock_file = cx.dst.join(".lock"); + let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file); + + // Add all the static files. These may already exist, but we just + // overwrite them anyway to make sure that they're fresh and up-to-date. + + write_minify( + &cx.shared.fs, + cx.path("rustdoc.css"), + static_files::RUSTDOC_CSS, + options.enable_minification, + )?; + write_minify( + &cx.shared.fs, + cx.path("settings.css"), + static_files::SETTINGS_CSS, + options.enable_minification, + )?; + write_minify( + &cx.shared.fs, + cx.path("noscript.css"), + static_files::NOSCRIPT_CSS, + options.enable_minification, + )?; + + // To avoid "light.css" to be overwritten, we'll first run over the received themes and only + // then we'll run over the "official" styles. + let mut themes: FxHashSet = FxHashSet::default(); + + for entry in &cx.shared.style_files { + let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path); + let extension = + try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); + + // Handle the official themes + match theme { + "light" => write_minify( + &cx.shared.fs, + cx.path("light.css"), + static_files::themes::LIGHT, + options.enable_minification, + )?, + "dark" => write_minify( + &cx.shared.fs, + cx.path("dark.css"), + static_files::themes::DARK, + options.enable_minification, + )?, + "ayu" => write_minify( + &cx.shared.fs, + cx.path("ayu.css"), + static_files::themes::AYU, + options.enable_minification, + )?, + _ => { + // Handle added third-party themes + let content = try_err!(fs::read(&entry.path), &entry.path); + cx.shared + .fs + .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?; + } + }; + + themes.insert(theme.to_owned()); + } + + let write = |p, c| cx.shared.fs.write(p, c); + if (*cx.shared).layout.logo.is_empty() { + write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?; + } + if (*cx.shared).layout.favicon.is_empty() { + write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?; + write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?; + write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?; + } + write(cx.path("brush.svg"), static_files::BRUSH_SVG)?; + write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?; + write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?; + + let mut themes: Vec<&String> = themes.iter().collect(); + themes.sort(); + // To avoid theme switch latencies as much as possible, we put everything theme related + // at the beginning of the html files into another js file. + let theme_js = format!( + r#"var themes = document.getElementById("theme-choices"); +var themePicker = document.getElementById("theme-picker"); + +function showThemeButtonState() {{ + themes.style.display = "block"; + themePicker.style.borderBottomRightRadius = "0"; + themePicker.style.borderBottomLeftRadius = "0"; +}} + +function hideThemeButtonState() {{ + themes.style.display = "none"; + themePicker.style.borderBottomRightRadius = "3px"; + themePicker.style.borderBottomLeftRadius = "3px"; +}} + +function switchThemeButtonState() {{ + if (themes.style.display === "block") {{ + hideThemeButtonState(); + }} else {{ + showThemeButtonState(); + }} +}}; + +function handleThemeButtonsBlur(e) {{ + var active = document.activeElement; + var related = e.relatedTarget; + + if (active.id !== "theme-picker" && + (!active.parentNode || active.parentNode.id !== "theme-choices") && + (!related || + (related.id !== "theme-picker" && + (!related.parentNode || related.parentNode.id !== "theme-choices")))) {{ + hideThemeButtonState(); + }} +}} + +themePicker.onclick = switchThemeButtonState; +themePicker.onblur = handleThemeButtonsBlur; +{}.forEach(function(item) {{ + var but = document.createElement("button"); + but.textContent = item; + but.onclick = function(el) {{ + switchTheme(currentTheme, mainTheme, item, true); + useSystemTheme(false); + }}; + but.onblur = handleThemeButtonsBlur; + themes.appendChild(but); +}});"#, + serde_json::to_string(&themes).unwrap() + ); + + write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?; + write_minify( + &cx.shared.fs, + cx.path("main.js"), + static_files::MAIN_JS, + options.enable_minification, + )?; + write_minify( + &cx.shared.fs, + cx.path("settings.js"), + static_files::SETTINGS_JS, + options.enable_minification, + )?; + if cx.shared.include_sources { + write_minify( + &cx.shared.fs, + cx.path("source-script.js"), + static_files::sidebar::SOURCE_SCRIPT, + options.enable_minification, + )?; + } + + { + write_minify( + &cx.shared.fs, + cx.path("storage.js"), + &format!( + "var resourcesSuffix = \"{}\";{}", + cx.shared.resource_suffix, + static_files::STORAGE_JS + ), + options.enable_minification, + )?; + } + + if let Some(ref css) = cx.shared.layout.css_file_extension { + let out = cx.path("theme.css"); + let buffer = try_err!(fs::read_to_string(css), css); + if !options.enable_minification { + cx.shared.fs.write(&out, &buffer)?; + } else { + write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?; + } + } + write_minify( + &cx.shared.fs, + cx.path("normalize.css"), + static_files::NORMALIZE_CSS, + options.enable_minification, + )?; + write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?; + write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?; + write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?; + write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?; + write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?; + write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?; + write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?; + write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?; + write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?; + write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?; + write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?; + write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?; + write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?; + write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?; + write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?; + + fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec, Vec)> { + let mut ret = Vec::new(); + let mut krates = Vec::new(); + + if path.exists() { + let prefix = format!(r#"{}["{}"]"#, key, krate); + for line in BufReader::new(File::open(path)?).lines() { + let line = line?; + if !line.starts_with(key) { + continue; + } + if line.starts_with(&prefix) { + continue; + } + ret.push(line.to_string()); + krates.push( + line[key.len() + 2..] + .split('"') + .next() + .map(|s| s.to_owned()) + .unwrap_or_else(String::new), + ); + } + } + Ok((ret, krates)) + } + + fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> { + let mut ret = Vec::new(); + let mut krates = Vec::new(); + + if path.exists() { + let prefix = format!("\"{}\"", krate); + for line in BufReader::new(File::open(path)?).lines() { + let line = line?; + if !line.starts_with('"') { + continue; + } + if line.starts_with(&prefix) { + continue; + } + if line.ends_with(",\\") { + ret.push(line[..line.len() - 2].to_string()); + } else { + // Ends with "\\" (it's the case for the last added crate line) + ret.push(line[..line.len() - 1].to_string()); + } + krates.push( + line.split('"') + .find(|s| !s.is_empty()) + .map(|s| s.to_owned()) + .unwrap_or_else(String::new), + ); + } + } + Ok((ret, krates)) + } + + use std::ffi::OsString; + + #[derive(Debug)] + struct Hierarchy { + elem: OsString, + children: FxHashMap, + elems: FxHashSet, + } + + impl Hierarchy { + fn new(elem: OsString) -> Hierarchy { + Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() } + } + + fn to_json_string(&self) -> String { + let mut subs: Vec<&Hierarchy> = self.children.values().collect(); + subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem)); + let mut files = self + .elems + .iter() + .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion"))) + .collect::>(); + files.sort_unstable(); + let subs = subs.iter().map(|s| s.to_json_string()).collect::>().join(","); + let dirs = + if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) }; + let files = files.join(","); + let files = + if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) }; + format!( + "{{\"name\":\"{name}\"{dirs}{files}}}", + name = self.elem.to_str().expect("invalid osstring conversion"), + dirs = dirs, + files = files + ) + } + } + + if cx.shared.include_sources { + let mut hierarchy = Hierarchy::new(OsString::new()); + for source in cx + .shared + .local_sources + .iter() + .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok()) + { + let mut h = &mut hierarchy; + let mut elems = source + .components() + .filter_map(|s| match s { + Component::Normal(s) => Some(s.to_owned()), + _ => None, + }) + .peekable(); + loop { + let cur_elem = elems.next().expect("empty file path"); + if elems.peek().is_none() { + h.elems.insert(cur_elem); + break; + } else { + let e = cur_elem.clone(); + h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e)); + } + } + } + + let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix)); + let (mut all_sources, _krates) = + try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst); + all_sources.push(format!( + "sourcesIndex[\"{}\"] = {};", + &krate.name, + hierarchy.to_json_string() + )); + all_sources.sort(); + let v = format!( + "var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n", + all_sources.join("\n") + ); + cx.shared.fs.write(&dst, v.as_bytes())?; + } + + // Update the search index and crate list. + let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix)); + let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst); + all_indexes.push(search_index); + krates.push(krate.name.to_string()); + krates.sort(); + + // Sort the indexes by crate so the file will be generated identically even + // with rustdoc running in parallel. + all_indexes.sort(); + { + let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); + v.push_str(&all_indexes.join(",\\\n")); + v.push_str("\\\n}');\ninitSearch(searchIndex);"); + cx.shared.fs.write(&dst, &v)?; + } + + let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix)); + let crate_list = + format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(",")); + cx.shared.fs.write(&crate_list_dst, &crate_list)?; + + if options.enable_index_page { + if let Some(index_page) = options.index_page.clone() { + let mut md_opts = options.clone(); + md_opts.output = cx.dst.clone(); + md_opts.external_html = (*cx.shared).layout.external_html.clone(); + + crate::markdown::render(&index_page, md_opts, cx.shared.edition) + .map_err(|e| Error::new(e, &index_page))?; + } else { + let dst = cx.dst.join("index.html"); + let page = layout::Page { + title: "Index of crates", + css_class: "mod", + root_path: "./", + static_root_path: cx.shared.static_root_path.as_deref(), + description: "List of crates", + keywords: BASIC_KEYWORDS, + resource_suffix: &cx.shared.resource_suffix, + extra_scripts: &[], + static_extra_scripts: &[], + }; + + let content = format!( + "

    \ + List of all crates\ +

      {}
    ", + krates + .iter() + .map(|s| { + format!( + "
  • {}
  • ", + ensure_trailing_slash(s), + s + ) + }) + .collect::() + ); + let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files); + cx.shared.fs.write(&dst, v.as_bytes())?; + } + } + + // Update the list of all implementors for traits + let dst = cx.dst.join("implementors"); + for (&did, imps) in &cx.cache.implementors { + // Private modules can leak through to this phase of rustdoc, which + // could contain implementations for otherwise private types. In some + // rare cases we could find an implementation for an item which wasn't + // indexed, so we just skip this step in that case. + // + // FIXME: this is a vague explanation for why this can't be a `get`, in + // theory it should be... + let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) { + Some(p) => p, + None => match cx.cache.external_paths.get(&did) { + Some(p) => p, + None => continue, + }, + }; + + #[derive(Serialize)] + struct Implementor { + text: String, + synthetic: bool, + types: Vec, + } + + let implementors = imps + .iter() + .filter_map(|imp| { + // If the trait and implementation are in the same crate, then + // there's no need to emit information about it (there's inlining + // going on). If they're in different crates then the crate defining + // the trait will be interested in our implementation. + // + // If the implementation is from another crate then that crate + // should add it. + if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() { + None + } else { + Some(Implementor { + text: imp.inner_impl().print(cx.cache(), false).to_string(), + synthetic: imp.inner_impl().synthetic, + types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()), + }) + } + }) + .collect::>(); + + // Only create a js file if we have impls to add to it. If the trait is + // documented locally though we always create the file to avoid dead + // links. + if implementors.is_empty() && !cx.cache.paths.contains_key(&did) { + continue; + } + + let implementors = format!( + r#"implementors["{}"] = {};"#, + krate.name, + serde_json::to_string(&implementors).unwrap() + ); + + let mut mydst = dst.clone(); + for part in &remote_path[..remote_path.len() - 1] { + mydst.push(part); + } + cx.shared.ensure_dir(&mydst)?; + mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1])); + + let (mut all_implementors, _) = + try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst); + all_implementors.push(implementors); + // Sort the implementors by crate so the file will be generated + // identically even with rustdoc running in parallel. + all_implementors.sort(); + + let mut v = String::from("(function() {var implementors = {};\n"); + for implementor in &all_implementors { + writeln!(v, "{}", *implementor).unwrap(); + } + v.push_str( + "if (window.register_implementors) {\ + window.register_implementors(implementors);\ + } else {\ + window.pending_implementors = implementors;\ + }", + ); + v.push_str("})()"); + cx.shared.fs.write(&mydst, &v)?; + } + Ok(()) +} + +fn write_minify( + fs: &DocFS, + dst: PathBuf, + contents: &str, + enable_minification: bool, +) -> Result<(), Error> { + if enable_minification { + if dst.extension() == Some(&OsStr::new("css")) { + let res = try_none!(minifier::css::minify(contents).ok(), &dst); + fs.write(dst, res.as_bytes()) + } else { + fs.write(dst, minifier::js::minify(contents).as_bytes()) + } + } else { + fs.write(dst, contents.as_bytes()) + } +} diff --git a/src/librustdoc/passes/doc_test_lints.rs b/src/librustdoc/passes/doc_test_lints.rs index 81104236314d9..ede9e18a511a8 100644 --- a/src/librustdoc/passes/doc_test_lints.rs +++ b/src/librustdoc/passes/doc_test_lints.rs @@ -9,8 +9,10 @@ use crate::clean::*; use crate::core::DocContext; use crate::fold::DocFolder; use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString}; +use crate::visit_ast::inherits_doc_hidden; use rustc_middle::lint::LintLevelSource; use rustc_session::lint; +use rustc_span::symbol::sym; crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass { name: "check-private-items-doc-tests", @@ -51,23 +53,30 @@ impl crate::doctest::Tester for Tests { } crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool { - if matches!( - *item.kind, - clean::StructFieldItem(_) - | clean::VariantItem(_) - | clean::AssocConstItem(_, _) - | clean::AssocTypeItem(_, _) - | clean::TypedefItem(_, _) - | clean::StaticItem(_) - | clean::ConstantItem(_) - | clean::ExternCrateItem(_, _) - | clean::ImportItem(_) - | clean::PrimitiveItem(_) - | clean::KeywordItem(_) - ) { + if !cx.cache.access_levels.is_public(item.def_id) + || matches!( + *item.kind, + clean::StructFieldItem(_) + | clean::VariantItem(_) + | clean::AssocConstItem(_, _) + | clean::AssocTypeItem(_, _) + | clean::TypedefItem(_, _) + | clean::StaticItem(_) + | clean::ConstantItem(_) + | clean::ExternCrateItem(_, _) + | clean::ImportItem(_) + | clean::PrimitiveItem(_) + | clean::KeywordItem(_) + ) + { return false; } let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id.expect_local()); + if cx.tcx.hir().attrs(hir_id).lists(sym::doc).has_word(sym::hidden) + || inherits_doc_hidden(cx.tcx, hir_id) + { + return false; + } let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id); level != lint::Level::Allow || matches!(source, LintLevelSource::Default) } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 5da7d2f1e9b84..ba6bb359b9135 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -29,6 +29,16 @@ fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec { std::iter::once(crate_name).chain(relative).collect() } +crate fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool { + while let Some(id) = tcx.hir().get_enclosing_scope(node) { + node = id; + if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) { + return true; + } + } + false +} + // Also, is there some reason that this doesn't use the 'visit' // framework from syntax?. @@ -158,19 +168,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { om: &mut Module<'tcx>, please_inline: bool, ) -> bool { - fn inherits_doc_hidden(cx: &core::DocContext<'_>, mut node: hir::HirId) -> bool { - while let Some(id) = cx.tcx.hir().get_enclosing_scope(node) { - node = id; - if cx.tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) { - return true; - } - if node == hir::CRATE_HIR_ID { - break; - } - } - false - } - debug!("maybe_inline_local res: {:?}", res); let tcx = self.cx.tcx; @@ -212,7 +209,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { }; let is_private = !self.cx.cache.access_levels.is_public(res_did); - let is_hidden = inherits_doc_hidden(self.cx, res_hir_id); + let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id); // Only inline if requested or if the item would otherwise be stripped. if (!please_inline && !is_private && !is_hidden) || is_no_inline { diff --git a/src/test/rustdoc-ui/lint-missing-doc-code-example.rs b/src/test/rustdoc-ui/lint-missing-doc-code-example.rs index 8d727b0d0b550..41e8847792694 100644 --- a/src/test/rustdoc-ui/lint-missing-doc-code-example.rs +++ b/src/test/rustdoc-ui/lint-missing-doc-code-example.rs @@ -12,16 +12,16 @@ /// ``` /// println!("hello"); /// ``` -fn test() { +pub fn test() { } #[allow(missing_docs)] -mod module1 { //~ ERROR +pub mod module1 { //~ ERROR } #[allow(rustdoc::missing_doc_code_examples)] /// doc -mod module2 { +pub mod module2 { /// doc pub fn test() {} @@ -63,9 +63,22 @@ pub enum Enum { /// Doc //~^ ERROR #[repr(C)] -union Union { +pub union Union { /// Doc, but no code example and it's fine! a: i32, /// Doc, but no code example and it's fine! b: f32, } + + +#[doc(hidden)] +pub mod foo { + pub fn bar() {} +} + +fn babar() {} + + +mod fofoo { + pub fn tadam() {} +} diff --git a/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr b/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr index 370c577f85d8f..3715797854281 100644 --- a/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr +++ b/src/test/rustdoc-ui/lint-missing-doc-code-example.stderr @@ -1,7 +1,7 @@ error: missing code example in this documentation --> $DIR/lint-missing-doc-code-example.rs:19:1 | -LL | / mod module1 { +LL | / pub mod module1 { LL | | } | |_^ | diff --git a/src/test/rustdoc/extern-links.rs b/src/test/rustdoc/extern-links.rs index 991f869138d93..0383ccf7db666 100644 --- a/src/test/rustdoc/extern-links.rs +++ b/src/test/rustdoc/extern-links.rs @@ -3,7 +3,7 @@ #![crate_name = "foo"] -extern crate extern_links; +pub extern crate extern_links; // @!has foo/index.html '//a' 'extern_links' #[doc(no_inline)] diff --git a/src/test/rustdoc/issue-28927.rs b/src/test/rustdoc/issue-28927.rs index 7b535f33bf7e3..38a520850b6dd 100644 --- a/src/test/rustdoc/issue-28927.rs +++ b/src/test/rustdoc/issue-28927.rs @@ -2,5 +2,5 @@ // aux-build:issue-28927-1.rs // ignore-cross-compile -extern crate issue_28927_1 as inner1; +pub extern crate issue_28927_1 as inner1; pub use inner1 as foo; diff --git a/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.rs b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.rs new file mode 100644 index 0000000000000..934870afc119c --- /dev/null +++ b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.rs @@ -0,0 +1,21 @@ +// Regression test for #81712. + +#![feature(generic_associated_types)] +#![allow(incomplete_features)] + +trait A { + type BType: B; +} + +trait B { + type AType: A; +} +trait C { + type DType: D; + //~^ ERROR: missing generics for associated type `C::DType` [E0107] +} +trait D { + type CType: C; +} + +fn main() {} diff --git a/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.stderr b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.stderr new file mode 100644 index 0000000000000..75f68cd314893 --- /dev/null +++ b/src/test/ui/generic-associated-types/issue-81712-cyclic-traits.stderr @@ -0,0 +1,19 @@ +error[E0107]: missing generics for associated type `C::DType` + --> $DIR/issue-81712-cyclic-traits.rs:14:10 + | +LL | type DType: D; + | ^^^^^ expected 1 type argument + | +note: associated type defined here, with 1 type parameter: `T` + --> $DIR/issue-81712-cyclic-traits.rs:14:10 + | +LL | type DType: D; + | ^^^^^ - +help: use angle brackets to add missing type argument + | +LL | type DType: D; + | ^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0107`. diff --git a/src/test/ui/pub/pub-reexport-priv-extern-crate.rs b/src/test/ui/pub/pub-reexport-priv-extern-crate.rs index e95d6924026ca..dd5cd420fa546 100644 --- a/src/test/ui/pub/pub-reexport-priv-extern-crate.rs +++ b/src/test/ui/pub/pub-reexport-priv-extern-crate.rs @@ -1,5 +1,3 @@ -#![allow(unused)] - extern crate core; pub use core as reexported_core; //~ ERROR `core` is private, and cannot be re-exported //~^ WARN this was previously accepted @@ -9,16 +7,14 @@ mod foo1 { } mod foo2 { - use foo1::core; //~ ERROR `core` is private, and cannot be re-exported - //~^ WARN this was previously accepted + use foo1::core; //~ ERROR crate import `core` is private pub mod bar { extern crate core; } } mod baz { - pub use foo2::bar::core; //~ ERROR `core` is private, and cannot be re-exported - //~^ WARN this was previously accepted + pub use foo2::bar::core; //~ ERROR crate import `core` is private } fn main() {} diff --git a/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr b/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr index 0b44c5a6525f6..e4d73c6475dc4 100644 --- a/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr +++ b/src/test/ui/pub/pub-reexport-priv-extern-crate.stderr @@ -1,30 +1,37 @@ -error: extern crate `core` is private, and cannot be re-exported (error E0365), consider declaring with `pub` - --> $DIR/pub-reexport-priv-extern-crate.rs:4:9 +error[E0603]: crate import `core` is private + --> $DIR/pub-reexport-priv-extern-crate.rs:10:15 | -LL | pub use core as reexported_core; - | ^^^^^^^^^^^^^^^^^^^^^^^ +LL | use foo1::core; + | ^^^^ private crate import | - = note: `#[deny(pub_use_of_private_extern_crate)]` on by default - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #34537 +note: the crate import `core` is defined here + --> $DIR/pub-reexport-priv-extern-crate.rs:6:5 + | +LL | extern crate core; + | ^^^^^^^^^^^^^^^^^^ -error: extern crate `core` is private, and cannot be re-exported (error E0365), consider declaring with `pub` - --> $DIR/pub-reexport-priv-extern-crate.rs:12:9 +error[E0603]: crate import `core` is private + --> $DIR/pub-reexport-priv-extern-crate.rs:17:24 | -LL | use foo1::core; - | ^^^^^^^^^^ +LL | pub use foo2::bar::core; + | ^^^^ private crate import | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #34537 +note: the crate import `core` is defined here + --> $DIR/pub-reexport-priv-extern-crate.rs:12:9 + | +LL | extern crate core; + | ^^^^^^^^^^^^^^^^^^ error: extern crate `core` is private, and cannot be re-exported (error E0365), consider declaring with `pub` - --> $DIR/pub-reexport-priv-extern-crate.rs:20:13 + --> $DIR/pub-reexport-priv-extern-crate.rs:2:9 | -LL | pub use foo2::bar::core; - | ^^^^^^^^^^^^^^^ +LL | pub use core as reexported_core; + | ^^^^^^^^^^^^^^^^^^^^^^^ | + = note: `#[deny(pub_use_of_private_extern_crate)]` on by default = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #34537 error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0603`.