From 4547b622d8d29df964fa2914213088b148c498fc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:18:32 +0200 Subject: Merging upstream version 1.67.1+dfsg1. Signed-off-by: Daniel Baumann --- src/librustdoc/Cargo.toml | 5 +- src/librustdoc/clean/auto_trait.rs | 28 +- src/librustdoc/clean/blanket_impl.rs | 12 +- src/librustdoc/clean/cfg.rs | 2 +- src/librustdoc/clean/inline.rs | 55 +- src/librustdoc/clean/mod.rs | 635 +++++++++++++-------- src/librustdoc/clean/simplify.rs | 17 +- src/librustdoc/clean/types.rs | 156 +++-- src/librustdoc/clean/utils.rs | 52 +- src/librustdoc/config.rs | 24 +- src/librustdoc/core.rs | 7 +- src/librustdoc/doctest.rs | 21 +- src/librustdoc/formats/cache.rs | 50 +- src/librustdoc/html/format.rs | 168 +++--- src/librustdoc/html/highlight.rs | 6 +- src/librustdoc/html/highlight/fixtures/sample.html | 8 +- src/librustdoc/html/highlight/tests.rs | 2 +- src/librustdoc/html/layout.rs | 25 +- src/librustdoc/html/render/context.rs | 33 +- src/librustdoc/html/render/mod.rs | 265 +++++---- src/librustdoc/html/render/print_item.rs | 174 +++--- src/librustdoc/html/render/span_map.rs | 10 +- src/librustdoc/html/render/write_shared.rs | 290 ++-------- src/librustdoc/html/sources.rs | 25 +- src/librustdoc/html/static/css/noscript.css | 6 + src/librustdoc/html/static/css/rustdoc.css | 526 ++++++++--------- src/librustdoc/html/static/css/settings.css | 57 +- src/librustdoc/html/static/css/themes/ayu.css | 126 ++-- src/librustdoc/html/static/css/themes/dark.css | 122 +--- src/librustdoc/html/static/css/themes/light.css | 116 +--- src/librustdoc/html/static/fonts/README.txt | 12 + src/librustdoc/html/static/js/main.js | 184 +++++- src/librustdoc/html/static/js/scrape-examples.js | 34 +- src/librustdoc/html/static/js/search.js | 21 +- src/librustdoc/html/static/js/settings.js | 74 ++- src/librustdoc/html/static/js/source-script.js | 11 +- src/librustdoc/html/static/js/storage.js | 24 +- src/librustdoc/html/static/scrape-examples-help.md | 5 +- src/librustdoc/html/static_files.rs | 263 ++++----- src/librustdoc/html/templates/page.html | 57 +- src/librustdoc/html/templates/print_item.html | 8 +- src/librustdoc/json/conversions.rs | 56 +- src/librustdoc/json/mod.rs | 58 +- src/librustdoc/lib.rs | 28 +- src/librustdoc/passes/bare_urls.rs | 110 ---- src/librustdoc/passes/calculate_doc_coverage.rs | 4 +- src/librustdoc/passes/check_code_block_syntax.rs | 209 ------- src/librustdoc/passes/check_doc_test_visibility.rs | 4 +- src/librustdoc/passes/collect_intra_doc_links.rs | 5 +- src/librustdoc/passes/collect_trait_impls.rs | 6 +- src/librustdoc/passes/html_tags.rs | 430 -------------- src/librustdoc/passes/lint.rs | 33 ++ src/librustdoc/passes/lint/bare_urls.rs | 89 +++ .../passes/lint/check_code_block_syntax.rs | 170 ++++++ src/librustdoc/passes/lint/html_tags.rs | 407 +++++++++++++ src/librustdoc/passes/mod.rs | 18 +- src/librustdoc/passes/strip_hidden.rs | 1 + src/librustdoc/passes/strip_priv_imports.rs | 4 +- src/librustdoc/passes/strip_private.rs | 4 +- src/librustdoc/passes/stripper.rs | 49 +- src/librustdoc/scrape_examples.rs | 10 +- src/librustdoc/visit_ast.rs | 112 ++-- src/librustdoc/visit_lib.rs | 108 ++-- 63 files changed, 2723 insertions(+), 2908 deletions(-) create mode 100644 src/librustdoc/html/static/fonts/README.txt delete mode 100644 src/librustdoc/passes/bare_urls.rs delete mode 100644 src/librustdoc/passes/check_code_block_syntax.rs delete mode 100644 src/librustdoc/passes/html_tags.rs create mode 100644 src/librustdoc/passes/lint.rs create mode 100644 src/librustdoc/passes/lint/bare_urls.rs create mode 100644 src/librustdoc/passes/lint/check_code_block_syntax.rs create mode 100644 src/librustdoc/passes/lint/html_tags.rs (limited to 'src/librustdoc') diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 7bc35c7d5..0271c27b4 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -9,7 +9,6 @@ path = "lib.rs" [dependencies] arrayvec = { version = "0.7", default-features = false } askama = { version = "0.11", default-features = false, features = ["config"] } -atty = "0.2" itertools = "0.10.1" minifier = "0.2.2" once_cell = "1.10.0" @@ -20,7 +19,7 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } smallvec = "1.8.1" tempfile = "3" -thin-vec = "0.2.8" +thin-vec = "0.2.9" tracing = "0.1" tracing-tree = "0.2.0" @@ -33,7 +32,7 @@ features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] rayon = "1.5.1" [dev-dependencies] -expect-test = "1.0" +expect-test = "1.4.0" [features] jemalloc = [] diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 764a6d3aa..953f4aa8a 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -3,6 +3,7 @@ use rustc_hir as hir; use rustc_hir::lang_items::LangItem; use rustc_middle::ty::{self, Region, RegionVid, TypeFoldable, TypeSuperFoldable}; use rustc_trait_selection::traits::auto_trait::{self, AutoTraitResult}; +use thin_vec::ThinVec; use std::fmt::Debug; @@ -43,7 +44,7 @@ where discard_positive_impl: bool, ) -> Option { let tcx = self.cx.tcx; - let trait_ref = ty::TraitRef { def_id: trait_def_id, substs: tcx.mk_substs_trait(ty, &[]) }; + let trait_ref = tcx.mk_trait_ref(trait_def_id, [ty]); if !self.cx.generated_synthetics.insert((ty, trait_def_id)) { debug!("get_auto_trait_impl_for({:?}): already generated, aborting", trait_ref); return None; @@ -110,7 +111,7 @@ where ); let params = raw_generics.params; - Generics { params, where_predicates: Vec::new() } + Generics { params, where_predicates: ThinVec::new() } } AutoTraitResult::ExplicitImpl => return None, }; @@ -118,7 +119,6 @@ where Some(Item { name: None, attrs: Default::default(), - visibility: Inherited, item_id: ItemId::Auto { trait_: trait_def_id, for_: item_def_id }, kind: Box::new(ImplItem(Box::new(Impl { unsafety: hir::Unsafety::Normal, @@ -130,6 +130,7 @@ where kind: ImplKind::Auto, }))), cfg: None, + inline_stmt_id: None, }) } @@ -183,7 +184,7 @@ where fn handle_lifetimes<'cx>( regions: &RegionConstraintData<'cx>, names_map: &FxHashMap, - ) -> Vec { + ) -> ThinVec { // Our goal is to 'flatten' the list of constraints by eliminating // all intermediate RegionVids. At the end, all constraints should // be between Regions (aka region variables). This gives us the information @@ -320,10 +321,10 @@ where let bound_predicate = pred.kind(); let tcx = self.cx.tcx; let regions = match bound_predicate.skip_binder() { - ty::PredicateKind::Trait(poly_trait_pred) => { + ty::PredicateKind::Clause(ty::Clause::Trait(poly_trait_pred)) => { tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_trait_pred)) } - ty::PredicateKind::Projection(poly_proj_pred) => { + ty::PredicateKind::Clause(ty::Clause::Projection(poly_proj_pred)) => { tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_proj_pred)) } _ => return FxHashSet::default(), @@ -335,10 +336,7 @@ where match br { // We only care about named late bound regions, as we need to add them // to the 'for<>' section - ty::BrNamed(_, name) => Some(GenericParamDef { - name, - kind: GenericParamDefKind::Lifetime { outlives: vec![] }, - }), + ty::BrNamed(_, name) => Some(GenericParamDef::lifetime(name)), _ => None, } }) @@ -429,7 +427,7 @@ where &mut self, item_def_id: DefId, param_env: ty::ParamEnv<'tcx>, - mut existing_predicates: Vec, + mut existing_predicates: ThinVec, vid_to_region: FxHashMap>, ) -> Generics { debug!( @@ -453,7 +451,9 @@ where .filter(|p| { !orig_bounds.contains(p) || match p.kind().skip_binder() { - ty::PredicateKind::Trait(pred) => pred.def_id() == sized_trait, + ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { + pred.def_id() == sized_trait + } _ => false, } }) @@ -663,7 +663,7 @@ where /// both for visual consistency between 'rustdoc' runs, and to /// make writing tests much easier #[inline] - fn sort_where_predicates(&self, predicates: &mut Vec) { + fn sort_where_predicates(&self, predicates: &mut [WherePredicate]) { // We should never have identical bounds - and if we do, // they're visually identical as well. Therefore, using // an unstable sort is fine. @@ -710,7 +710,7 @@ where /// approach is probably somewhat slower, but the small number of items /// involved (impls rarely have more than a few bounds) means that it /// shouldn't matter in practice. - fn unstable_debug_sort(&self, vec: &mut Vec) { + fn unstable_debug_sort(&self, vec: &mut [T]) { vec.sort_by_cached_key(|x| format!("{:?}", x)) } diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs index 8b63c3db3..a1145b90d 100644 --- a/src/librustdoc/clean/blanket_impl.rs +++ b/src/librustdoc/clean/blanket_impl.rs @@ -20,7 +20,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { trace!("get_blanket_impls({:?})", ty); let mut impls = Vec::new(); for trait_def_id in cx.tcx.all_traits() { - if !cx.cache.effective_visibilities.is_directly_public(trait_def_id) + if !cx.cache.effective_visibilities.is_directly_public(cx.tcx, trait_def_id) || cx.generated_synthetics.get(&(ty.0, trait_def_id)).is_some() { continue; @@ -67,15 +67,11 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { .instantiate(cx.tcx, impl_substs) .predicates .into_iter() - .chain(Some( - ty::Binder::dummy(impl_trait_ref) - .to_poly_trait_predicate() - .map_bound(ty::PredicateKind::Trait) - .to_predicate(infcx.tcx), - )); + .chain(Some(ty::Binder::dummy(impl_trait_ref).to_predicate(infcx.tcx))); for predicate in predicates { debug!("testing predicate {:?}", predicate); let obligation = traits::Obligation::new( + infcx.tcx, traits::ObligationCause::dummy(), param_env, predicate, @@ -97,7 +93,6 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { impls.push(Item { name: None, attrs: Default::default(), - visibility: Inherited, item_id: ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id }, kind: Box::new(ImplItem(Box::new(Impl { unsafety: hir::Unsafety::Normal, @@ -128,6 +123,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { ))), }))), cfg: None, + inline_stmt_id: None, }); } } diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index f33f5d27d..1843a2120 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -50,7 +50,7 @@ impl Cfg { ) -> Result, InvalidCfgError> { match nested_cfg { NestedMetaItem::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude), - NestedMetaItem::Literal(ref lit) => { + NestedMetaItem::Lit(ref lit) => { Err(InvalidCfgError { msg: "unexpected literal", span: lit.span }) } } diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 4e2031a91..e7c3e5a45 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -3,7 +3,7 @@ use std::iter::once; use std::sync::Arc; -use thin_vec::ThinVec; +use thin_vec::{thin_vec, ThinVec}; use rustc_ast as ast; use rustc_data_structures::fx::FxHashSet; @@ -19,8 +19,7 @@ use rustc_span::symbol::{kw, sym, Symbol}; use crate::clean::{ self, clean_fn_decl_from_did_and_sig, clean_generics, clean_impl_item, clean_middle_assoc_item, clean_middle_field, clean_middle_ty, clean_trait_ref_with_bindings, clean_ty, - clean_ty_generics, clean_variant_def, clean_visibility, utils, Attributes, AttributesExt, - ImplKind, ItemId, Type, Visibility, + clean_ty_generics, clean_variant_def, utils, Attributes, AttributesExt, ImplKind, ItemId, Type, }; use crate::core::DocContext; use crate::formats::item_type::ItemType; @@ -152,18 +151,10 @@ pub(crate) fn try_inline( let (attrs, cfg) = merge_attrs(cx, Some(parent_module), load_attrs(cx, did), attrs); cx.inlined.insert(did.into()); - let mut item = clean::Item::from_def_id_and_attrs_and_parts( - did, - Some(name), - kind, - Box::new(attrs), - cx, - cfg, - ); - if let Some(import_def_id) = import_def_id { - // The visibility needs to reflect the one from the reexport and not from the "source" DefId. - item.visibility = clean_visibility(cx.tcx.visibility(import_def_id)); - } + let mut item = + clean::Item::from_def_id_and_attrs_and_parts(did, Some(name), kind, Box::new(attrs), cfg); + // The visibility needs to reflect the one from the reexport and not from the "source" DefId. + item.inline_stmt_id = import_def_id; ret.push(item); Some(ret) } @@ -239,13 +230,7 @@ pub(crate) fn build_external_trait(cx: &mut DocContext<'_>, did: DefId) -> clean .tcx .associated_items(did) .in_definition_order() - .map(|item| { - // When building an external trait, the cleaned trait will have all items public, - // which causes methods to have a `pub` prefix, which is invalid since items in traits - // can not have a visibility prefix. Thus we override the visibility here manually. - // See https://github.com/rust-lang/rust/issues/81274 - clean::Item { visibility: Visibility::Inherited, ..clean_middle_assoc_item(item, cx) } - }) + .map(|item| clean_middle_assoc_item(item, cx)) .collect(); let predicates = cx.tcx.predicates_of(did); @@ -258,10 +243,19 @@ pub(crate) fn build_external_trait(cx: &mut DocContext<'_>, did: DefId) -> clean fn build_external_function<'tcx>(cx: &mut DocContext<'tcx>, did: DefId) -> Box { let sig = cx.tcx.fn_sig(did); - let predicates = cx.tcx.predicates_of(did); + let late_bound_regions = sig.bound_vars().into_iter().filter_map(|var| match var { + ty::BoundVariableKind::Region(ty::BrNamed(_, name)) if name != kw::UnderscoreLifetime => { + Some(clean::GenericParamDef::lifetime(name)) + } + _ => None, + }); + + let predicates = cx.tcx.explicit_predicates_of(did); let (generics, decl) = clean::enter_impl_trait(cx, |cx| { // NOTE: generics need to be cleaned before the decl! - let generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates); + let mut generics = clean_ty_generics(cx, cx.tcx.generics_of(did), predicates); + // FIXME: This does not place parameters in source order (late-bound ones come last) + generics.params.extend(late_bound_regions); let decl = clean_fn_decl_from_did_and_sig(cx, Some(did), sig); (generics, decl) }); @@ -282,7 +276,7 @@ fn build_struct(cx: &mut DocContext<'_>, did: DefId) -> clean::Struct { let variant = cx.tcx.adt_def(did).non_enum_variant(); clean::Struct { - struct_type: variant.ctor_kind, + ctor_kind: variant.ctor_kind(), generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates), fields: variant.fields.iter().map(|x| clean_middle_field(x, cx)).collect(), } @@ -389,7 +383,7 @@ pub(crate) fn build_impl( if !did.is_local() { if let Some(traitref) = associated_trait { let did = traitref.def_id; - if !cx.cache.effective_visibilities.is_directly_public(did) { + if !cx.cache.effective_visibilities.is_directly_public(tcx, did) { return; } @@ -418,7 +412,7 @@ pub(crate) fn build_impl( // reachable in rustdoc generated documentation if !did.is_local() { if let Some(did) = for_.def_id(&cx.cache) { - if !cx.cache.effective_visibilities.is_directly_public(did) { + if !cx.cache.effective_visibilities.is_directly_public(tcx, did) { return; } @@ -559,7 +553,6 @@ pub(crate) fn build_impl( }, })), Box::new(merged_attrs), - cx, cfg, )); } @@ -607,13 +600,12 @@ fn build_module_items( name: None, attrs: Box::new(clean::Attributes::default()), item_id: ItemId::Primitive(prim_ty, did.krate), - visibility: clean::Public, kind: Box::new(clean::ImportItem(clean::Import::new_simple( item.ident.name, clean::ImportSource { path: clean::Path { res, - segments: vec![clean::PathSegment { + segments: thin_vec![clean::PathSegment { name: prim_ty.as_sym(), args: clean::GenericArgs::AngleBracketed { args: Default::default(), @@ -626,6 +618,7 @@ fn build_module_items( true, ))), cfg: None, + inline_stmt_id: None, }); } else if let Some(i) = try_inline(cx, did, None, res, item.ident.name, None, visited) { items.extend(i) @@ -669,7 +662,7 @@ fn build_macro( match CStore::from_tcx(cx.tcx).load_macro_untracked(def_id, cx.sess()) { LoadedMacro::MacroDef(item_def, _) => { if let ast::ItemKind::MacroDef(ref def) = item_def.kind { - let vis = clean_visibility(cx.tcx.visibility(import_def_id.unwrap_or(def_id))); + let vis = cx.tcx.visibility(import_def_id.unwrap_or(def_id)); clean::MacroItem(clean::Macro { source: utils::display_macro_source(cx, name, def, def_id, vis), }) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 64a18757b..2a2a9470d 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -12,7 +12,7 @@ pub(crate) mod utils; use rustc_ast as ast; use rustc_attr as attr; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; @@ -74,12 +74,12 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< // This covers the case where somebody does an import which should pull in an item, // but there's already an item with the same namespace and same name. Rust gives // priority to the not-imported one, so we should, too. - items.extend(doc.items.iter().flat_map(|(item, renamed)| { + items.extend(doc.items.iter().flat_map(|(item, renamed, import_id)| { // First, lower everything other than imports. if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { return Vec::new(); } - let v = clean_maybe_renamed_item(cx, item, *renamed); + let v = clean_maybe_renamed_item(cx, item, *renamed, *import_id); for item in &v { if let Some(name) = item.name && !item.attrs.lists(sym::doc).has_word(sym::hidden) { inserted.insert((item.type_(), name)); @@ -87,7 +87,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< } v })); - items.extend(doc.items.iter().flat_map(|(item, renamed)| { + items.extend(doc.items.iter().flat_map(|(item, renamed, _)| { // Now we actually lower the imports, skipping everything else. if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind { let name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id())); @@ -182,10 +182,7 @@ fn clean_poly_trait_ref_with_bindings<'tcx>( .collect_referenced_late_bound_regions(&poly_trait_ref) .into_iter() .filter_map(|br| match br { - ty::BrNamed(_, name) if name != kw::UnderscoreLifetime => Some(GenericParamDef { - name, - kind: GenericParamDefKind::Lifetime { outlives: vec![] }, - }), + ty::BrNamed(_, name) if br.is_named() => Some(GenericParamDef::lifetime(name)), _ => None, }) .collect(); @@ -209,7 +206,7 @@ fn clean_lifetime<'tcx>(lifetime: &hir::Lifetime, cx: &mut DocContext<'tcx>) -> return lt; } } - Lifetime(lifetime.name.ident().name) + Lifetime(lifetime.ident.name) } pub(crate) fn clean_const<'tcx>(constant: &hir::ConstArg, cx: &mut DocContext<'tcx>) -> Constant { @@ -227,23 +224,18 @@ pub(crate) fn clean_middle_const<'tcx>( // FIXME: instead of storing the stringified expression, store `self` directly instead. Constant { type_: clean_middle_ty(constant.ty(), cx, None), - kind: ConstantKind::TyConst { expr: constant.to_string() }, + kind: ConstantKind::TyConst { expr: constant.to_string().into() }, } } pub(crate) fn clean_middle_region<'tcx>(region: ty::Region<'tcx>) -> Option { match *region { ty::ReStatic => Some(Lifetime::statik()), + _ if !region.has_name() => None, ty::ReLateBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. }) => { - if name != kw::UnderscoreLifetime { Some(Lifetime(name)) } else { None } - } - ty::ReEarlyBound(ref data) => { - if data.name != kw::UnderscoreLifetime { - Some(Lifetime(data.name)) - } else { - None - } + Some(Lifetime(name)) } + ty::ReEarlyBound(ref data) => Some(Lifetime(data.name)), ty::ReLateBound(..) | ty::ReFree(..) | ty::ReVar(..) @@ -303,12 +295,16 @@ pub(crate) fn clean_predicate<'tcx>( ) -> Option { let bound_predicate = predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Trait(pred) => { + ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { clean_poly_trait_predicate(bound_predicate.rebind(pred), cx) } - ty::PredicateKind::RegionOutlives(pred) => clean_region_outlives_predicate(pred), - ty::PredicateKind::TypeOutlives(pred) => clean_type_outlives_predicate(pred, cx), - ty::PredicateKind::Projection(pred) => { + ty::PredicateKind::Clause(ty::Clause::RegionOutlives(pred)) => { + clean_region_outlives_predicate(pred) + } + ty::PredicateKind::Clause(ty::Clause::TypeOutlives(pred)) => { + clean_type_outlives_predicate(pred, cx) + } + ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => { Some(clean_projection_predicate(bound_predicate.rebind(pred), cx)) } ty::PredicateKind::ConstEvaluatable(..) => None, @@ -319,6 +315,7 @@ pub(crate) fn clean_predicate<'tcx>( | ty::PredicateKind::ObjectSafe(..) | ty::PredicateKind::ClosureKind(..) | ty::PredicateKind::ConstEquate(..) + | ty::PredicateKind::Ambiguous | ty::PredicateKind::TypeWellFormedFromEnv(..) => panic!("not user writable"), } } @@ -396,7 +393,7 @@ fn clean_projection_predicate<'tcx>( .collect_referenced_late_bound_regions(&pred) .into_iter() .filter_map(|br| match br { - ty::BrNamed(_, name) if name != kw::UnderscoreLifetime => Some(Lifetime(name)), + ty::BrNamed(_, name) if br.is_named() => Some(Lifetime(name)), _ => None, }) .collect(); @@ -601,47 +598,105 @@ pub(crate) fn clean_generics<'tcx>( }) .collect::>(); - let mut params = Vec::with_capacity(gens.params.len()); - for p in gens.params.iter().filter(|p| !is_impl_trait(p) && !is_elided_lifetime(p)) { - let p = clean_generic_param(cx, Some(gens), p); - params.push(p); + let mut bound_predicates = FxIndexMap::default(); + let mut region_predicates = FxIndexMap::default(); + let mut eq_predicates = ThinVec::default(); + for pred in gens.predicates.iter().filter_map(|x| clean_where_predicate(x, cx)) { + match pred { + WherePredicate::BoundPredicate { ty, bounds, bound_params } => { + match bound_predicates.entry(ty) { + IndexEntry::Vacant(v) => { + v.insert((bounds, bound_params)); + } + IndexEntry::Occupied(mut o) => { + // we merge both bounds. + for bound in bounds { + if !o.get().0.contains(&bound) { + o.get_mut().0.push(bound); + } + } + for bound_param in bound_params { + if !o.get().1.contains(&bound_param) { + o.get_mut().1.push(bound_param); + } + } + } + } + } + WherePredicate::RegionPredicate { lifetime, bounds } => { + match region_predicates.entry(lifetime) { + IndexEntry::Vacant(v) => { + v.insert(bounds); + } + IndexEntry::Occupied(mut o) => { + // we merge both bounds. + for bound in bounds { + if !o.get().contains(&bound) { + o.get_mut().push(bound); + } + } + } + } + } + WherePredicate::EqPredicate { lhs, rhs, bound_params } => { + eq_predicates.push(WherePredicate::EqPredicate { lhs, rhs, bound_params }); + } + } } - params.extend(impl_trait_params); - let mut generics = Generics { - params, - where_predicates: gens - .predicates - .iter() - .filter_map(|x| clean_where_predicate(x, cx)) - .collect(), - }; - - // Some duplicates are generated for ?Sized bounds between type params and where - // predicates. The point in here is to move the bounds definitions from type params - // to where predicates when such cases occur. - for where_pred in &mut generics.where_predicates { - match *where_pred { - WherePredicate::BoundPredicate { ty: Generic(ref name), ref mut bounds, .. } => { - if bounds.is_empty() { - for param in &mut generics.params { - match param.kind { - GenericParamDefKind::Lifetime { .. } => {} - GenericParamDefKind::Type { bounds: ref mut ty_bounds, .. } => { - if ¶m.name == name { - mem::swap(bounds, ty_bounds); - break; - } - } - GenericParamDefKind::Const { .. } => {} + let mut params = ThinVec::with_capacity(gens.params.len()); + // In this loop, we gather the generic parameters (`<'a, B: 'a>`) and check if they have + // bounds in the where predicates. If so, we move their bounds into the where predicates + // while also preventing duplicates. + for p in gens.params.iter().filter(|p| !is_impl_trait(p) && !is_elided_lifetime(p)) { + let mut p = clean_generic_param(cx, Some(gens), p); + match &mut p.kind { + GenericParamDefKind::Lifetime { ref mut outlives } => { + if let Some(region_pred) = region_predicates.get_mut(&Lifetime(p.name)) { + // We merge bounds in the `where` clause. + for outlive in outlives.drain(..) { + let outlive = GenericBound::Outlives(outlive); + if !region_pred.contains(&outlive) { + region_pred.push(outlive); + } + } + } + } + GenericParamDefKind::Type { bounds, synthetic: false, .. } => { + if let Some(bound_pred) = bound_predicates.get_mut(&Type::Generic(p.name)) { + // We merge bounds in the `where` clause. + for bound in bounds.drain(..) { + if !bound_pred.0.contains(&bound) { + bound_pred.0.push(bound); } } } } - _ => continue, + GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { + // nothing to do here. + } } + params.push(p); + } + params.extend(impl_trait_params); + + Generics { + params, + where_predicates: bound_predicates + .into_iter() + .map(|(ty, (bounds, bound_params))| WherePredicate::BoundPredicate { + ty, + bounds, + bound_params, + }) + .chain( + region_predicates + .into_iter() + .map(|(lifetime, bounds)| WherePredicate::RegionPredicate { lifetime, bounds }), + ) + .chain(eq_predicates.into_iter()) + .collect(), } - generics } fn clean_ty_generics<'tcx>( @@ -660,7 +715,7 @@ fn clean_ty_generics<'tcx>( .params .iter() .filter_map(|param| match param.kind { - ty::GenericParamDefKind::Lifetime if param.name == kw::UnderscoreLifetime => None, + ty::GenericParamDefKind::Lifetime if param.is_anonymous_lifetime() => None, ty::GenericParamDefKind::Lifetime => Some(clean_generic_param_def(param, cx)), ty::GenericParamDefKind::Type { synthetic, .. } => { if param.name == kw::SelfUpper { @@ -675,7 +730,7 @@ fn clean_ty_generics<'tcx>( } ty::GenericParamDefKind::Const { .. } => Some(clean_generic_param_def(param, cx)), }) - .collect::>(); + .collect::>(); // param index -> [(trait DefId, associated type name & generics, type, higher-ranked params)] let mut impl_trait_proj = @@ -689,17 +744,20 @@ fn clean_ty_generics<'tcx>( let param_idx = (|| { let bound_p = p.kind(); match bound_p.skip_binder() { - ty::PredicateKind::Trait(pred) => { + ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { if let ty::Param(param) = pred.self_ty().kind() { return Some(param.index); } } - ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(ty, _reg)) => { + ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( + ty, + _reg, + ))) => { if let ty::Param(param) = ty.kind() { return Some(param.index); } } - ty::PredicateKind::Projection(p) => { + ty::PredicateKind::Clause(ty::Clause::Projection(p)) => { if let ty::Param(param) = p.projection_ty.self_ty().kind() { projection = Some(bound_p.rebind(p)); return Some(param.index); @@ -741,10 +799,7 @@ fn clean_ty_generics<'tcx>( p.get_bound_params() .into_iter() .flatten() - .map(|param| GenericParamDef { - name: param.0, - kind: GenericParamDefKind::Lifetime { outlives: Vec::new() }, - }) + .map(|param| GenericParamDef::lifetime(param.0)) .collect(), )); } @@ -880,7 +935,7 @@ fn clean_fn_or_proc_macro<'tcx>( ProcMacroItem(ProcMacro { kind, helpers }) } None => { - let mut func = clean_function(cx, sig, generics, body_id); + let mut func = clean_function(cx, sig, generics, FunctionArgs::Body(body_id)); clean_fn_decl_legacy_const_generics(&mut func, attrs); FunctionItem(func) } @@ -896,7 +951,7 @@ fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[ast::Attrib .filter(|a| a.has_name(sym::rustc_legacy_const_generics)) .filter_map(|a| a.meta_item_list()) { - for (pos, literal) in meta_item_list.iter().filter_map(|meta| meta.literal()).enumerate() { + for (pos, literal) in meta_item_list.iter().filter_map(|meta| meta.lit()).enumerate() { match literal.kind { ast::LitKind::Int(a, _) => { let gen = func.generics.params.remove(0); @@ -917,16 +972,28 @@ fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[ast::Attrib } } +enum FunctionArgs<'tcx> { + Body(hir::BodyId), + Names(&'tcx [Ident]), +} + fn clean_function<'tcx>( cx: &mut DocContext<'tcx>, sig: &hir::FnSig<'tcx>, generics: &hir::Generics<'tcx>, - body_id: hir::BodyId, + args: FunctionArgs<'tcx>, ) -> Box { let (generics, decl) = enter_impl_trait(cx, |cx| { // NOTE: generics must be cleaned before args let generics = clean_generics(generics, cx); - let args = clean_args_from_types_and_body_id(cx, sig.decl.inputs, body_id); + let args = match args { + FunctionArgs::Body(body_id) => { + clean_args_from_types_and_body_id(cx, sig.decl.inputs, body_id) + } + FunctionArgs::Names(names) => { + clean_args_from_types_and_names(cx, sig.decl.inputs, names) + } + }; let mut decl = clean_fn_decl_with_args(cx, sig.decl, args); if sig.header.is_async() { decl.output = decl.sugared_async_return_type(); @@ -945,12 +1012,14 @@ fn clean_args_from_types_and_names<'tcx>( values: types .iter() .enumerate() - .map(|(i, ty)| { - let mut name = names.get(i).map_or(kw::Empty, |ident| ident.name); - if name.is_empty() { - name = kw::Underscore; - } - Argument { name, type_: clean_ty(ty, cx), is_const: false } + .map(|(i, ty)| Argument { + type_: clean_ty(ty, cx), + name: names + .get(i) + .map(|ident| ident.name) + .filter(|ident| !ident.is_empty()) + .unwrap_or(kw::Underscore), + is_const: false, }) .collect(), } @@ -1012,7 +1081,11 @@ fn clean_fn_decl_from_did_and_sig<'tcx>( .iter() .map(|t| Argument { type_: clean_middle_ty(*t, cx, None), - name: names.next().map_or(kw::Empty, |i| i.name), + name: names + .next() + .map(|i| i.name) + .filter(|i| !i.is_empty()) + .unwrap_or(kw::Underscore), is_const: false, }) .collect(), @@ -1051,18 +1124,12 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext ), hir::TraitItemKind::Const(ty, None) => TyAssocConstItem(clean_ty(ty, cx)), hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => { - let m = clean_function(cx, sig, trait_item.generics, body); + let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Body(body)); MethodItem(m, None) } hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(names)) => { - let (generics, decl) = enter_impl_trait(cx, |cx| { - // NOTE: generics must be cleaned before args - let generics = clean_generics(trait_item.generics, cx); - let args = clean_args_from_types_and_names(cx, sig.decl.inputs, names); - let decl = clean_fn_decl_with_args(cx, sig.decl, args); - (generics, decl) - }); - TyMethodItem(Box::new(Function { decl, generics })) + let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Names(names)); + TyMethodItem(m) } hir::TraitItemKind::Type(bounds, Some(default)) => { let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); @@ -1080,13 +1147,10 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext hir::TraitItemKind::Type(bounds, None) => { let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); let bounds = bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(); - TyAssocTypeItem(Box::new(generics), bounds) + TyAssocTypeItem(generics, bounds) } }; - let what_rustc_thinks = - Item::from_def_id_and_parts(local_did, Some(trait_item.ident.name), inner, cx); - // Trait items always inherit the trait's visibility -- we don't want to show `pub`. - Item { visibility: Inherited, ..what_rustc_thinks } + Item::from_def_id_and_parts(local_did, Some(trait_item.ident.name), inner, cx) }) } @@ -1102,7 +1166,7 @@ pub(crate) fn clean_impl_item<'tcx>( AssocConstItem(clean_ty(ty, cx), default) } hir::ImplItemKind::Fn(ref sig, body) => { - let m = clean_function(cx, sig, impl_.generics, body); + let m = clean_function(cx, sig, impl_.generics, FunctionArgs::Body(body)); let defaultness = cx.tcx.impl_defaultness(impl_.owner_id); MethodItem(m, Some(defaultness)) } @@ -1117,18 +1181,7 @@ pub(crate) fn clean_impl_item<'tcx>( } }; - let mut what_rustc_thinks = - Item::from_def_id_and_parts(local_did, Some(impl_.ident.name), inner, cx); - - let impl_ref = cx.tcx.impl_trait_ref(cx.tcx.local_parent(impl_.owner_id.def_id)); - - // Trait impl items always inherit the impl's visibility -- - // we don't want to show `pub`. - if impl_ref.is_some() { - what_rustc_thinks.visibility = Inherited; - } - - what_rustc_thinks + Item::from_def_id_and_parts(local_did, Some(impl_.ident.name), inner, cx) }) } @@ -1152,12 +1205,25 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( } } ty::AssocKind::Fn => { - let generics = clean_ty_generics( + let sig = tcx.fn_sig(assoc_item.def_id); + + let late_bound_regions = sig.bound_vars().into_iter().filter_map(|var| match var { + ty::BoundVariableKind::Region(ty::BrNamed(_, name)) + if name != kw::UnderscoreLifetime => + { + Some(GenericParamDef::lifetime(name)) + } + _ => None, + }); + + let mut generics = clean_ty_generics( cx, tcx.generics_of(assoc_item.def_id), tcx.explicit_predicates_of(assoc_item.def_id), ); - let sig = tcx.fn_sig(assoc_item.def_id); + // FIXME: This does not place parameters in source order (late-bound ones come last) + generics.params.extend(late_bound_regions); + let mut decl = clean_fn_decl_from_did_and_sig(cx, Some(assoc_item.def_id), sig); if assoc_item.fn_has_self_parameter { @@ -1208,7 +1274,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( true } (GenericParamDefKind::Const { .. }, GenericArg::Const(c)) => match &c.kind { - ConstantKind::TyConst { expr } => expr == param.name.as_str(), + ConstantKind::TyConst { expr } => **expr == *param.name.as_str(), _ => false, }, _ => false, @@ -1225,56 +1291,47 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( tcx.generics_of(assoc_item.def_id), ty::GenericPredicates { parent: None, predicates }, ); - // Move bounds that are (likely) directly attached to the associated type - // from the where clause to the associated type. - // There is no guarantee that this is what the user actually wrote but we have - // no way of knowing. - let mut bounds = generics - .where_predicates - .drain_filter(|pred| match *pred { - WherePredicate::BoundPredicate { - ty: QPath(box QPathData { ref assoc, ref self_type, ref trait_, .. }), - .. - } => { - if assoc.name != my_name { - return false; - } - if trait_.def_id() != assoc_item.container_id(tcx) { - return false; - } - match *self_type { - Generic(ref s) if *s == kw::SelfUpper => {} - _ => return false, - } - match &assoc.args { - GenericArgs::AngleBracketed { args, bindings } => { - if !bindings.is_empty() - || generics - .params - .iter() - .zip(args.iter()) - .any(|(param, arg)| !param_eq_arg(param, arg)) - { - return false; - } - } - GenericArgs::Parenthesized { .. } => { - // The only time this happens is if we're inside the rustdoc for Fn(), - // which only has one associated type, which is not a GAT, so whatever. + // Filter out the bounds that are (likely?) directly attached to the associated type, + // as opposed to being located in the where clause. + let mut bounds: Vec = Vec::new(); + generics.where_predicates.retain_mut(|pred| match *pred { + WherePredicate::BoundPredicate { + ty: QPath(box QPathData { ref assoc, ref self_type, ref trait_, .. }), + bounds: ref mut pred_bounds, + .. + } => { + if assoc.name != my_name { + return true; + } + if trait_.def_id() != assoc_item.container_id(tcx) { + return true; + } + match *self_type { + Generic(ref s) if *s == kw::SelfUpper => {} + _ => return true, + } + match &assoc.args { + GenericArgs::AngleBracketed { args, bindings } => { + if !bindings.is_empty() + || generics + .params + .iter() + .zip(args.iter()) + .any(|(param, arg)| !param_eq_arg(param, arg)) + { + return true; } } - true - } - _ => false, - }) - .flat_map(|pred| { - if let WherePredicate::BoundPredicate { bounds, .. } = pred { - bounds - } else { - unreachable!() + GenericArgs::Parenthesized { .. } => { + // The only time this happens is if we're inside the rustdoc for Fn(), + // which only has one associated type, which is not a GAT, so whatever. + } } - }) - .collect::>(); + bounds.extend(mem::replace(pred_bounds, Vec::new())); + false + } + _ => true, + }); // Our Sized/?Sized bound didn't get handled when creating the generics // because we didn't actually get our whole set of bounds until just now // (some of them may have come from the trait). If we do have a sized @@ -1290,7 +1347,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( // (generic) associated type from the where clause to the respective parameter. // There is no guarantee that this is what the user actually wrote but we have // no way of knowing. - let mut where_predicates = Vec::new(); + let mut where_predicates = ThinVec::new(); for mut pred in generics.where_predicates { if let WherePredicate::BoundPredicate { ty: Generic(arg), bounds, .. } = &mut pred && let Some(GenericParamDef { @@ -1298,7 +1355,16 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( .. }) = generics.params.iter_mut().find(|param| ¶m.name == arg) { - param_bounds.extend(mem::take(bounds)); + param_bounds.append(bounds); + } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } = &mut pred + && let Some(GenericParamDef { + kind: GenericParamDefKind::Lifetime { outlives: param_bounds }, + .. + }) = generics.params.iter_mut().find(|param| ¶m.name == arg) { + param_bounds.extend(bounds.drain(..).map(|bound| match bound { + GenericBound::Outlives(lifetime) => lifetime, + _ => unreachable!(), + })); } else { where_predicates.push(pred); } @@ -1320,7 +1386,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( bounds, ) } else { - TyAssocTypeItem(Box::new(generics), bounds) + TyAssocTypeItem(generics, bounds) } } else { // FIXME: when could this happen? Associated items in inherent impls? @@ -1331,7 +1397,10 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( cx, Some(assoc_item.def_id), ), - generics: Generics { params: Vec::new(), where_predicates: Vec::new() }, + generics: Generics { + params: ThinVec::new(), + where_predicates: ThinVec::new(), + }, item_type: None, }), Vec::new(), @@ -1340,18 +1409,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( } }; - let mut what_rustc_thinks = - Item::from_def_id_and_parts(assoc_item.def_id, Some(assoc_item.name), kind, cx); - - let impl_ref = tcx.impl_trait_ref(tcx.parent(assoc_item.def_id)); - - // Trait impl items always inherit the impl's visibility -- - // we don't want to show `pub`. - if impl_ref.is_some() { - what_rustc_thinks.visibility = Visibility::Inherited; - } - - what_rustc_thinks + Item::from_def_id_and_parts(assoc_item.def_id, Some(assoc_item.name), kind, cx) } fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type { @@ -1406,7 +1464,8 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type ty::Projection(proj) => Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id), // Rustdoc handles `ty::Error`s by turning them into `Type::Infer`s. ty::Error(_) => return Type::Infer, - _ => bug!("clean: expected associated type, found `{:?}`", ty), + // Otherwise, this is an inherent associated type. + _ => return clean_middle_ty(ty, cx, None), }; let trait_ = clean_path(&hir::Path { span, res, segments: &[] }, cx); register_res(cx, trait_.res); @@ -1431,7 +1490,7 @@ fn maybe_expand_private_type_alias<'tcx>( let Res::Def(DefKind::TyAlias, def_id) = path.res else { return None }; // Substitute private type aliases let def_id = def_id.as_local()?; - let alias = if !cx.cache.effective_visibilities.is_exported(def_id.to_def_id()) { + let alias = if !cx.cache.effective_visibilities.is_exported(cx.tcx, def_id.to_def_id()) { &cx.tcx.hir().expect_item(def_id).kind } else { return None; @@ -1459,8 +1518,11 @@ fn maybe_expand_private_type_alias<'tcx>( }); if let Some(lt) = lifetime { let lt_def_id = cx.tcx.hir().local_def_id(param.hir_id); - let cleaned = - if !lt.is_elided() { clean_lifetime(lt, cx) } else { Lifetime::elided() }; + let cleaned = if !lt.is_anonymous() { + clean_lifetime(lt, cx) + } else { + Lifetime::elided() + }; substs.insert(lt_def_id.to_def_id(), SubstParam::Lifetime(cleaned)); } indices.lifetimes += 1; @@ -1523,16 +1585,7 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T TyKind::Never => Primitive(PrimitiveType::Never), TyKind::Ptr(ref m) => RawPointer(m.mutbl, Box::new(clean_ty(m.ty, cx))), TyKind::Rptr(ref l, ref m) => { - // There are two times a `Fresh` lifetime can be created: - // 1. For `&'_ x`, written by the user. This corresponds to `lower_lifetime` in `rustc_ast_lowering`. - // 2. For `&x` as a parameter to an `async fn`. This corresponds to `elided_ref_lifetime in `rustc_ast_lowering`. - // See #59286 for more information. - // Ideally we would only hide the `'_` for case 2., but I don't know a way to distinguish it. - // Turning `fn f(&'_ self)` into `fn f(&self)` isn't the worst thing in the world, though; - // there's no case where it could cause the function to fail to compile. - let elided = - l.is_elided() || matches!(l.name, LifetimeName::Param(_, ParamName::Fresh)); - let lifetime = if elided { None } else { Some(clean_lifetime(*l, cx)) }; + let lifetime = if l.is_anonymous() { None } else { Some(clean_lifetime(*l, cx)) }; BorrowedRef { lifetime, mutability: m.mutbl, type_: Box::new(clean_ty(m.ty, cx)) } } TyKind::Slice(ty) => Slice(Box::new(clean_ty(ty, cx))), @@ -1554,7 +1607,7 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T } }; - Array(Box::new(clean_ty(ty, cx)), length) + Array(Box::new(clean_ty(ty, cx)), length.into()) } TyKind::Tup(tys) => Tuple(tys.iter().map(|ty| clean_ty(ty, cx)).collect()), TyKind::OpaqueDef(item_id, _, _) => { @@ -1586,14 +1639,14 @@ fn normalize<'tcx>(cx: &mut DocContext<'tcx>, ty: Ty<'tcx>) -> Option> } use crate::rustc_trait_selection::infer::TyCtxtInferExt; - use crate::rustc_trait_selection::traits::query::normalize::AtExt; + use crate::rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; use rustc_middle::traits::ObligationCause; // Try to normalize `::T` to a type let infcx = cx.tcx.infer_ctxt().build(); let normalized = infcx .at(&ObligationCause::dummy(), cx.param_env) - .normalize(ty) + .query_normalize(ty) .map(|resolved| infcx.resolve_vars_if_possible(resolved.value)); match normalized { Ok(normalized_value) => { @@ -1626,7 +1679,7 @@ pub(crate) fn clean_middle_ty<'tcx>( ty::Array(ty, mut n) => { n = n.eval(cx.tcx, ty::ParamEnv::reveal_all()); let n = print_const(cx, n); - Array(Box::new(clean_middle_ty(ty, cx, None)), n) + Array(Box::new(clean_middle_ty(ty, cx, None)), n.into()) } ty::RawPtr(mt) => RawPointer(mt.mutbl, Box::new(clean_middle_ty(mt.ty, cx, None))), ty::Ref(r, ty, mutbl) => BorrowedRef { @@ -1677,6 +1730,9 @@ pub(crate) fn clean_middle_ty<'tcx>( inline::record_extern_fqn(cx, did, ItemType::Trait); + // FIXME(fmease): Hide the trait-object lifetime bound if it coincides with its default + // to partially address #44306. Follow the rules outlined at + // https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes let lifetime = clean_middle_region(*reg); let mut bounds = dids .map(|did| { @@ -1704,8 +1760,22 @@ pub(crate) fn clean_middle_ty<'tcx>( }) .collect(); + let late_bound_regions: FxIndexSet<_> = obj + .iter() + .flat_map(|pb| pb.bound_vars()) + .filter_map(|br| match br { + ty::BoundVariableKind::Region(ty::BrNamed(_, name)) + if name != kw::UnderscoreLifetime => + { + Some(GenericParamDef::lifetime(name)) + } + _ => None, + }) + .collect(); + let late_bound_regions = late_bound_regions.into_iter().collect(); + let path = external_path(cx, did, false, bindings, substs); - bounds.insert(0, PolyTrait { trait_: path, generic_params: Vec::new() }); + bounds.insert(0, PolyTrait { trait_: path, generic_params: late_bound_regions }); DynTrait(bounds, lifetime) } @@ -1754,8 +1824,13 @@ fn clean_middle_opaque_bounds<'tcx>( .filter_map(|bound| { let bound_predicate = bound.kind(); let trait_ref = match bound_predicate.skip_binder() { - ty::PredicateKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref), - ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => { + ty::PredicateKind::Clause(ty::Clause::Trait(tr)) => { + bound_predicate.rebind(tr.trait_ref) + } + ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( + _ty, + reg, + ))) => { if let Some(r) = clean_middle_region(reg) { regions.push(GenericBound::Outlives(r)); } @@ -1774,7 +1849,9 @@ fn clean_middle_opaque_bounds<'tcx>( let bindings: ThinVec<_> = bounds .iter() .filter_map(|bound| { - if let ty::PredicateKind::Projection(proj) = bound.kind().skip_binder() { + if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = + bound.kind().skip_binder() + { if proj.projection_ty.trait_ref(cx.tcx) == trait_ref.skip_binder() { Some(TypeBinding { assoc: projection_to_path_segment(proj.projection_ty, cx), @@ -1821,50 +1898,24 @@ pub(crate) fn clean_field_with_def_id( ty: Type, cx: &mut DocContext<'_>, ) -> Item { - let what_rustc_thinks = - Item::from_def_id_and_parts(def_id, Some(name), StructFieldItem(ty), cx); - if is_field_vis_inherited(cx.tcx, def_id) { - // Variant fields inherit their enum's visibility. - Item { visibility: Visibility::Inherited, ..what_rustc_thinks } - } else { - what_rustc_thinks - } -} - -fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool { - let parent = tcx.parent(def_id); - match tcx.def_kind(parent) { - DefKind::Struct | DefKind::Union => false, - DefKind::Variant => true, - parent_kind => panic!("unexpected parent kind: {:?}", parent_kind), - } -} - -pub(crate) fn clean_visibility(vis: ty::Visibility) -> Visibility { - match vis { - ty::Visibility::Public => Visibility::Public, - ty::Visibility::Restricted(module) => Visibility::Restricted(module), - } + Item::from_def_id_and_parts(def_id, Some(name), StructFieldItem(ty), cx) } pub(crate) fn clean_variant_def<'tcx>(variant: &ty::VariantDef, cx: &mut DocContext<'tcx>) -> Item { - let kind = match variant.ctor_kind { - CtorKind::Const => Variant::CLike(match variant.discr { + let kind = match variant.ctor_kind() { + Some(CtorKind::Const) => Variant::CLike(match variant.discr { ty::VariantDiscr::Explicit(def_id) => Some(Discriminant { expr: None, value: def_id }), ty::VariantDiscr::Relative(_) => None, }), - CtorKind::Fn => Variant::Tuple( + Some(CtorKind::Fn) => Variant::Tuple( variant.fields.iter().map(|field| clean_middle_field(field, cx)).collect(), ), - CtorKind::Fictive => Variant::Struct(VariantStruct { - struct_type: CtorKind::Fictive, + None => Variant::Struct(VariantStruct { + ctor_kind: None, fields: variant.fields.iter().map(|field| clean_middle_field(field, cx)).collect(), }), }; - let what_rustc_thinks = - Item::from_def_id_and_parts(variant.def_id, Some(variant.name), VariantItem(kind), cx); - // don't show `pub` for variants, which always inherit visibility - Item { visibility: Inherited, ..what_rustc_thinks } + Item::from_def_id_and_parts(variant.def_id, Some(variant.name), VariantItem(kind), cx) } fn clean_variant_data<'tcx>( @@ -1874,7 +1925,7 @@ fn clean_variant_data<'tcx>( ) -> Variant { match variant { hir::VariantData::Struct(..) => Variant::Struct(VariantStruct { - struct_type: CtorKind::from_hir(variant), + ctor_kind: None, fields: variant.fields().iter().map(|x| clean_field(x, cx)).collect(), }), hir::VariantData::Tuple(..) => { @@ -1909,7 +1960,7 @@ fn clean_generic_args<'tcx>( .args .iter() .map(|arg| match arg { - hir::GenericArg::Lifetime(lt) if !lt.is_elided() => { + hir::GenericArg::Lifetime(lt) if !lt.is_anonymous() => { GenericArg::Lifetime(clean_lifetime(*lt, cx)) } hir::GenericArg::Lifetime(_) => GenericArg::Lifetime(Lifetime::elided()), @@ -1951,10 +2002,84 @@ fn clean_bare_fn_ty<'tcx>( BareFunctionDecl { unsafety: bare_fn.unsafety, abi: bare_fn.abi, decl, generic_params } } +/// This visitor is used to go through only the "top level" of a item and not enter any sub +/// item while looking for a given `Ident` which is stored into `item` if found. +struct OneLevelVisitor<'hir> { + map: rustc_middle::hir::map::Map<'hir>, + item: Option<&'hir hir::Item<'hir>>, + looking_for: Ident, + target_hir_id: hir::HirId, +} + +impl<'hir> OneLevelVisitor<'hir> { + fn new(map: rustc_middle::hir::map::Map<'hir>, target_hir_id: hir::HirId) -> Self { + Self { map, item: None, looking_for: Ident::empty(), target_hir_id } + } + + fn reset(&mut self, looking_for: Ident) { + self.looking_for = looking_for; + self.item = None; + } +} + +impl<'hir> hir::intravisit::Visitor<'hir> for OneLevelVisitor<'hir> { + type NestedFilter = rustc_middle::hir::nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.map + } + + fn visit_item(&mut self, item: &'hir hir::Item<'hir>) { + if self.item.is_none() + && item.ident == self.looking_for + && matches!(item.kind, hir::ItemKind::Use(_, _)) + || item.hir_id() == self.target_hir_id + { + self.item = Some(item); + } + } +} + +/// Because a `Use` item directly links to the imported item, we need to manually go through each +/// import one by one. To do so, we go to the parent item and look for the `Ident` into it. Then, +/// if we found the "end item" (the imported one), we stop there because we don't need its +/// documentation. Otherwise, we repeat the same operation until we find the "end item". +fn get_all_import_attributes<'hir>( + mut item: &hir::Item<'hir>, + tcx: TyCtxt<'hir>, + target_hir_id: hir::HirId, + attributes: &mut Vec, +) { + let hir_map = tcx.hir(); + let mut visitor = OneLevelVisitor::new(hir_map, target_hir_id); + // If the item is an import and has at least a path with two parts, we go into it. + while let hir::ItemKind::Use(path, _) = item.kind && + path.segments.len() > 1 && + let hir::def::Res::Def(_, def_id) = path.segments[path.segments.len() - 2].res + { + if let Some(hir::Node::Item(parent_item)) = hir_map.get_if_local(def_id) { + // We add the attributes from this import into the list. + attributes.extend_from_slice(hir_map.attrs(item.hir_id())); + // We get the `Ident` we will be looking for into `item`. + let looking_for = path.segments[path.segments.len() - 1].ident; + visitor.reset(looking_for); + hir::intravisit::walk_item(&mut visitor, parent_item); + if let Some(i) = visitor.item { + item = i; + } else { + break; + } + } else { + break; + } + } +} + fn clean_maybe_renamed_item<'tcx>( cx: &mut DocContext<'tcx>, item: &hir::Item<'tcx>, renamed: Option, + import_id: Option, ) -> Vec { use hir::ItemKind; @@ -1995,7 +2120,7 @@ fn clean_maybe_renamed_item<'tcx>( fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(), }), ItemKind::Struct(ref variant_data, generics) => StructItem(Struct { - struct_type: CtorKind::from_hir(variant_data), + ctor_kind: variant_data.ctor_kind(), generics: clean_generics(generics, cx), fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(), }), @@ -2005,7 +2130,7 @@ fn clean_maybe_renamed_item<'tcx>( clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx) } ItemKind::Macro(ref macro_def, _) => { - let ty_vis = clean_visibility(cx.tcx.visibility(def_id)); + let ty_vis = cx.tcx.visibility(def_id); MacroItem(Macro { source: display_macro_source(cx, name, macro_def, def_id, ty_vis), }) @@ -2032,16 +2157,35 @@ fn clean_maybe_renamed_item<'tcx>( _ => unreachable!("not yet converted"), }; - vec![Item::from_def_id_and_parts(def_id, Some(name), kind, cx)] + let mut extra_attrs = Vec::new(); + if let Some(hir::Node::Item(use_node)) = + import_id.and_then(|hir_id| cx.tcx.hir().find(hir_id)) + { + // We get all the various imports' attributes. + get_all_import_attributes(use_node, cx.tcx, item.hir_id(), &mut extra_attrs); + } + + if !extra_attrs.is_empty() { + extra_attrs.extend_from_slice(inline::load_attrs(cx, def_id)); + let attrs = Attributes::from_ast(&extra_attrs); + let cfg = extra_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg); + + vec![Item::from_def_id_and_attrs_and_parts( + def_id, + Some(name), + kind, + Box::new(attrs), + cfg, + )] + } else { + vec![Item::from_def_id_and_parts(def_id, Some(name), kind, cx)] + } }) } fn clean_variant<'tcx>(variant: &hir::Variant<'tcx>, cx: &mut DocContext<'tcx>) -> Item { let kind = VariantItem(clean_variant_data(&variant.data, &variant.disr_expr, cx)); - let what_rustc_thinks = - Item::from_hir_id_and_parts(variant.id, Some(variant.ident.name), kind, cx); - // don't show `pub` for variants, which are always public - Item { visibility: Inherited, ..what_rustc_thinks } + Item::from_hir_id_and_parts(variant.hir_id, Some(variant.ident.name), kind, cx) } fn clean_impl<'tcx>( @@ -2114,6 +2258,7 @@ fn clean_extern_crate<'tcx>( } }); + let krate_owner_def_id = krate.owner_id.to_def_id(); if please_inline { let mut visited = FxHashSet::default(); @@ -2122,7 +2267,7 @@ fn clean_extern_crate<'tcx>( if let Some(items) = inline::try_inline( cx, cx.tcx.parent_module(krate.hir_id()).to_def_id(), - Some(krate.owner_id.to_def_id()), + Some(krate_owner_def_id), res, name, Some(attrs), @@ -2137,13 +2282,33 @@ fn clean_extern_crate<'tcx>( name: Some(name), attrs: Box::new(Attributes::from_ast(attrs)), item_id: crate_def_id.into(), - visibility: clean_visibility(ty_vis), kind: Box::new(ExternCrateItem { src: orig_name }), cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg), + inline_stmt_id: Some(krate_owner_def_id), }] } fn clean_use_statement<'tcx>( + import: &hir::Item<'tcx>, + name: Symbol, + path: &hir::UsePath<'tcx>, + kind: hir::UseKind, + cx: &mut DocContext<'tcx>, + inlined_names: &mut FxHashSet<(ItemType, Symbol)>, +) -> Vec { + let mut items = Vec::new(); + let hir::UsePath { segments, ref res, span } = *path; + for &res in res { + if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = res { + continue; + } + let path = hir::Path { segments, res, span }; + items.append(&mut clean_use_statement_inner(import, name, &path, kind, cx, inlined_names)); + } + items +} + +fn clean_use_statement_inner<'tcx>( import: &hir::Item<'tcx>, name: Symbol, path: &hir::Path<'tcx>, diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index 1bcb9fcd5..e96a9bab7 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -14,13 +14,14 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_hir::def_id::DefId; use rustc_middle::ty; +use thin_vec::ThinVec; use crate::clean; use crate::clean::GenericArgs as PP; use crate::clean::WherePredicate as WP; use crate::core::DocContext; -pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec) -> Vec { +pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec) -> ThinVec { // First, partition the where clause into its separate components. // // We use `FxIndexMap` so that the insertion order is preserved to prevent messing up to @@ -50,16 +51,13 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec) -> Vec { let Some((bounds, _)) = tybounds.get_mut(ty) else { return true }; let bound_params = bound_params .into_iter() - .map(|param| clean::GenericParamDef { - name: param.0, - kind: clean::GenericParamDefKind::Lifetime { outlives: Vec::new() }, - }) + .map(|param| clean::GenericParamDef::lifetime(param.0)) .collect(); merge_bounds(cx, bounds, bound_params, trait_did, name, rhs) }); // And finally, let's reassemble everything - let mut clauses = Vec::new(); + let mut clauses = ThinVec::with_capacity(lifetimes.len() + tybounds.len() + equalities.len()); clauses.extend( lifetimes.into_iter().map(|(lt, bounds)| WP::RegionPredicate { lifetime: lt, bounds }), ); @@ -98,9 +96,8 @@ pub(crate) fn merge_bounds( let last = trait_ref.trait_.segments.last_mut().expect("segments were empty"); trait_ref.generic_params.append(&mut bound_params); - // Since the parameters (probably) originate from `tcx.collect_*_late_bound_regions` which - // returns a hash set, sort them alphabetically to guarantee a stable and deterministic - // output (and to fully deduplicate them). + // Sort parameters (likely) originating from a hashset alphabetically to + // produce predictable output (and to allow for full deduplication). trait_ref.generic_params.sort_unstable_by(|p, q| p.name.as_str().cmp(q.name.as_str())); trait_ref.generic_params.dedup_by_key(|p| p.name); @@ -135,7 +132,7 @@ fn trait_is_same_or_supertrait(cx: &DocContext<'_>, child: DefId, trait_: DefId) .predicates .iter() .filter_map(|(pred, _)| { - if let ty::PredicateKind::Trait(pred) = pred.kind().skip_binder() { + if let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = pred.kind().skip_binder() { if pred.trait_ref.self_ty() == self_ty { Some(pred.def_id()) } else { None } } else { None diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index cd1f972dc..2590bb0df 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -24,7 +24,7 @@ use rustc_hir::{BodyId, Mutability}; use rustc_hir_analysis::check::intrinsic::intrinsic_operation_unsafety; use rustc_index::vec::IndexVec; use rustc_middle::ty::fast_reject::SimplifiedType; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, DefIdTree, TyCtxt, Visibility}; use rustc_session::Session; use rustc_span::hygiene::MacroKind; use rustc_span::source_map::DUMMY_SP; @@ -34,7 +34,6 @@ use rustc_target::abi::VariantIdx; use rustc_target::spec::abi::Abi; use crate::clean::cfg::Cfg; -use crate::clean::clean_visibility; use crate::clean::external_path; use crate::clean::inline::{self, print_inlined_const}; use crate::clean::utils::{is_literal_expr, print_const_expr, print_evaluated_const}; @@ -51,7 +50,6 @@ pub(crate) use self::Type::{ Array, BareFunction, BorrowedRef, DynTrait, Generic, ImplTrait, Infer, Primitive, QPath, RawPointer, Slice, Tuple, }; -pub(crate) use self::Visibility::{Inherited, Public}; #[cfg(test)] mod tests; @@ -117,7 +115,6 @@ impl From for ItemId { #[derive(Clone, Debug)] pub(crate) struct Crate { pub(crate) module: Item, - pub(crate) primitives: ThinVec<(DefId, PrimitiveType)>, /// Only here so that they can be filtered through the rustdoc passes. pub(crate) external_traits: Rc>>, } @@ -245,7 +242,9 @@ impl ExternalCrate { hir::ItemKind::Use(path, hir::UseKind::Single) if tcx.visibility(id.owner_id).is_public() => { - as_keyword(path.res.expect_non_local()) + path.res + .iter() + .find_map(|res| as_keyword(res.expect_non_local())) .map(|(_, prim)| (id.owner_id.to_def_id(), prim)) } _ => None, @@ -313,10 +312,11 @@ impl ExternalCrate { hir::ItemKind::Use(path, hir::UseKind::Single) if tcx.visibility(id.owner_id).is_public() => { - as_primitive(path.res.expect_non_local()).map(|(_, prim)| { + path.res + .iter() + .find_map(|res| as_primitive(res.expect_non_local())) // Pretend the primitive is local. - (id.owner_id.to_def_id(), prim) - }) + .map(|(_, prim)| (id.owner_id.to_def_id(), prim)) } _ => None, } @@ -348,12 +348,12 @@ pub(crate) struct Item { /// Optional because not every item has a name, e.g. impls. pub(crate) name: Option, pub(crate) attrs: Box, - pub(crate) visibility: Visibility, /// Information about this item that is specific to what kind of item it is. /// E.g., struct vs enum vs function. pub(crate) kind: Box, pub(crate) item_id: ItemId, - + /// This is the `DefId` of the `use` statement if the item was inlined. + pub(crate) inline_stmt_id: Option, pub(crate) cfg: Option>, } @@ -364,9 +364,7 @@ impl fmt::Debug for Item { let alternate = f.alternate(); // hand-picked fields that don't bloat the logs too much let mut fmt = f.debug_struct("Item"); - fmt.field("name", &self.name) - .field("visibility", &self.visibility) - .field("item_id", &self.item_id); + fmt.field("name", &self.name).field("item_id", &self.item_id); // allow printing the full item if someone really wants to if alternate { fmt.field("attrs", &self.attrs).field("kind", &self.kind).field("cfg", &self.cfg); @@ -388,6 +386,15 @@ pub(crate) fn rustc_span(def_id: DefId, tcx: TyCtxt<'_>) -> Span { )) } +fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + let parent = tcx.parent(def_id); + match tcx.def_kind(parent) { + DefKind::Struct | DefKind::Union => false, + DefKind::Variant => true, + parent_kind => panic!("unexpected parent kind: {:?}", parent_kind), + } +} + impl Item { pub(crate) fn stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option { self.item_id.as_def_id().and_then(|did| tcx.lookup_stability(did)) @@ -462,7 +469,6 @@ impl Item { name, kind, Box::new(Attributes::from_ast(ast_attrs)), - cx, ast_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg), ) } @@ -472,21 +478,18 @@ impl Item { name: Option, kind: ItemKind, attrs: Box, - cx: &mut DocContext<'_>, cfg: Option>, ) -> Item { trace!("name={:?}, def_id={:?} cfg={:?}", name, def_id, cfg); - // Primitives and Keywords are written in the source code as private modules. - // The modules need to be private so that nobody actually uses them, but the - // keywords and primitives that they are documenting are public. - let visibility = if matches!(&kind, ItemKind::KeywordItem | ItemKind::PrimitiveItem(..)) { - Visibility::Public - } else { - clean_visibility(cx.tcx.visibility(def_id)) - }; - - Item { item_id: def_id.into(), kind: Box::new(kind), name, attrs, visibility, cfg } + Item { + item_id: def_id.into(), + kind: Box::new(kind), + name, + attrs, + cfg, + inline_stmt_id: None, + } } /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined @@ -691,17 +694,61 @@ impl Item { asyncness: hir::IsAsync::NotAsync, } } - ItemKind::FunctionItem(_) | ItemKind::MethodItem(_, _) => { + ItemKind::FunctionItem(_) | ItemKind::MethodItem(_, _) | ItemKind::TyMethodItem(_) => { let def_id = self.item_id.as_def_id().unwrap(); build_fn_header(def_id, tcx, tcx.asyncness(def_id)) } - ItemKind::TyMethodItem(_) => { - build_fn_header(self.item_id.as_def_id().unwrap(), tcx, hir::IsAsync::NotAsync) - } _ => return None, }; Some(header) } + + /// Returns the visibility of the current item. If the visibility is "inherited", then `None` + /// is returned. + pub(crate) fn visibility(&self, tcx: TyCtxt<'_>) -> Option> { + let def_id = match self.item_id { + // Anything but DefId *shouldn't* matter, but return a reasonable value anyway. + ItemId::Auto { .. } | ItemId::Blanket { .. } => return None, + // Primitives and Keywords are written in the source code as private modules. + // The modules need to be private so that nobody actually uses them, but the + // keywords and primitives that they are documenting are public. + ItemId::Primitive(..) => return Some(Visibility::Public), + ItemId::DefId(def_id) => def_id, + }; + + match *self.kind { + // Explication on `ItemId::Primitive` just above. + ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) => return Some(Visibility::Public), + // Variant fields inherit their enum's visibility. + StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => { + return None; + } + // Variants always inherit visibility + VariantItem(..) => return None, + // Trait items inherit the trait's visibility + AssocConstItem(..) | TyAssocConstItem(..) | AssocTypeItem(..) | TyAssocTypeItem(..) + | TyMethodItem(..) | MethodItem(..) => { + let assoc_item = tcx.associated_item(def_id); + let is_trait_item = match assoc_item.container { + ty::TraitContainer => true, + ty::ImplContainer => { + // Trait impl items always inherit the impl's visibility -- + // we don't want to show `pub`. + tcx.impl_trait_ref(tcx.parent(assoc_item.def_id)).is_some() + } + }; + if is_trait_item { + return None; + } + } + _ => {} + } + let def_id = match self.inline_stmt_id { + Some(inlined) => inlined, + None => def_id, + }; + Some(tcx.visibility(def_id)) + } } #[derive(Clone, Debug)] @@ -747,7 +794,7 @@ pub(crate) enum ItemKind { /// A required associated type in a trait declaration. /// /// The bounds may be non-empty if there is a `where` clause. - TyAssocTypeItem(Box, Vec), + TyAssocTypeItem(Generics, Vec), /// An associated type in a trait impl or a provided one in a trait declaration. AssocTypeItem(Box, Vec), /// An item that has been stripped by a rustdoc pass @@ -1261,7 +1308,7 @@ impl Attributes { for attr in self.other_attrs.lists(sym::doc).filter(|a| a.has_name(sym::alias)) { if let Some(values) = attr.meta_item_list() { for l in values { - match l.literal().unwrap().kind { + match l.lit().unwrap().kind { ast::LitKind::Str(s, _) => { aliases.insert(s); } @@ -1392,6 +1439,10 @@ pub(crate) struct GenericParamDef { } impl GenericParamDef { + pub(crate) fn lifetime(name: Symbol) -> Self { + Self { name, kind: GenericParamDefKind::Lifetime { outlives: Vec::new() } } + } + pub(crate) fn is_synthetic_type_param(&self) -> bool { match self.kind { GenericParamDefKind::Lifetime { .. } | GenericParamDefKind::Const { .. } => false, @@ -1414,8 +1465,8 @@ impl GenericParamDef { // maybe use a Generic enum and use Vec? #[derive(Clone, Debug, Default)] pub(crate) struct Generics { - pub(crate) params: Vec, - pub(crate) where_predicates: Vec, + pub(crate) params: ThinVec, + pub(crate) where_predicates: ThinVec, } impl Generics { @@ -1576,7 +1627,7 @@ pub(crate) enum Type { /// An array type. /// /// The `String` field is a stringified version of the array's length parameter. - Array(Box, String), + Array(Box, Box), /// A raw pointer type: `*const i32`, `*mut i32` RawPointer(Mutability, Box), /// A reference type: `&i32`, `&'a mut Foo` @@ -2030,27 +2081,9 @@ impl From for PrimitiveType { } } -#[derive(Copy, Clone, Debug)] -pub(crate) enum Visibility { - /// `pub` - Public, - /// Visibility inherited from parent. - /// - /// For example, this is the visibility of private items and of enum variants. - Inherited, - /// `pub(crate)`, `pub(super)`, or `pub(in path::to::somewhere)` - Restricted(DefId), -} - -impl Visibility { - pub(crate) fn is_public(&self) -> bool { - matches!(self, Visibility::Public) - } -} - #[derive(Clone, Debug)] pub(crate) struct Struct { - pub(crate) struct_type: CtorKind, + pub(crate) ctor_kind: Option, pub(crate) generics: Generics, pub(crate) fields: Vec, } @@ -2078,7 +2111,7 @@ impl Union { /// only as a variant in an enum. #[derive(Clone, Debug)] pub(crate) struct VariantStruct { - pub(crate) struct_type: CtorKind, + pub(crate) ctor_kind: Option, pub(crate) fields: Vec, } @@ -2179,7 +2212,7 @@ impl Span { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) struct Path { pub(crate) res: Res, - pub(crate) segments: Vec, + pub(crate) segments: ThinVec, } impl Path { @@ -2329,7 +2362,7 @@ pub(crate) enum ConstantKind { /// /// Note that `ty::Const` includes generic parameters, and may not always be uniquely identified /// by a DefId. So this field must be different from `Extern`. - TyConst { expr: String }, + TyConst { expr: Box }, /// A constant (expression) that's not an item or associated item. These are usually found /// nested inside types (e.g., array lengths) or expressions (e.g., repeat counts), and also /// used to define explicit discriminant values for enum variants. @@ -2357,7 +2390,7 @@ impl Constant { impl ConstantKind { pub(crate) fn expr(&self, tcx: TyCtxt<'_>) -> String { match *self { - ConstantKind::TyConst { ref expr } => expr.clone(), + ConstantKind::TyConst { ref expr } => expr.to_string(), ConstantKind::Extern { def_id } => print_inlined_const(tcx, def_id), ConstantKind::Local { body, .. } | ConstantKind::Anonymous { body } => { print_const_expr(tcx, body) @@ -2541,14 +2574,15 @@ mod size_asserts { use super::*; use rustc_data_structures::static_assert_size; // tidy-alphabetical-start - static_assert_size!(Crate, 72); // frequently moved by-value + static_assert_size!(Crate, 64); // frequently moved by-value static_assert_size!(DocFragment, 32); - static_assert_size!(GenericArg, 48); + static_assert_size!(GenericArg, 32); static_assert_size!(GenericArgs, 32); static_assert_size!(GenericParamDef, 56); + static_assert_size!(Generics, 16); static_assert_size!(Item, 56); - static_assert_size!(ItemKind, 88); + static_assert_size!(ItemKind, 64); static_assert_size!(PathSegment, 40); - static_assert_size!(Type, 48); + static_assert_size!(Type, 32); // tidy-alphabetical-end } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 58767d3a4..246560bad 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -4,10 +4,10 @@ use crate::clean::render_macro_matchers::render_macro_matcher; use crate::clean::{ clean_doc_module, clean_middle_const, clean_middle_region, clean_middle_ty, inline, Crate, ExternalCrate, Generic, GenericArg, GenericArgs, ImportSource, Item, ItemKind, Lifetime, Path, - PathSegment, Primitive, PrimitiveType, Type, TypeBinding, Visibility, + PathSegment, Primitive, PrimitiveType, Term, Type, TypeBinding, TypeBindingKind, }; use crate::core::DocContext; -use crate::visit_lib::LibEmbargoVisitor; +use crate::html::format::visibility_to_src_with_space; use rustc_ast as ast; use rustc_ast::tokenstream::TokenTree; @@ -21,7 +21,7 @@ use rustc_middle::ty::{self, DefIdTree, TyCtxt}; use rustc_span::symbol::{kw, sym, Symbol}; use std::fmt::Write as _; use std::mem; -use thin_vec::ThinVec; +use thin_vec::{thin_vec, ThinVec}; #[cfg(test)] mod tests; @@ -31,7 +31,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { for &cnum in cx.tcx.crates(()) { // Analyze doc-reachability for extern items - LibEmbargoVisitor::new(cx).visit_lib(cnum); + crate::visit_lib::lib_embargo_visit_item(cx, cnum.as_def_id()); } // Clean the crate, translating the entire librustc_ast AST to one that is @@ -73,7 +73,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { })); } - Crate { module, primitives, external_traits: cx.external_traits.clone() } + Crate { module, external_traits: cx.external_traits.clone() } } pub(crate) fn substs_to_args<'tcx>( @@ -106,19 +106,19 @@ fn external_generic_args<'tcx>( ) -> GenericArgs { let args = substs_to_args(cx, substs, has_self); - if cx.tcx.fn_trait_kind_from_lang_item(did).is_some() { + if cx.tcx.fn_trait_kind_from_def_id(did).is_some() { let inputs = // The trait's first substitution is the one after self, if there is one. match substs.iter().nth(if has_self { 1 } else { 0 }).unwrap().expect_ty().kind() { ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(t, cx, None)).collect::>().into(), _ => return GenericArgs::AngleBracketed { args: args.into(), bindings }, }; - let output = None; - // FIXME(#20299) return type comes from a projection now - // match types[1].kind { - // ty::Tuple(ref v) if v.is_empty() => None, // -> () - // _ => Some(types[1].clean(cx)) - // }; + let output = bindings.into_iter().next().and_then(|binding| match binding.kind { + TypeBindingKind::Equality { term: Term::Type(ty) } if ty != Type::Tuple(Vec::new()) => { + Some(Box::new(ty)) + } + _ => None, + }); GenericArgs::Parenthesized { inputs, output } } else { GenericArgs::AngleBracketed { args: args.into(), bindings: bindings.into() } @@ -136,7 +136,7 @@ pub(super) fn external_path<'tcx>( let name = cx.tcx.item_name(did); Path { res: Res::Def(def_kind, did), - segments: vec![PathSegment { + segments: thin_vec![PathSegment { name, args: external_generic_args(cx, did, has_self, bindings, substs), }], @@ -242,19 +242,13 @@ pub(crate) fn print_const(cx: &DocContext<'_>, n: ty::Const<'_>) -> String { s } - _ => { - let mut s = n.to_string(); - // array lengths are obviously usize - if s.ends_with("_usize") { - let n = s.len() - "_usize".len(); - s.truncate(n); - if s.ends_with(": ") { - let n = s.len() - ": ".len(); - s.truncate(n); - } - } - s + // array lengths are obviously usize + ty::ConstKind::Value(ty::ValTree::Leaf(scalar)) + if *n.ty().kind() == ty::Uint(ty::UintTy::Usize) => + { + scalar.to_string() } + _ => n.to_string(), } } @@ -584,9 +578,9 @@ pub(super) fn display_macro_source( name: Symbol, def: &ast::MacroDef, def_id: DefId, - vis: Visibility, + vis: ty::Visibility, ) -> String { - let tts: Vec<_> = def.body.inner_tokens().into_trees().collect(); + let tts: Vec<_> = def.body.tokens.clone().into_trees().collect(); // Extract the spans of all matchers. They represent the "interface" of the macro. let matchers = tts.chunks(4).map(|arm| &arm[0]); @@ -596,14 +590,14 @@ pub(super) fn display_macro_source( if matchers.len() <= 1 { format!( "{}macro {}{} {{\n ...\n}}", - vis.to_src_with_space(cx.tcx, def_id), + visibility_to_src_with_space(Some(vis), cx.tcx, def_id), name, matchers.map(|matcher| render_macro_matcher(cx.tcx, matcher)).collect::(), ) } else { format!( "{}macro {} {{\n{}}}", - vis.to_src_with_space(cx.tcx, def_id), + visibility_to_src_with_space(Some(vis), cx.tcx, def_id), name, render_macro_arms(cx.tcx, matchers, ","), ) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 67ea39fb9..56b40d8c6 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -69,6 +69,8 @@ pub(crate) struct Options { pub(crate) input: PathBuf, /// The name of the crate being documented. pub(crate) crate_name: Option, + /// Whether or not this is a bin crate + pub(crate) bin_crate: bool, /// Whether or not this is a proc-macro crate pub(crate) proc_macro_crate: bool, /// How to format errors and warnings. @@ -176,6 +178,7 @@ impl fmt::Debug for Options { f.debug_struct("Options") .field("input", &self.input) .field("crate_name", &self.crate_name) + .field("bin_crate", &self.bin_crate) .field("proc_macro_crate", &self.proc_macro_crate) .field("error_format", &self.error_format) .field("libs", &self.libs) @@ -239,9 +242,6 @@ pub(crate) struct RenderOptions { pub(crate) default_settings: FxHashMap, /// If present, suffix added to CSS/JavaScript files when referencing them in generated pages. pub(crate) resource_suffix: String, - /// Whether to run the static CSS/JavaScript through a minifier when outputting them. `true` by - /// default. - pub(crate) enable_minification: bool, /// Whether to create an index page in the root of the output directory. If this is true but /// `enable_index_page` is None, generate a static listing of crates instead. pub(crate) enable_index_page: bool, @@ -329,7 +329,7 @@ impl Options { crate::usage("rustdoc"); return Err(0); } else if matches.opt_present("version") { - rustc_driver::version("rustdoc", matches); + rustc_driver::version!("rustdoc", matches); return Err(0); } @@ -416,10 +416,12 @@ impl Options { let to_check = matches.opt_strs("check-theme"); if !to_check.is_empty() { - let paths = match theme::load_css_paths(static_files::themes::LIGHT) { + let paths = match theme::load_css_paths( + std::str::from_utf8(static_files::STATIC_FILES.theme_light_css.bytes).unwrap(), + ) { Ok(p) => p, Err(e) => { - diag.struct_err(&e.to_string()).emit(); + diag.struct_err(e).emit(); return Err(1); } }; @@ -557,10 +559,12 @@ impl Options { let mut themes = Vec::new(); if matches.opt_present("theme") { - let paths = match theme::load_css_paths(static_files::themes::LIGHT) { + let paths = match theme::load_css_paths( + std::str::from_utf8(static_files::STATIC_FILES.theme_light_css.bytes).unwrap(), + ) { Ok(p) => p, Err(e) => { - diag.struct_err(&e.to_string()).emit(); + diag.struct_err(e).emit(); return Err(1); } }; @@ -666,6 +670,7 @@ impl Options { None => OutputFormat::default(), }; let crate_name = matches.opt_str("crate-name"); + let bin_crate = crate_types.contains(&CrateType::Executable); let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); let playground_url = matches.opt_str("playground-url"); let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from); @@ -675,7 +680,6 @@ impl Options { ModuleSorting::Alphabetical }; let resource_suffix = matches.opt_str("resource-suffix").unwrap_or_default(); - let enable_minification = !matches.opt_present("disable-minification"); let markdown_no_toc = matches.opt_present("markdown-no-toc"); let markdown_css = matches.opt_strs("markdown-css"); let markdown_playground_url = matches.opt_str("markdown-playground-url"); @@ -718,6 +722,7 @@ impl Options { rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); let options = Options { input, + bin_crate, proc_macro_crate, error_format, diagnostic_width, @@ -768,7 +773,6 @@ impl Options { extern_html_root_takes_precedence, default_settings, resource_suffix, - enable_minification, enable_index_page, index_page, static_root_path, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 3e5f42b7a..da0df596c 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -166,6 +166,7 @@ pub(crate) fn new_handler( unstable_opts.teach, diagnostic_width, false, + unstable_opts.track_diagnostics, ) .ui_testing(unstable_opts.ui_testing), ) @@ -184,6 +185,7 @@ pub(crate) fn new_handler( json_rendered, diagnostic_width, false, + unstable_opts.track_diagnostics, ) .ui_testing(unstable_opts.ui_testing), ) @@ -348,7 +350,6 @@ pub(crate) fn run_global_ctxt( let auto_traits = tcx.all_traits().filter(|&trait_def_id| tcx.trait_is_auto(trait_def_id)).collect(); - let effective_visibilities = tcx.effective_visibilities(()).map_id(Into::into); let mut ctxt = DocContext { tcx, @@ -361,7 +362,7 @@ pub(crate) fn run_global_ctxt( impl_trait_bounds: Default::default(), generated_synthetics: Default::default(), auto_traits, - cache: Cache::new(effective_visibilities, render_options.document_private), + cache: Cache::new(render_options.document_private), inlined: FxHashSet::default(), output_format, render_options, @@ -488,7 +489,7 @@ impl<'tcx> Visitor<'tcx> for EmitIgnoredResolutionErrors<'tcx> { self.tcx.hir() } - fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) { + fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) { debug!("visiting path {:?}", path); if path.res == Res::Err { // We have less context here than in rustc_resolve, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index db70029f6..81d9c4644 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -19,7 +19,7 @@ use rustc_span::edition::Edition; use rustc_span::source_map::SourceMap; use rustc_span::symbol::sym; use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP}; -use rustc_target::spec::TargetTriple; +use rustc_target::spec::{Target, TargetTriple}; use tempfile::Builder as TempFileBuilder; use std::env; @@ -293,6 +293,16 @@ struct UnusedExterns { unused_extern_names: Vec, } +fn add_exe_suffix(input: String, target: &TargetTriple) -> String { + let exe_suffix = match target { + TargetTriple::TargetTriple(_) => Target::expect_builtin(target).options.exe_suffix, + TargetTriple::TargetJson { contents, .. } => { + Target::from_json(contents.parse().unwrap()).unwrap().0.options.exe_suffix + } + }; + input + &exe_suffix +} + fn run_test( test: &str, crate_name: &str, @@ -313,7 +323,9 @@ fn run_test( let (test, line_offset, supports_color) = make_test(test, Some(crate_name), lang_string.test_harness, opts, edition, Some(test_id)); - let output_file = outdir.path().join("rust_out"); + // Make sure we emit well-formed executable names for our target. + let rust_out = add_exe_suffix("rust_out".to_owned(), &target); + let output_file = outdir.path().join(rust_out); let rustc_binary = rustdoc_options .test_builder @@ -551,6 +563,7 @@ pub(crate) fn make_test( false, Some(80), false, + false, ) .supports_color(); @@ -564,6 +577,7 @@ pub(crate) fn make_test( false, None, false, + false, ); // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser @@ -748,6 +762,7 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool { false, None, false, + false, ); let handler = Handler::with_emitter(false, None, Box::new(emitter)); @@ -1290,7 +1305,7 @@ impl<'a, 'hir, 'tcx> intravisit::Visitor<'hir> for HirCollector<'a, 'hir, 'tcx> } fn visit_variant(&mut self, v: &'hir hir::Variant<'_>) { - self.visit_testable(v.ident.to_string(), v.id, v.span, |this| { + self.visit_testable(v.ident.to_string(), v.hir_id, v.span, |this| { intravisit::walk_variant(this, v); }); } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index afe2264e8..d027fb6e8 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -2,7 +2,6 @@ use std::mem; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def_id::{CrateNum, DefId}; -use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::Symbol; @@ -15,6 +14,7 @@ use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; use crate::html::render::search_index::get_function_type_for_search; use crate::html::render::IndexItem; +use crate::visit_lib::RustdocEffectiveVisibilities; /// This cache is used to store information about the [`clean::Crate`] being /// rendered in order to provide more useful documentation. This contains @@ -78,7 +78,7 @@ pub(crate) struct Cache { // Note that external items for which `doc(hidden)` applies to are shown as // non-reachable while local items aren't. This is because we're reusing // the effective visibilities from the privacy check pass. - pub(crate) effective_visibilities: EffectiveVisibilities, + pub(crate) effective_visibilities: RustdocEffectiveVisibilities, /// The version of the crate being documented, if given from the `--crate-version` flag. pub(crate) crate_version: Option, @@ -132,11 +132,8 @@ struct CacheBuilder<'a, 'tcx> { } impl Cache { - pub(crate) fn new( - effective_visibilities: EffectiveVisibilities, - document_private: bool, - ) -> Self { - Cache { effective_visibilities, document_private, ..Cache::default() } + pub(crate) fn new(document_private: bool) -> Self { + Cache { document_private, ..Cache::default() } } /// Populates the `Cache` with more data. The returned `Crate` will be missing some data that was @@ -319,21 +316,28 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { let desc = item.doc_value().map_or_else(String::new, |x| { short_markdown_summary(x.as_str(), &item.link_names(self.cache)) }); - self.cache.search_index.push(IndexItem { - ty: item.type_(), - name: s.to_string(), - path: join_with_double_colon(path), - desc, - parent, - parent_idx: None, - search_type: get_function_type_for_search( - &item, - self.tcx, - clean_impl_generics(self.cache.parent_stack.last()).as_ref(), - self.cache, - ), - aliases: item.attrs.get_doc_aliases(), - }); + let ty = item.type_(); + let name = s.to_string(); + if ty != ItemType::StructField || u16::from_str_radix(&name, 10).is_err() { + // In case this is a field from a tuple struct, we don't add it into + // the search index because its name is something like "0", which is + // not useful for rustdoc search. + self.cache.search_index.push(IndexItem { + ty, + name, + path: join_with_double_colon(path), + desc, + parent, + parent_idx: None, + search_type: get_function_type_for_search( + &item, + self.tcx, + clean_impl_generics(self.cache.parent_stack.last()).as_ref(), + self.cache, + ), + aliases: item.attrs.get_doc_aliases(), + }); + } } } (Some(parent), None) if is_inherent_impl_item => { @@ -387,7 +391,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { || self .cache .effective_visibilities - .is_directly_public(item.item_id.expect_def_id()) + .is_directly_public(self.tcx, item.item_id.expect_def_id()) { self.cache.paths.insert( item.item_id.expect_def_id(), diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 92e7f2739..39e2a9022 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -107,10 +107,6 @@ impl Buffer { self.buffer } - pub(crate) fn insert_str(&mut self, idx: usize, s: &str) { - self.buffer.insert_str(idx, s); - } - pub(crate) fn push_str(&mut self, s: &str) { self.buffer.push_str(s); } @@ -659,7 +655,7 @@ pub(crate) fn href_with_root_path( } if !did.is_local() - && !cache.effective_visibilities.is_directly_public(did) + && !cache.effective_visibilities.is_directly_public(tcx, did) && !cache.document_private && !cache.primitive_locations.values().any(|&id| id == did) { @@ -1232,9 +1228,8 @@ impl clean::Arguments { ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { for (i, input) in self.values.iter().enumerate() { - if !input.name.is_empty() { - write!(f, "{}: ", input.name)?; - } + write!(f, "{}: ", input.name)?; + if f.alternate() { write!(f, "{:#}", input.type_.print(cx))?; } else { @@ -1367,10 +1362,8 @@ impl clean::FnDecl { args.push_str("const "); args_plain.push_str("const "); } - if !input.name.is_empty() { - write!(args, "{}: ", input.name); - write!(args_plain, "{}: ", input.name); - } + write!(args, "{}: ", input.name); + write!(args_plain, "{}: ", input.name); if f.alternate() { write!(args, "{:#}", input.type_.print(cx)); @@ -1420,87 +1413,84 @@ impl clean::FnDecl { } } -impl clean::Visibility { - pub(crate) fn print_with_space<'a, 'tcx: 'a>( - self, - item_did: ItemId, - cx: &'a Context<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { - use std::fmt::Write as _; - - let to_print: Cow<'static, str> = match self { - clean::Public => "pub ".into(), - clean::Inherited => "".into(), - clean::Visibility::Restricted(vis_did) => { - // FIXME(camelid): This may not work correctly if `item_did` is a module. - // However, rustdoc currently never displays a module's - // visibility, so it shouldn't matter. - let parent_module = find_nearest_parent_module(cx.tcx(), item_did.expect_def_id()); - - if vis_did.is_crate_root() { - "pub(crate) ".into() - } else if parent_module == Some(vis_did) { - // `pub(in foo)` where `foo` is the parent module - // is the same as no visibility modifier - "".into() - } else if parent_module - .and_then(|parent| find_nearest_parent_module(cx.tcx(), parent)) - == Some(vis_did) - { - "pub(super) ".into() - } else { - let path = cx.tcx().def_path(vis_did); - debug!("path={:?}", path); - // modified from `resolved_path()` to work with `DefPathData` - let last_name = path.data.last().unwrap().data.get_opt_name().unwrap(); - let anchor = anchor(vis_did, last_name, cx).to_string(); - - let mut s = "pub(in ".to_owned(); - for seg in &path.data[..path.data.len() - 1] { - let _ = write!(s, "{}::", seg.data.get_opt_name().unwrap()); - } - let _ = write!(s, "{}) ", anchor); - s.into() +pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>( + visibility: Option>, + item_did: ItemId, + cx: &'a Context<'tcx>, +) -> impl fmt::Display + 'a + Captures<'tcx> { + use std::fmt::Write as _; + + let to_print: Cow<'static, str> = match visibility { + None => "".into(), + Some(ty::Visibility::Public) => "pub ".into(), + Some(ty::Visibility::Restricted(vis_did)) => { + // FIXME(camelid): This may not work correctly if `item_did` is a module. + // However, rustdoc currently never displays a module's + // visibility, so it shouldn't matter. + let parent_module = find_nearest_parent_module(cx.tcx(), item_did.expect_def_id()); + + if vis_did.is_crate_root() { + "pub(crate) ".into() + } else if parent_module == Some(vis_did) { + // `pub(in foo)` where `foo` is the parent module + // is the same as no visibility modifier + "".into() + } else if parent_module.and_then(|parent| find_nearest_parent_module(cx.tcx(), parent)) + == Some(vis_did) + { + "pub(super) ".into() + } else { + let path = cx.tcx().def_path(vis_did); + debug!("path={:?}", path); + // modified from `resolved_path()` to work with `DefPathData` + let last_name = path.data.last().unwrap().data.get_opt_name().unwrap(); + let anchor = anchor(vis_did, last_name, cx).to_string(); + + let mut s = "pub(in ".to_owned(); + for seg in &path.data[..path.data.len() - 1] { + let _ = write!(s, "{}::", seg.data.get_opt_name().unwrap()); } + let _ = write!(s, "{}) ", anchor); + s.into() } - }; - display_fn(move |f| write!(f, "{}", to_print)) - } + } + }; + display_fn(move |f| write!(f, "{}", to_print)) +} - /// This function is the same as print_with_space, except that it renders no links. - /// It's used for macros' rendered source view, which is syntax highlighted and cannot have - /// any HTML in it. - pub(crate) fn to_src_with_space<'a, 'tcx: 'a>( - self, - tcx: TyCtxt<'tcx>, - item_did: DefId, - ) -> impl fmt::Display + 'a + Captures<'tcx> { - let to_print = match self { - clean::Public => "pub ".to_owned(), - clean::Inherited => String::new(), - clean::Visibility::Restricted(vis_did) => { - // FIXME(camelid): This may not work correctly if `item_did` is a module. - // However, rustdoc currently never displays a module's - // visibility, so it shouldn't matter. - let parent_module = find_nearest_parent_module(tcx, item_did); - - if vis_did.is_crate_root() { - "pub(crate) ".to_owned() - } else if parent_module == Some(vis_did) { - // `pub(in foo)` where `foo` is the parent module - // is the same as no visibility modifier - String::new() - } else if parent_module.and_then(|parent| find_nearest_parent_module(tcx, parent)) - == Some(vis_did) - { - "pub(super) ".to_owned() - } else { - format!("pub(in {}) ", tcx.def_path_str(vis_did)) - } +/// This function is the same as print_with_space, except that it renders no links. +/// It's used for macros' rendered source view, which is syntax highlighted and cannot have +/// any HTML in it. +pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>( + visibility: Option>, + tcx: TyCtxt<'tcx>, + item_did: DefId, +) -> impl fmt::Display + 'a + Captures<'tcx> { + let to_print = match visibility { + None => String::new(), + Some(ty::Visibility::Public) => "pub ".to_owned(), + Some(ty::Visibility::Restricted(vis_did)) => { + // FIXME(camelid): This may not work correctly if `item_did` is a module. + // However, rustdoc currently never displays a module's + // visibility, so it shouldn't matter. + let parent_module = find_nearest_parent_module(tcx, item_did); + + if vis_did.is_crate_root() { + "pub(crate) ".to_owned() + } else if parent_module == Some(vis_did) { + // `pub(in foo)` where `foo` is the parent module + // is the same as no visibility modifier + String::new() + } else if parent_module.and_then(|parent| find_nearest_parent_module(tcx, parent)) + == Some(vis_did) + { + "pub(super) ".to_owned() + } else { + format!("pub(in {}) ", tcx.def_path_str(vis_did)) } - }; - display_fn(move |f| f.write_str(&to_print)) - } + } + }; + display_fn(move |f| f.write_str(&to_print)) } pub(crate) trait PrintWithSpace { diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 5e28204b2..cd8c8c463 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -72,8 +72,12 @@ pub(crate) fn render_source_with_highlighting( line_numbers: Buffer, href_context: HrefContext<'_, '_, '_>, decoration_info: DecorationInfo, + extra: Option<&str>, ) { write_header(out, "", Some(line_numbers), Tooltip::None); + if let Some(extra) = extra { + out.push_str(extra); + } write_code(out, src, Some(href_context), Some(decoration_info)); write_footer(out, None); } @@ -358,7 +362,7 @@ impl Class { match self { Class::Comment => "comment", Class::DocComment => "doccomment", - Class::Attribute => "attribute", + Class::Attribute => "attr", Class::KeyWord => "kw", Class::RefKeyWord => "kw-2", Class::Self_(_) => "self", diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html index 4a5a3cf60..fced2eacd 100644 --- a/src/librustdoc/html/highlight/fixtures/sample.html +++ b/src/librustdoc/html/highlight/fixtures/sample.html @@ -3,16 +3,16 @@ .kw { color: #8959A8; } .kw-2, .prelude-ty { color: #4271AE; } .number, .string { color: #718C00; } -.self, .bool-val, .prelude-val, .attribute, .attribute .ident { color: #C82829; } +.self, .bool-val, .prelude-val, .attr, .attr .ident { color: #C82829; } .macro, .macro-nonterminal { color: #3E999F; } .lifetime { color: #B76514; } .question-mark { color: #ff9011; } -
#![crate_type = "lib"]
+
#![crate_type = "lib"]
 
 use std::path::{Path, PathBuf};
 
-#[cfg(target_os = "linux")]
+#[cfg(target_os = "linux")]
 #[cfg(target_os = "windows")]
 fn main() -> () {
     let foo = true && false || true;
@@ -23,7 +23,7 @@
     mac!(foo, &mut bar);
     assert!(self.length < N && index <= self.length);
     ::std::env::var("gateau").is_ok();
-    #[rustfmt::skip]
+    #[rustfmt::skip]
     let s:std::path::PathBuf = std::path::PathBuf::new();
     let mut s = String::new();
 
diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs
index a5e633df4..2c93b9a09 100644
--- a/src/librustdoc/html/highlight/tests.rs
+++ b/src/librustdoc/html/highlight/tests.rs
@@ -9,7 +9,7 @@ const STYLE: &str = r#"
 .kw { color: #8959A8; }
 .kw-2, .prelude-ty { color: #4271AE; }
 .number, .string { color: #718C00; }
-.self, .bool-val, .prelude-val, .attribute, .attribute .ident { color: #C82829; }
+.self, .bool-val, .prelude-val, .attr, .attr .ident { color: #C82829; }
 .macro, .macro-nonterminal { color: #3E999F; }
 .lifetime { color: #B76514; }
 .question-mark { color: #ff9011; }
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 7d6d4b71e..a60e7cb10 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -2,13 +2,14 @@ use std::path::PathBuf;
 
 use rustc_data_structures::fx::FxHashMap;
 
-use crate::error::Error;
 use crate::externalfiles::ExternalHtml;
 use crate::html::format::{Buffer, Print};
 use crate::html::render::{ensure_trailing_slash, StylePath};
 
 use askama::Template;
 
+use super::static_files::{StaticFiles, STATIC_FILES};
+
 #[derive(Clone)]
 pub(crate) struct Layout {
     pub(crate) logo: String,
@@ -34,17 +35,23 @@ pub(crate) struct Page<'a> {
 }
 
 impl<'a> Page<'a> {
-    pub(crate) fn get_static_root_path(&self) -> &str {
-        self.static_root_path.unwrap_or(self.root_path)
+    pub(crate) fn get_static_root_path(&self) -> String {
+        match self.static_root_path {
+            Some(s) => s.to_string(),
+            None => format!("{}static.files/", self.root_path),
+        }
     }
 }
 
 #[derive(Template)]
 #[template(path = "page.html")]
 struct PageLayout<'a> {
-    static_root_path: &'a str,
+    static_root_path: String,
     page: &'a Page<'a>,
     layout: &'a Layout,
+
+    files: &'static StaticFiles,
+
     themes: Vec,
     sidebar: String,
     content: String,
@@ -61,19 +68,17 @@ pub(crate) fn render(
 ) -> String {
     let static_root_path = page.get_static_root_path();
     let krate_with_trailing_slash = ensure_trailing_slash(&layout.krate).to_string();
-    let mut themes: Vec = style_files
-        .iter()
-        .map(StylePath::basename)
-        .collect::>()
-        .unwrap_or_default();
+    let mut themes: Vec = style_files.iter().map(|s| s.basename().unwrap()).collect();
     themes.sort();
-    let rustdoc_version = rustc_interface::util::version_str().unwrap_or("unknown version");
+
+    let rustdoc_version = rustc_interface::util::version_str!().unwrap_or("unknown version");
     let content = Buffer::html().to_display(t); // Note: This must happen before making the sidebar.
     let sidebar = Buffer::html().to_display(sidebar);
     PageLayout {
         static_root_path,
         page,
         layout,
+        files: &STATIC_FILES,
         themes,
         sidebar,
         content,
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 5733d1f9c..73690c86f 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -32,7 +32,7 @@ use crate::html::escape::Escape;
 use crate::html::format::{join_with_double_colon, Buffer};
 use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
 use crate::html::url_parts_builder::UrlPartsBuilder;
-use crate::html::{layout, sources};
+use crate::html::{layout, sources, static_files};
 use crate::scrape_examples::AllCallLocations;
 use crate::try_err;
 
@@ -69,11 +69,13 @@ pub(crate) struct Context<'tcx> {
     /// the source files are present in the html rendering, then this will be
     /// `true`.
     pub(crate) include_sources: bool,
+    /// Collection of all types with notable traits referenced in the current module.
+    pub(crate) types_with_notable_traits: FxHashSet,
 }
 
 // `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
 #[cfg(all(not(windows), target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(Context<'_>, 128);
+rustc_data_structures::static_assert_size!(Context<'_>, 160);
 
 /// Shared mutable state used in [`Context`] and elsewhere.
 pub(crate) struct SharedContext<'tcx> {
@@ -498,7 +500,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         );
 
         let (sender, receiver) = channel();
-        let mut scx = SharedContext {
+        let scx = SharedContext {
             tcx,
             src_root,
             local_sources,
@@ -521,19 +523,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             call_locations,
         };
 
-        // 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") });
-        scx.style_files.push(StylePath { path: PathBuf::from("dark.css") });
-        scx.style_files.push(StylePath { path: PathBuf::from("ayu.css") });
-
         let dst = output;
         scx.ensure_dir(&dst)?;
 
@@ -545,6 +534,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             deref_id_map: FxHashMap::default(),
             shared: Rc::new(scx),
             include_sources,
+            types_with_notable_traits: FxHashSet::default(),
         };
 
         if emit_crate {
@@ -573,6 +563,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             id_map: IdMap::new(),
             shared: Rc::clone(&self.shared),
             include_sources: self.include_sources,
+            types_with_notable_traits: FxHashSet::default(),
         }
     }
 
@@ -647,10 +638,11 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
                         \
                      \
                      \
-                     ",
-                    root_path = page.static_root_path.unwrap_or(""),
-                    suffix = page.resource_suffix,
+                         href=\"{static_root_path}{settings_css}\">\
+                     ",
+                    static_root_path = page.get_static_root_path(),
+                    settings_css = static_files::STATIC_FILES.settings_css,
+                    settings_js = static_files::STATIC_FILES.settings_js,
                 )
             },
             &shared.style_files,
@@ -815,6 +807,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
                 }
             }
         }
+
         Ok(())
     }
 
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 96c57c8c8..36d15ec3b 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -59,7 +59,7 @@ use rustc_span::{
     symbol::{sym, Symbol},
     BytePos, FileName, RealFileName,
 };
-use serde::ser::SerializeSeq;
+use serde::ser::{SerializeMap, SerializeSeq};
 use serde::{Serialize, Serializer};
 
 use crate::clean::{self, ItemId, RenderedLink, SelfTy};
@@ -70,8 +70,8 @@ use crate::formats::{AssocItemRender, Impl, RenderMode};
 use crate::html::escape::Escape;
 use crate::html::format::{
     href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
-    print_default_space, print_generic_bounds, print_where_clause, Buffer, Ending, HrefError,
-    PrintWithSpace,
+    print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space,
+    Buffer, Ending, HrefError, PrintWithSpace,
 };
 use crate::html::highlight;
 use crate::html::markdown::{
@@ -747,11 +747,12 @@ fn assoc_const(
     extra: &str,
     cx: &Context<'_>,
 ) {
+    let tcx = cx.tcx();
     write!(
         w,
         "{extra}{vis}const {name}: {ty}",
         extra = extra,
-        vis = it.visibility.print_with_space(it.item_id, cx),
+        vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
         href = assoc_href_attr(it, link, cx),
         name = it.name.as_ref().unwrap(),
         ty = ty.print(cx),
@@ -764,7 +765,7 @@ fn assoc_const(
         //        This hurts readability in this context especially when more complex expressions
         //        are involved and it doesn't add much of value.
         //        Find a way to print constants here without all that jazz.
-        write!(w, "{}", Escape(&default.value(cx.tcx()).unwrap_or_else(|| default.expr(cx.tcx()))));
+        write!(w, "{}", Escape(&default.value(tcx).unwrap_or_else(|| default.expr(tcx))));
     }
 }
 
@@ -802,17 +803,18 @@ fn assoc_method(
     d: &clean::FnDecl,
     link: AssocItemLink<'_>,
     parent: ItemType,
-    cx: &Context<'_>,
+    cx: &mut Context<'_>,
     render_mode: RenderMode,
 ) {
-    let header = meth.fn_header(cx.tcx()).expect("Trying to get header from a non-function item");
+    let tcx = cx.tcx();
+    let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item");
     let name = meth.name.as_ref().unwrap();
-    let vis = meth.visibility.print_with_space(meth.item_id, cx).to_string();
+    let vis = visibility_print_with_space(meth.visibility(tcx), meth.item_id, cx).to_string();
     // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
     // this condition.
     let constness = match render_mode {
         RenderMode::Normal => {
-            print_constness_with_space(&header.constness, meth.const_stability(cx.tcx()))
+            print_constness_with_space(&header.constness, meth.const_stability(tcx))
         }
         RenderMode::ForDeref { .. } => "",
     };
@@ -834,6 +836,8 @@ fn assoc_method(
         + name.as_str().len()
         + generics_len;
 
+    let notable_traits = d.output.as_return().and_then(|output| notable_traits_button(output, cx));
+
     let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
         header_len += 4;
         let indent_str = "    ";
@@ -843,10 +847,10 @@ fn assoc_method(
         render_attributes_in_code(w, meth);
         (0, "", Ending::Newline)
     };
-    w.reserve(header_len + "{".len() + "".len());
+    w.reserve(header_len + "{".len() + "".len());
     write!(
         w,
-        "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\
+        "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\
          {generics}{decl}{notable_traits}{where_clause}",
         indent = indent_str,
         vis = vis,
@@ -859,9 +863,9 @@ fn assoc_method(
         name = name,
         generics = g.print(cx),
         decl = d.full_print(header_len, indent, cx),
-        notable_traits = notable_traits_decl(d, cx),
+        notable_traits = notable_traits.unwrap_or_default(),
         where_clause = print_where_clause(g, cx, indent, end_newline),
-    )
+    );
 }
 
 /// Writes a span containing the versions at which an item became stable and/or const-stable. For
@@ -961,7 +965,7 @@ fn render_assoc_item(
     item: &clean::Item,
     link: AssocItemLink<'_>,
     parent: ItemType,
-    cx: &Context<'_>,
+    cx: &mut Context<'_>,
     render_mode: RenderMode,
 ) {
     match &*item.kind {
@@ -1067,7 +1071,7 @@ fn write_impl_section_heading(w: &mut Buffer, title: &str, id: &str) {
         w,
         "

\ {title}\ - \ + §\

" ); } @@ -1271,88 +1275,133 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> } } -fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String { - let mut out = Buffer::html(); +pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option { + let mut has_notable_trait = false; + + let did = ty.def_id(cx.cache())?; - if let Some((did, ty)) = decl.output.as_return().and_then(|t| Some((t.def_id(cx.cache())?, t))) + // Box has pass-through impls for Read, Write, Iterator, and Future when the + // boxed type implements one of those. We don't want to treat every Box return + // as being notably an Iterator (etc), though, so we exempt it. Pin has the same + // issue, with a pass-through impl for Future. + if Some(did) == cx.tcx().lang_items().owned_box() + || Some(did) == cx.tcx().lang_items().pin_type() { - // Box has pass-through impls for Read, Write, Iterator, and Future when the - // boxed type implements one of those. We don't want to treat every Box return - // as being notably an Iterator (etc), though, so we exempt it. Pin has the same - // issue, with a pass-through impl for Future. - if Some(did) == cx.tcx().lang_items().owned_box() - || Some(did) == cx.tcx().lang_items().pin_type() - { - return "".to_string(); - } - if let Some(impls) = cx.cache().impls.get(&did) { - for i in impls { - let impl_ = i.inner_impl(); - if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) + return None; + } + + if let Some(impls) = cx.cache().impls.get(&did) { + for i in impls { + let impl_ = i.inner_impl(); + if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) { + // Two different types might have the same did, + // without actually being the same. + continue; + } + if let Some(trait_) = &impl_.trait_ { + let trait_did = trait_.def_id(); + + if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) { - // Two different types might have the same did, - // without actually being the same. - continue; + has_notable_trait = true; } - if let Some(trait_) = &impl_.trait_ { - let trait_did = trait_.def_id(); - - if cx - .cache() - .traits - .get(&trait_did) - .map_or(false, |t| t.is_notable_trait(cx.tcx())) - { - if out.is_empty() { - write!( - &mut out, - "Notable traits for {}\ - ", - impl_.for_.print(cx) - ); - } + } + } + } - //use the "where" class here to make it small - write!( + if has_notable_trait { + cx.types_with_notable_traits.insert(ty.clone()); + Some(format!( + " ⓘ", + ty = Escape(&format!("{:#}", ty.print(cx))), + )) + } else { + None + } +} + +fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { + let mut out = Buffer::html(); + + let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this"); + + let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this"); + + for i in impls { + let impl_ = i.inner_impl(); + if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) { + // Two different types might have the same did, + // without actually being the same. + continue; + } + if let Some(trait_) = &impl_.trait_ { + let trait_did = trait_.def_id(); + + if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) { + if out.is_empty() { + write!( + &mut out, + "

Notable traits for {}

\ +
",
+                        impl_.for_.print(cx)
+                    );
+                }
+
+                //use the "where" class here to make it small
+                write!(
+                    &mut out,
+                    "{}",
+                    impl_.print(false, cx)
+                );
+                for it in &impl_.items {
+                    if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
+                        out.push_str("    ");
+                        let empty_set = FxHashSet::default();
+                        let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set);
+                        assoc_type(
                             &mut out,
-                            "{}",
-                            impl_.print(false, cx)
+                            it,
+                            &tydef.generics,
+                            &[], // intentionally leaving out bounds
+                            Some(&tydef.type_),
+                            src_link,
+                            0,
+                            cx,
                         );
-                        for it in &impl_.items {
-                            if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
-                                out.push_str("    ");
-                                let empty_set = FxHashSet::default();
-                                let src_link =
-                                    AssocItemLink::GotoSource(trait_did.into(), &empty_set);
-                                assoc_type(
-                                    &mut out,
-                                    it,
-                                    &tydef.generics,
-                                    &[], // intentionally leaving out bounds
-                                    Some(&tydef.type_),
-                                    src_link,
-                                    0,
-                                    cx,
-                                );
-                                out.push_str(";");
-                            }
-                        }
+                        out.push_str(";");
                     }
                 }
             }
         }
     }
-
-    if !out.is_empty() {
-        out.insert_str(
-            0,
-            "ⓘ\
-            ",
-        );
-        out.push_str("");
+    if out.is_empty() {
+        write!(&mut out, "
",); } - out.into_inner() + (format!("{:#}", ty.print(cx)), out.into_inner()) +} + +pub(crate) fn notable_traits_json<'a>( + tys: impl Iterator, + cx: &Context<'_>, +) -> String { + let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect(); + mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2)); + struct NotableTraitsMap(Vec<(String, String)>); + impl Serialize for NotableTraitsMap { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(self.0.len()))?; + for item in &self.0 { + map.serialize_entry(&item.0, &item.1)?; + } + map.end() + } + } + serde_json::to_string(&NotableTraitsMap(mp)) + .expect("serialize (string, string) -> json object cannot fail") } #[derive(Clone, Copy, Debug)] @@ -1487,7 +1536,7 @@ fn render_impl( render_rightside(w, cx, item, containing_item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "", id); + write!(w, "§", id); } w.write_str("

"); render_assoc_item( @@ -1513,7 +1562,7 @@ fn render_impl( render_rightside(w, cx, item, containing_item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "", id); + write!(w, "§", id); } w.write_str("

"); assoc_const( @@ -1538,7 +1587,7 @@ fn render_impl( write!(w, "
", id, item_type, in_trait_class); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "", id); + write!(w, "§", id); } w.write_str("

"); assoc_type( @@ -1564,7 +1613,7 @@ fn render_impl( ); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "", id); + write!(w, "§", id); } w.write_str("

"); assoc_type( @@ -1797,7 +1846,7 @@ pub(crate) fn render_impl_summary( }; write!(w, "
", id, aliases); render_rightside(w, cx, &i.impl_item, containing_item, RenderMode::Normal); - write!(w, "", id); + write!(w, "§", id); write!(w, "

"); if let Some(use_absolute) = use_absolute { @@ -2231,12 +2280,12 @@ fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clea let fields = get_struct_fields_name(&s.fields); if !fields.is_empty() { - match s.struct_type { - CtorKind::Fictive => { + match s.ctor_kind { + None => { print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter()); } - CtorKind::Fn => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"), - CtorKind::Const => {} + Some(CtorKind::Fn) => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"), + Some(CtorKind::Const) => {} } } @@ -2866,11 +2915,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite ); if line_ranges.len() > 1 { - write!(w, r#" "#); - } - - if needs_expansion { - write!(w, r#""#); + write!(w, r#" "#); } // Look for the example file in the source map if it exists, otherwise return a dummy span @@ -2892,9 +2937,6 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite })() .unwrap_or(rustc_span::DUMMY_SP); - // The root path is the inverse of Context::current - let root_path = vec!["../"; cx.current.len() - 1].join(""); - let mut decoration_info = FxHashMap::default(); decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]); decoration_info.insert("highlight", byte_ranges); @@ -2904,9 +2946,9 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite contents_subset, file_span, cx, - &root_path, + &cx.root_path(), highlight::DecorationInfo(decoration_info), - sources::SourceContext::Embedded { offset: line_min }, + sources::SourceContext::Embedded { offset: line_min, needs_expansion }, ); write!(w, ""); @@ -2915,14 +2957,23 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // The call locations are output in sequence, so that sequence needs to be determined. // Ideally the most "relevant" examples would be shown first, but there's no general algorithm - // for determining relevance. Instead, we prefer the smallest examples being likely the easiest to - // understand at a glance. + // for determining relevance. We instead proxy relevance with the following heuristics: + // 1. Code written to be an example is better than code not written to be an example, e.g. + // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo + // directory structure in Rustdoc, so we proxy this by prioritizing code that comes from + // a --crate-type bin. + // 2. Smaller examples are better than large examples. So we prioritize snippets that have + // the smallest number of lines in their enclosing item. + // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the + // ordering of examples from randomly changing between Rustdoc invocations. let ordered_locations = { - let sort_criterion = |(_, call_data): &(_, &CallData)| { + fn sort_criterion<'a>( + (_, call_data): &(&PathBuf, &'a CallData), + ) -> (bool, u32, &'a String) { // Use the first location because that's what the user will see initially let (lo, hi) = call_data.locations[0].enclosing_item.byte_span; - hi - lo - }; + (!call_data.is_bin, hi - lo, &call_data.display_name) + } let mut locs = call_locations.iter().collect::>(); locs.sort_by_key(sort_criterion); diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 632781736..acbe3f228 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -7,19 +7,20 @@ use rustc_hir::def_id::DefId; use rustc_middle::middle::stability; use rustc_middle::span_bug; use rustc_middle::ty::layout::LayoutError; -use rustc_middle::ty::{Adt, TyCtxt}; +use rustc_middle::ty::{self, Adt, TyCtxt}; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Symbol}; -use rustc_target::abi::{Layout, Primitive, TagEncoding, Variants}; +use rustc_target::abi::{LayoutS, Primitive, TagEncoding, VariantIdx, Variants}; use std::cmp::Ordering; use std::fmt; use std::rc::Rc; use super::{ collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference, - item_ty_to_section, notable_traits_decl, render_all_impls, render_assoc_item, - render_assoc_items, render_attributes_in_code, render_attributes_in_pre, render_impl, - render_rightside, render_stability_since_raw, AssocItemLink, Context, ImplRenderingParameters, + item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls, + render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre, + render_impl, render_rightside, render_stability_since_raw, + render_stability_since_raw_with_extra, AssocItemLink, Context, ImplRenderingParameters, }; use crate::clean; use crate::config::ModuleSorting; @@ -28,12 +29,12 @@ use crate::formats::{AssocItemRender, Impl, RenderMode}; use crate::html::escape::Escape; use crate::html::format::{ join_with_double_colon, print_abi_with_space, print_constness_with_space, print_where_clause, - Buffer, Ending, PrintWithSpace, + visibility_print_with_space, Buffer, Ending, PrintWithSpace, }; -use crate::html::highlight; use crate::html::layout::Page; use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine}; use crate::html::url_parts_builder::UrlPartsBuilder; +use crate::html::{highlight, static_files}; use askama::Template; use itertools::Itertools; @@ -52,8 +53,8 @@ struct PathComponent { #[derive(Template)] #[template(path = "print_item.html")] struct ItemVars<'a> { - page: &'a Page<'a>, static_root_path: &'a str, + clipboard_svg: &'static static_files::StaticFile, typ: &'a str, name: &'a str, item_type: &'a str, @@ -147,8 +148,8 @@ pub(super) fn print_item( }; let item_vars = ItemVars { - page, - static_root_path: page.get_static_root_path(), + static_root_path: &page.get_static_root_path(), + clipboard_svg: &static_files::STATIC_FILES.clipboard_svg, typ, name: item.name.as_ref().unwrap().as_str(), item_type: &item.type_().to_string(), @@ -183,6 +184,16 @@ pub(super) fn print_item( unreachable!(); } } + + // Render notable-traits.js used for all methods in this module. + if !cx.types_with_notable_traits.is_empty() { + write!( + buf, + r#""#, + notable_traits_json(cx.types_with_notable_traits.iter(), cx) + ); + cx.types_with_notable_traits.clear(); + } } /// For large structs, enums, unions, etc, determine whether to hide their fields @@ -318,6 +329,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: ); } + let tcx = cx.tcx(); match *myitem.kind { clean::ExternCrateItem { ref src } => { use crate::html::format::anchor; @@ -327,14 +339,14 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: Some(src) => write!( w, "
{}extern crate {} as {};", - myitem.visibility.print_with_space(myitem.item_id, cx), + visibility_print_with_space(myitem.visibility(tcx), myitem.item_id, cx), anchor(myitem.item_id.expect_def_id(), src, cx), myitem.name.unwrap(), ), None => write!( w, "
{}extern crate {};", - myitem.visibility.print_with_space(myitem.item_id, cx), + visibility_print_with_space(myitem.visibility(tcx), myitem.item_id, cx), anchor(myitem.item_id.expect_def_id(), myitem.name.unwrap(), cx), ), } @@ -384,7 +396,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
\ {stab_tags_before}{stab_tags}{stab_tags_after}", stab = stab.unwrap_or_default(), - vis = myitem.visibility.print_with_space(myitem.item_id, cx), + vis = visibility_print_with_space(myitem.visibility(tcx), myitem.item_id, cx), imp = import.print(cx), ); w.write_str(ITEM_TABLE_ROW_CLOSE); @@ -408,8 +420,8 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: let stab = myitem.stability_class(cx.tcx()); let add = if stab.is_some() { " " } else { "" }; - let visibility_emoji = match myitem.visibility { - clean::Visibility::Restricted(_) => { + let visibility_emoji = match myitem.visibility(tcx) { + Some(ty::Visibility::Restricted(_)) => { " ðŸ”’ " } _ => "", @@ -496,12 +508,13 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> } fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &clean::Function) { - let header = it.fn_header(cx.tcx()).expect("printing a function which isn't a function"); - let constness = print_constness_with_space(&header.constness, it.const_stability(cx.tcx())); + let tcx = cx.tcx(); + let header = it.fn_header(tcx).expect("printing a function which isn't a function"); + let constness = print_constness_with_space(&header.constness, it.const_stability(tcx)); let unsafety = header.unsafety.print_with_space(); let abi = print_abi_with_space(header.abi).to_string(); let asyncness = header.asyncness.print_with_space(); - let visibility = it.visibility.print_with_space(it.item_id, cx).to_string(); + let visibility = visibility_print_with_space(it.visibility(tcx), it.item_id, cx).to_string(); let name = it.name.unwrap(); let generics_len = format!("{:#}", f.generics.print(cx)).len(); @@ -514,6 +527,9 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle + name.as_str().len() + generics_len; + let notable_traits = + f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx)); + wrap_into_item_decl(w, |w| { wrap_item(w, "fn", |w| { render_attributes_in_pre(w, it, ""); @@ -531,14 +547,15 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle generics = f.generics.print(cx), where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline), decl = f.decl.full_print(header_len, 0, cx), - notable_traits = notable_traits_decl(&f.decl, cx), + notable_traits = notable_traits.unwrap_or_default(), ); }); }); - document(w, cx, it, None, HeadingOffset::H2) + document(w, cx, it, None, HeadingOffset::H2); } fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) { + let tcx = cx.tcx(); let bounds = bounds(&t.bounds, false, cx); let required_types = t.items.iter().filter(|m| m.is_ty_associated_type()).collect::>(); let provided_types = t.items.iter().filter(|m| m.is_associated_type()).collect::>(); @@ -549,8 +566,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: let count_types = required_types.len() + provided_types.len(); let count_consts = required_consts.len() + provided_consts.len(); let count_methods = required_methods.len() + provided_methods.len(); - let must_implement_one_of_functions = - cx.tcx().trait_def(t.def_id).must_implement_one_of.clone(); + let must_implement_one_of_functions = tcx.trait_def(t.def_id).must_implement_one_of.clone(); // Output the trait definition wrap_into_item_decl(w, |w| { @@ -559,9 +575,9 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: write!( w, "{}{}{}trait {}{}{}", - it.visibility.print_with_space(it.item_id, cx), - t.unsafety(cx.tcx()).print_with_space(), - if t.is_auto(cx.tcx()) { "auto " } else { "" }, + visibility_print_with_space(it.visibility(tcx), it.item_id, cx), + t.unsafety(tcx).print_with_space(), + if t.is_auto(tcx) { "auto " } else { "" }, it.name.unwrap(), t.generics.print(cx), bounds @@ -701,7 +717,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: write!( w, "

\ - {1}\ + {1}§\

{2}", id, title, extra_content ) @@ -1020,7 +1036,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } let extern_crates = extern_crates .into_iter() - .map(|cnum| cx.shared.tcx.crate_name(cnum).to_string()) + .map(|cnum| tcx.crate_name(cnum).to_string()) .collect::>() .join(","); let (extern_before, extern_after) = @@ -1084,7 +1100,7 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) { wrap_item(w, "typedef", |w| { render_attributes_in_pre(w, it, ""); - write!(w, "{}", it.visibility.print_with_space(it.item_id, cx)); + write!(w, "{}", visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx)); write!( w, "type {}{}{where_clause} = {type_};", @@ -1131,7 +1147,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: write!( w, "

\ - Fields\ + Fields§\

" ); for (field, ty) in fields { @@ -1140,7 +1156,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: write!( w, "\ - \ + §\ {name}: {ty}\ ", id = id, @@ -1173,6 +1189,7 @@ fn print_tuple_struct_fields(w: &mut Buffer, cx: &Context<'_>, s: &[clean::Item] } fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) { + let tcx = cx.tcx(); let count_variants = e.variants().count(); wrap_into_item_decl(w, |w| { wrap_item(w, "enum", |w| { @@ -1180,7 +1197,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: write!( w, "{}enum {}{}", - it.visibility.print_with_space(it.item_id, cx), + visibility_print_with_space(it.visibility(tcx), it.item_id, cx), it.name.unwrap(), e.generics.print(cx), ); @@ -1215,7 +1232,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w, v, None, - s.struct_type, + s.ctor_kind, &s.fields, " ", false, @@ -1245,35 +1262,35 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: write!( w, "

\ - Variants{}\ + Variants{}§\

", document_non_exhaustive_header(it) ); document_non_exhaustive(w, it); + write!(w, "
"); for variant in e.variants() { let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); write!( w, - "

\ - \ - {name}", + "
\ + §", id = id, - name = variant.name.unwrap() ); + render_stability_since_raw_with_extra( + w, + variant.stable_since(tcx), + variant.const_stability(tcx), + it.stable_since(tcx), + it.const_stable_since(tcx), + " rightside", + ); + write!(w, "

{name}", name = variant.name.unwrap()); if let clean::VariantItem(clean::Variant::Tuple(ref s)) = *variant.kind { w.write_str("("); print_tuple_struct_fields(w, cx, s); w.write_str(")"); } - w.write_str(""); - render_stability_since_raw( - w, - variant.stable_since(cx.tcx()), - variant.const_stability(cx.tcx()), - it.stable_since(cx.tcx()), - it.const_stable_since(cx.tcx()), - ); - w.write_str("

"); + w.write_str("

"); use crate::clean::Variant; @@ -1307,8 +1324,8 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: write!( w, "
\ - \ - \ + \ + §\ {f}: {t}\ ", id = id, @@ -1326,6 +1343,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: document(w, cx, variant, Some(it), HeadingOffset::H4); } + write!(w, "
"); } let def_id = it.item_id.expect_def_id(); render_assoc_items(w, cx, it, def_id, AssocItemRender::All); @@ -1389,12 +1407,13 @@ fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) { wrap_into_item_decl(w, |w| { wrap_item(w, "const", |w| { + let tcx = cx.tcx(); render_attributes_in_code(w, it); write!( w, "{vis}const {name}: {typ}", - vis = it.visibility.print_with_space(it.item_id, cx), + vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx), name = it.name.unwrap(), typ = c.type_.print(cx), ); @@ -1408,9 +1427,9 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle // ` = 100i32;` // instead? - let value = c.value(cx.tcx()); - let is_literal = c.is_literal(cx.tcx()); - let expr = c.expr(cx.tcx()); + let value = c.value(tcx); + let is_literal = c.is_literal(tcx); + let expr = c.expr(tcx); if value.is_some() || is_literal { write!(w, " = {expr};", expr = Escape(&expr)); } else { @@ -1439,7 +1458,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean wrap_into_item_decl(w, |w| { wrap_item(w, "struct", |w| { render_attributes_in_code(w, it); - render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx); + render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); }); }); @@ -1453,14 +1472,14 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean _ => None, }) .peekable(); - if let CtorKind::Fictive | CtorKind::Fn = s.struct_type { + if let None | Some(CtorKind::Fn) = s.ctor_kind { if fields.peek().is_some() { write!( w, "

\ - {}{}\ + {}{}§\

", - if let CtorKind::Fictive = s.struct_type { "Fields" } else { "Tuple Fields" }, + if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, document_non_exhaustive_header(it) ); document_non_exhaustive(w, it); @@ -1471,7 +1490,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean write!( w, "\ - \ + §\ {name}: {ty}\ ", item_type = ItemType::StructField, @@ -1495,7 +1514,7 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean write!( w, "{vis}static {mutability}{name}: {typ}", - vis = it.visibility.print_with_space(it.item_id, cx), + vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), mutability = s.mutability.print_with_space(), name = it.name.unwrap(), typ = s.type_.print(cx) @@ -1513,7 +1532,7 @@ fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { write!( w, " {}type {};\n}}", - it.visibility.print_with_space(it.item_id, cx), + visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), it.name.unwrap(), ); }); @@ -1666,7 +1685,13 @@ fn render_union( tab: &str, cx: &Context<'_>, ) { - write!(w, "{}union {}", it.visibility.print_with_space(it.item_id, cx), it.name.unwrap(),); + let tcx = cx.tcx(); + write!( + w, + "{}union {}", + visibility_print_with_space(it.visibility(tcx), it.item_id, cx), + it.name.unwrap(), + ); let where_displayed = g .map(|g| { @@ -1693,7 +1718,7 @@ fn render_union( write!( w, " {}{}: {},\n{}", - field.visibility.print_with_space(field.item_id, cx), + visibility_print_with_space(field.visibility(tcx), field.item_id, cx), field.name.unwrap(), ty.print(cx), tab @@ -1714,16 +1739,17 @@ fn render_struct( w: &mut Buffer, it: &clean::Item, g: Option<&clean::Generics>, - ty: CtorKind, + ty: Option, fields: &[clean::Item], tab: &str, structhead: bool, cx: &Context<'_>, ) { + let tcx = cx.tcx(); write!( w, "{}{}{}", - it.visibility.print_with_space(it.item_id, cx), + visibility_print_with_space(it.visibility(tcx), it.item_id, cx), if structhead { "struct " } else { "" }, it.name.unwrap() ); @@ -1731,7 +1757,7 @@ fn render_struct( write!(w, "{}", g.print(cx)) } match ty { - CtorKind::Fictive => { + None => { let where_diplayed = g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false); // If there wasn't a `where` clause, we add a whitespace. @@ -1753,7 +1779,7 @@ fn render_struct( w, "\n{} {}{}: {},", tab, - field.visibility.print_with_space(field.item_id, cx), + visibility_print_with_space(field.visibility(tcx), field.item_id, cx), field.name.unwrap(), ty.print(cx), ); @@ -1773,7 +1799,7 @@ fn render_struct( } w.write_str("}"); } - CtorKind::Fn => { + Some(CtorKind::Fn) => { w.write_str("("); for (i, field) in fields.iter().enumerate() { if i > 0 { @@ -1785,7 +1811,7 @@ fn render_struct( write!( w, "{}{}", - field.visibility.print_with_space(field.item_id, cx), + visibility_print_with_space(field.visibility(tcx), field.item_id, cx), ty.print(cx), ) } @@ -1801,7 +1827,7 @@ fn render_struct( w.write_str(";"); } } - CtorKind::Const => { + Some(CtorKind::Const) => { // Needed for PhantomData. if let Some(g) = g { write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline)); @@ -1866,11 +1892,11 @@ fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) { } fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) { - fn write_size_of_layout(w: &mut Buffer, layout: Layout<'_>, tag_size: u64) { - if layout.abi().is_unsized() { + fn write_size_of_layout(w: &mut Buffer, layout: &LayoutS, tag_size: u64) { + if layout.abi.is_unsized() { write!(w, "(unsized)"); } else { - let bytes = layout.size().bytes() - tag_size; + let bytes = layout.size.bytes() - tag_size; write!(w, "{size} byte{pl}", size = bytes, pl = if bytes == 1 { "" } else { "s" },); } } @@ -1882,7 +1908,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) { writeln!( w, "

\ - Layout

" + Layout§

" ); writeln!(w, "
"); @@ -1901,7 +1927,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) { chapter for details on type layout guarantees.

" ); w.write_str("

Size: "); - write_size_of_layout(w, ty_layout.layout, 0); + write_size_of_layout(w, &ty_layout.layout.0, 0); writeln!(w, "

"); if let Variants::Multiple { variants, tag, tag_encoding, .. } = &ty_layout.layout.variants() @@ -1927,7 +1953,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) { for (index, layout) in variants.iter_enumerated() { let name = adt.variant(index).name; write!(w, "
  • {name}: ", name = name); - write_size_of_layout(w, *layout, tag_size); + write_size_of_layout(w, layout, tag_size); writeln!(w, "
  • "); } w.write_str(""); diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 151ec2b28..4514894ca 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -140,7 +140,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { self.tcx.hir() } - fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) { + fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) { if self.handle_macro(path.span) { return; } @@ -190,12 +190,4 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } intravisit::walk_expr(self, expr); } - - fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) { - if self.handle_macro(path.span) { - return; - } - self.handle_path(path); - intravisit::walk_use(self, path, id); - } } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 85f63c985..94d8a9fec 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -1,10 +1,8 @@ -use std::ffi::OsStr; use std::fs::{self, File}; use std::io::prelude::*; use std::io::{self, BufReader}; -use std::path::{Component, Path, PathBuf}; +use std::path::{Component, Path}; use std::rc::Rc; -use std::sync::LazyLock as Lazy; use itertools::Itertools; use rustc_data_structures::flock; @@ -20,123 +18,20 @@ use crate::error::Error; use crate::html::{layout, static_files}; use crate::{try_err, try_none}; -static FILES_UNVERSIONED: Lazy> = Lazy::new(|| { - map! { - "FiraSans-Regular.woff2" => static_files::fira_sans::REGULAR, - "FiraSans-Medium.woff2" => static_files::fira_sans::MEDIUM, - "FiraSans-LICENSE.txt" => static_files::fira_sans::LICENSE, - "SourceSerif4-Regular.ttf.woff2" => static_files::source_serif_4::REGULAR, - "SourceSerif4-Bold.ttf.woff2" => static_files::source_serif_4::BOLD, - "SourceSerif4-It.ttf.woff2" => static_files::source_serif_4::ITALIC, - "SourceSerif4-LICENSE.md" => static_files::source_serif_4::LICENSE, - "SourceCodePro-Regular.ttf.woff2" => static_files::source_code_pro::REGULAR, - "SourceCodePro-Semibold.ttf.woff2" => static_files::source_code_pro::SEMIBOLD, - "SourceCodePro-It.ttf.woff2" => static_files::source_code_pro::ITALIC, - "SourceCodePro-LICENSE.txt" => static_files::source_code_pro::LICENSE, - "NanumBarunGothic.ttf.woff2" => static_files::nanum_barun_gothic::REGULAR, - "NanumBarunGothic-LICENSE.txt" => static_files::nanum_barun_gothic::LICENSE, - "LICENSE-MIT.txt" => static_files::LICENSE_MIT, - "LICENSE-APACHE.txt" => static_files::LICENSE_APACHE, - "COPYRIGHT.txt" => static_files::COPYRIGHT, - } -}); - -enum SharedResource<'a> { - /// This file will never change, no matter what toolchain is used to build it. - /// - /// It does not have a resource suffix. - Unversioned { name: &'static str }, - /// This file may change depending on the toolchain. - /// - /// It has a resource suffix. - ToolchainSpecific { basename: &'static str }, - /// This file may change for any crate within a build, or based on the CLI arguments. - /// - /// This differs from normal invocation-specific files because it has a resource suffix. - InvocationSpecific { basename: &'a str }, -} - -impl SharedResource<'_> { - fn extension(&self) -> Option<&OsStr> { - use SharedResource::*; - match self { - Unversioned { name } - | ToolchainSpecific { basename: name } - | InvocationSpecific { basename: name } => Path::new(name).extension(), - } - } - - fn path(&self, cx: &Context<'_>) -> PathBuf { - match self { - SharedResource::Unversioned { name } => cx.dst.join(name), - SharedResource::ToolchainSpecific { basename } => cx.suffix_path(basename), - SharedResource::InvocationSpecific { basename } => cx.suffix_path(basename), - } - } - - fn should_emit(&self, emit: &[EmitType]) -> bool { - if emit.is_empty() { - return true; - } - let kind = match self { - SharedResource::Unversioned { .. } => EmitType::Unversioned, - SharedResource::ToolchainSpecific { .. } => EmitType::Toolchain, - SharedResource::InvocationSpecific { .. } => EmitType::InvocationSpecific, - }; - emit.contains(&kind) - } -} - -impl Context<'_> { - fn suffix_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 write_shared( - &self, - resource: SharedResource<'_>, - contents: impl 'static + Send + AsRef<[u8]>, - emit: &[EmitType], - ) -> Result<(), Error> { - if resource.should_emit(emit) { - self.shared.fs.write(resource.path(self), contents) - } else { - Ok(()) - } - } - - fn write_minify( - &self, - resource: SharedResource<'_>, - contents: impl 'static + Send + AsRef + AsRef<[u8]>, - minify: bool, - emit: &[EmitType], - ) -> Result<(), Error> { - if minify { - let contents = contents.as_ref(); - let contents = if resource.extension() == Some(OsStr::new("css")) { - minifier::css::minify(contents) - .map_err(|e| { - Error::new(format!("failed to minify CSS file: {}", e), resource.path(self)) - })? - .to_string() - } else { - minifier::js::minify(contents).to_string() - }; - self.write_shared(resource, contents, emit) - } else { - self.write_shared(resource, contents, emit) - } - } -} - +/// Rustdoc writes out two kinds of shared files: +/// - Static files, which are embedded in the rustdoc binary and are written with a +/// filename that includes a hash of their contents. These will always have a new +/// URL if the contents change, so they are safe to cache with the +/// `Cache-Control: immutable` directive. They are written under the static.files/ +/// directory and are written when --emit-type is empty (default) or contains +/// "toolchain-specific". If using the --static-root-path flag, it should point +/// to a URL path prefix where each of these filenames can be fetched. +/// - Invocation specific files. These are generated based on the crate(s) being +/// documented. Their filenames need to be predictable without knowing their +/// contents, so they do not include a hash in their filename and are not safe to +/// cache with `Cache-Control: immutable`. They include the contents of the +/// --resource-suffix flag and are emitted when --emit-type is empty (default) +/// or contains "invocation-specific". pub(super) fn write_shared( cx: &mut Context<'_>, krate: &Crate, @@ -149,139 +44,52 @@ pub(super) fn write_shared( let lock_file = cx.dst.join(".lock"); let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file); - // Minified resources are usually toolchain resources. If they're not, they should use `cx.write_minify` directly. - fn write_minify( - basename: &'static str, - contents: impl 'static + Send + AsRef + AsRef<[u8]>, - cx: &Context<'_>, - options: &RenderOptions, - ) -> Result<(), Error> { - cx.write_minify( - SharedResource::ToolchainSpecific { basename }, - contents, - options.enable_minification, - &options.emit, - ) - } - - // Toolchain resources should never be dynamic. - let write_toolchain = |p: &'static _, c: &'static _| { - cx.write_shared(SharedResource::ToolchainSpecific { basename: p }, c, &options.emit) - }; - - // Crate resources should always be dynamic. - let write_crate = |p: &_, make_content: &dyn Fn() -> Result, Error>| { + // InvocationSpecific resources should always be dynamic. + let write_invocation_specific = |p: &str, make_content: &dyn Fn() -> Result, Error>| { let content = make_content()?; - cx.write_shared(SharedResource::InvocationSpecific { basename: p }, content, &options.emit) + if options.emit.is_empty() || options.emit.contains(&EmitType::InvocationSpecific) { + let output_filename = static_files::suffix_path(p, &cx.shared.resource_suffix); + cx.shared.fs.write(cx.dst.join(output_filename), content) + } else { + Ok(()) + } }; - // Given "foo.svg", return e.g. "url(\"foo1.58.0.svg\")" - fn ver_url(cx: &Context<'_>, basename: &'static str) -> String { - format!( - "url(\"{}\")", - SharedResource::ToolchainSpecific { basename } - .path(cx) - .file_name() - .unwrap() - .to_str() - .unwrap() - ) - } - - // We use the AUTOREPLACE mechanism to inject into our static JS and CSS certain - // values that are only known at doc build time. Since this mechanism is somewhat - // surprising when reading the code, please limit it to rustdoc.css. - write_minify( - "rustdoc.css", - static_files::RUSTDOC_CSS - .replace( - "/* AUTOREPLACE: */url(\"toggle-minus.svg\")", - &ver_url(cx, "toggle-minus.svg"), - ) - .replace("/* AUTOREPLACE: */url(\"toggle-plus.svg\")", &ver_url(cx, "toggle-plus.svg")) - .replace("/* AUTOREPLACE: */url(\"down-arrow.svg\")", &ver_url(cx, "down-arrow.svg")), - cx, - options, - )?; - - // 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("settings.css", static_files::SETTINGS_CSS, cx, options)?; - write_minify("noscript.css", static_files::NOSCRIPT_CSS, cx, options)?; - - // 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(); + cx.shared + .fs + .create_dir_all(cx.dst.join("static.files")) + .map_err(|e| PathError::new(e, "static.files"))?; + // Handle added third-party themes for entry in &cx.shared.style_files { let theme = entry.basename()?; let extension = try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); - // Handle the official themes - match theme.as_str() { - "light" => write_minify("light.css", static_files::themes::LIGHT, cx, options)?, - "dark" => write_minify("dark.css", static_files::themes::DARK, cx, options)?, - "ayu" => write_minify("ayu.css", static_files::themes::AYU, cx, options)?, - _ => { - // Handle added third-party themes - let filename = format!("{}.{}", theme, extension); - write_crate(&filename, &|| Ok(try_err!(fs::read(&entry.path), &entry.path)))?; - } - }; - - themes.insert(theme.to_owned()); - } - - if (*cx.shared).layout.logo.is_empty() { - write_toolchain("rust-logo.svg", static_files::RUST_LOGO_SVG)?; - } - if (*cx.shared).layout.favicon.is_empty() { - write_toolchain("favicon.svg", static_files::RUST_FAVICON_SVG)?; - write_toolchain("favicon-16x16.png", static_files::RUST_FAVICON_PNG_16)?; - write_toolchain("favicon-32x32.png", static_files::RUST_FAVICON_PNG_32)?; - } - write_toolchain("wheel.svg", static_files::WHEEL_SVG)?; - write_toolchain("clipboard.svg", static_files::CLIPBOARD_SVG)?; - write_toolchain("down-arrow.svg", static_files::DOWN_ARROW_SVG)?; - write_toolchain("toggle-minus.svg", static_files::TOGGLE_MINUS_PNG)?; - write_toolchain("toggle-plus.svg", static_files::TOGGLE_PLUS_PNG)?; - - let mut themes: Vec<&String> = themes.iter().collect(); - themes.sort(); - - write_minify("main.js", static_files::MAIN_JS, cx, options)?; - write_minify("search.js", static_files::SEARCH_JS, cx, options)?; - write_minify("settings.js", static_files::SETTINGS_JS, cx, options)?; - - if cx.include_sources { - write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT, cx, options)?; - } - - write_minify("storage.js", static_files::STORAGE_JS, cx, options)?; + // Skip the official themes. They are written below as part of STATIC_FILES_LIST. + if matches!(theme.as_str(), "light" | "dark" | "ayu") { + continue; + } - if cx.shared.layout.scrape_examples_extension { - cx.write_minify( - SharedResource::InvocationSpecific { basename: "scrape-examples.js" }, - static_files::SCRAPE_EXAMPLES_JS, - options.enable_minification, - &options.emit, - )?; + let bytes = try_err!(fs::read(&entry.path), &entry.path); + let filename = format!("{}{}.{}", theme, cx.shared.resource_suffix, extension); + cx.shared.fs.write(cx.dst.join(filename), bytes)?; } + // When the user adds their own CSS files with --extend-css, we write that as an + // invocation-specific file (that is, with a resource suffix). if let Some(ref css) = cx.shared.layout.css_file_extension { let buffer = try_err!(fs::read_to_string(css), css); - // This varies based on the invocation, so it can't go through the write_minify wrapper. - cx.write_minify( - SharedResource::InvocationSpecific { basename: "theme.css" }, - buffer, - options.enable_minification, - &options.emit, - )?; + let path = static_files::suffix_path("theme.css", &cx.shared.resource_suffix); + cx.shared.fs.write(cx.dst.join(path), buffer)?; } - write_minify("normalize.css", static_files::NORMALIZE_CSS, cx, options)?; - for (name, contents) in &*FILES_UNVERSIONED { - cx.write_shared(SharedResource::Unversioned { name }, contents, &options.emit)?; + + if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) { + let static_dir = cx.dst.join(Path::new("static.files")); + static_files::for_each(|f: &static_files::StaticFile| { + let filename = static_dir.join(f.output_filename()); + cx.shared.fs.write(filename, f.minified()) + })?; } /// Read a file and return all lines that match the `"{crate}":{data},` format, @@ -463,7 +271,7 @@ pub(super) fn write_shared( v.push_str("\\\n}');\ncreateSourceSidebar();\n"); Ok(v.into_bytes()) }; - write_crate("source-files.js", &make_sources)?; + write_invocation_specific("source-files.js", &make_sources)?; } // Update the search index and crate list. @@ -477,7 +285,7 @@ pub(super) fn write_shared( // Sort the indexes by crate so the file will be generated identically even // with rustdoc running in parallel. all_indexes.sort(); - write_crate("search-index.js", &|| { + write_invocation_specific("search-index.js", &|| { let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); v.push_str(&all_indexes.join(",\\\n")); v.push_str( @@ -490,7 +298,7 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; Ok(v.into_bytes()) })?; - write_crate("crates.js", &|| { + write_invocation_specific("crates.js", &|| { let krates = krates.iter().map(|k| format!("\"{}\"", k)).join(","); Ok(format!("window.ALL_CRATES = [{}];", krates).into_bytes()) })?; diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 7ab65bff3..54e296959 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -258,7 +258,7 @@ where pub(crate) enum SourceContext { Standalone, - Embedded { offset: usize }, + Embedded { offset: usize, needs_expansion: bool }, } /// Wrapper struct to render the source code of a file. This will do things like @@ -274,28 +274,37 @@ pub(crate) fn print_src( ) { let lines = s.lines().count(); let mut line_numbers = Buffer::empty_from(buf); + let extra; line_numbers.write_str("
    ");
    +    let current_href = &context
    +        .href_from_span(clean::Span::new(file_span), false)
    +        .expect("only local crates should have sources emitted");
         match source_context {
             SourceContext::Standalone => {
    +            extra = None;
                 for line in 1..=lines {
    -                writeln!(line_numbers, "{0}", line)
    +                writeln!(line_numbers, "{line}")
                 }
             }
    -        SourceContext::Embedded { offset } => {
    -            for line in 1..=lines {
    -                writeln!(line_numbers, "{0}", line + offset)
    +        SourceContext::Embedded { offset, needs_expansion } => {
    +            extra = if needs_expansion {
    +                Some(r#""#)
    +            } else {
    +                None
    +            };
    +            for line_number in 1..=lines {
    +                let line = line_number + offset;
    +                writeln!(line_numbers, "{line}")
                 }
             }
         }
         line_numbers.write_str("
    "); - let current_href = &context - .href_from_span(clean::Span::new(file_span), false) - .expect("only local crates should have sources emitted"); highlight::render_source_with_highlighting( s, buf, line_numbers, highlight::HrefContext { context, file_span, root_path, current_href }, decoration_info, + extra, ); } diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index 301f03a16..54e8b6561 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -22,3 +22,9 @@ nav.sub { .source .sidebar { display: none; } + +.notable-traits { + /* layout requires javascript + https://github.com/rust-lang/rust/issues/102576 */ + display: none; +} diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 1cc954a98..afcb40224 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -4,7 +4,7 @@ font-style: normal; font-weight: 400; src: local('Fira Sans'), - url("FiraSans-Regular.woff2") format("woff2"); + url("FiraSans-Regular-018c141bf0843ffd.woff2") format("woff2"); font-display: swap; } @font-face { @@ -12,7 +12,7 @@ font-style: normal; font-weight: 500; src: local('Fira Sans Medium'), - url("FiraSans-Medium.woff2") format("woff2"); + url("FiraSans-Medium-8f9a781e4970d388.woff2") format("woff2"); font-display: swap; } @@ -22,7 +22,7 @@ font-style: normal; font-weight: 400; src: local('Source Serif 4'), - url("SourceSerif4-Regular.ttf.woff2") format("woff2"); + url("SourceSerif4-Regular-1f7d512b176f0f72.ttf.woff2") format("woff2"); font-display: swap; } @font-face { @@ -30,7 +30,7 @@ font-style: italic; font-weight: 400; src: local('Source Serif 4 Italic'), - url("SourceSerif4-It.ttf.woff2") format("woff2"); + url("SourceSerif4-It-d034fe4ef9d0fa00.ttf.woff2") format("woff2"); font-display: swap; } @font-face { @@ -38,7 +38,7 @@ font-style: normal; font-weight: 700; src: local('Source Serif 4 Bold'), - url("SourceSerif4-Bold.ttf.woff2") format("woff2"); + url("SourceSerif4-Bold-124a1ca42af929b6.ttf.woff2") format("woff2"); font-display: swap; } @@ -49,28 +49,28 @@ font-weight: 400; /* Avoid using locally installed font because bad versions are in circulation: * see https://github.com/rust-lang/rust/issues/24355 */ - src: url("SourceCodePro-Regular.ttf.woff2") format("woff2"); + src: url("SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2") format("woff2"); font-display: swap; } @font-face { font-family: 'Source Code Pro'; font-style: italic; font-weight: 400; - src: url("SourceCodePro-It.ttf.woff2") format("woff2"); + src: url("SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2") format("woff2"); font-display: swap; } @font-face { font-family: 'Source Code Pro'; font-style: normal; font-weight: 600; - src: url("SourceCodePro-Semibold.ttf.woff2") format("woff2"); + src: url("SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2") format("woff2"); font-display: swap; } /* Avoid using legacy CJK serif fonts in Windows like Batang. */ @font-face { font-family: 'NanumBarunGothic'; - src: url("NanumBarunGothic.ttf.woff2") format("woff2"); + src: url("NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2") format("woff2"); font-display: swap; unicode-range: U+AC00-D7AF, U+1100-11FF, U+3130-318F, U+A960-A97F, U+D7B0-D7FF; } @@ -159,13 +159,9 @@ h1.fqn { .main-heading { display: flex; flex-wrap: wrap; - justify-content: space-between; padding-bottom: 6px; margin-bottom: 15px; } -#toggle-all-docs { - text-decoration: none; -} /* The only headings that get underlines are: Markdown-generated headings within the top-doc Rustdoc-generated h2 section headings (e.g. "Implementations", "Required Methods", etc) @@ -199,17 +195,14 @@ h1, h2, h3, h4, h5, h6, span.since, a.srclink, #help-button > a, -details.rustdoc-toggle.top-doc > summary, -details.rustdoc-toggle.non-exhaustive > summary, -.scraped-example-title, -.more-examples-toggle summary, .more-examples-toggle .hide-more, -.example-links a, +summary.hideme, +.scraped-example-list, /* This selector is for the items listed in the "all items" page. */ ul.all-items { font-family: "Fira Sans", Arial, NanumBarunGothic, sans-serif; } -a#toggle-all-docs, +#toggle-all-docs, a.anchor, .small-section-header a, #source-sidebar a, @@ -219,12 +212,8 @@ pre.rust a, .mobile-topbar h2 a, h1 a, .search-results a, -.module-item .stab, -.import-item .stab, -.result-name .primitive > i, .result-name .keyword > i, -.method .where, -.fn .where, -.where.fmt-newline { +.item-left .stab, +.result-name .primitive > i, .result-name .keyword > i { color: var(--main-color); } @@ -249,7 +238,6 @@ h1 a, } .content span.fn, .content a.fn, -.content .fnname, .content span.method, .content a.method, .content span.tymethod, .content a.tymethod { color: var(--function-link-color); @@ -297,10 +285,21 @@ p:last-child { button { /* Buttons on Safari have different default padding than other platforms. Make them the same. */ padding: 1px 6px; + /* Opinionated tweak: use pointer cursor as clickability signifier. */ + cursor: pointer; } /* end tweaks for normalize.css 8 */ +button#toggle-all-docs { + padding: 0; + background: none; + border: none; + /* iOS button gradient: https://stackoverflow.com/q/5438567 */ + -webkit-appearance: none; + opacity: 1; +} + .rustdoc { display: flex; flex-direction: row; @@ -311,7 +310,7 @@ main { position: relative; flex-grow: 1; padding: 10px 15px 40px 45px; - min-width: 0; + min-width: 0; /* avoid growing beyond the size limit */ } .source main { @@ -360,7 +359,8 @@ img { overflow: visible; } -.sub-logo-container { +.sub-logo-container, .logo-container { + /* zero text boxes so that computed line height = image height exactly */ line-height: 0; } @@ -370,14 +370,17 @@ img { object-fit: contain; } +.rust-logo { + filter: var(--rust-logo-filter); +} + .sidebar, .mobile-topbar, .sidebar-menu-toggle { background-color: var(--sidebar-background-color); } .sidebar { font-size: 0.875rem; - width: 200px; - min-width: 200px; + flex: 0 0 200px; overflow-y: scroll; position: sticky; height: 100vh; @@ -386,12 +389,7 @@ img { } .rustdoc.source .sidebar { - width: 50px; - min-width: 0px; - max-width: 300px; - flex-grow: 0; - flex-shrink: 0; - flex-basis: auto; + flex-basis: 50px; border-right: 1px solid; overflow-x: hidden; /* The sidebar is by default hidden */ @@ -412,7 +410,7 @@ img { .source-sidebar-expanded .source .sidebar { overflow-y: auto; - width: 300px; + flex-basis: 300px; } .source-sidebar-expanded .source .sidebar > *:not(#sidebar-toggle) { @@ -458,10 +456,9 @@ img { } .sidebar .logo-container { - display: flex; margin-top: 10px; margin-bottom: 10px; - justify-content: center; + text-align: center; } .version { @@ -479,23 +476,17 @@ ul.block, .block li { list-style: none; } -.block a, -.sidebar h2 a, -.sidebar h3 a { +.sidebar-elems a, +.sidebar > h2 a { display: block; - padding: 0.25rem; + padding: 0.25rem; /* 4px */ margin-left: -0.25rem; - - text-overflow: ellipsis; - overflow: hidden; } .sidebar h2 { overflow-wrap: anywhere; padding: 0; - margin: 0; - margin-top: 0.7rem; - margin-bottom: 0.7rem; + margin: 0.7rem 0; } .sidebar h3 { @@ -523,6 +514,8 @@ ul.block, .block li { .sidebar-elems .block li a { white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } .mobile-topbar { @@ -570,15 +563,16 @@ ul.block, .block li { border-color: var(--example-line-numbers-border-color); } -.src-line-numbers span { - cursor: pointer; +.src-line-numbers a, .src-line-numbers span { color: var(--src-line-numbers-span-color); } -.src-line-numbers .line-highlighted { - background-color: var(--src-line-number-highlighted-background-color); -} .src-line-numbers :target { background-color: transparent; + border-right: none; + padding-right: 0; +} +.src-line-numbers .line-highlighted { + background-color: var(--src-line-number-highlighted-background-color); } .search-loading { @@ -636,22 +630,16 @@ pre, .rustdoc.source .example-wrap { .docblock table { margin: .5em 0; - width: calc(100% - 2px); - overflow-x: auto; - display: block; border-collapse: collapse; } -.docblock table td { +.docblock table td, .docblock table th { padding: .5em; - border: 1px dashed var(--border-color); - vertical-align: top; + border: 1px solid var(--border-color); } -.docblock table th { - padding: .5em; - text-align: left; - border: 1px solid var(--border-color); +.docblock table tbody tr:nth-child(2n) { + background: var(--table-alt-row-background-color); } /* Shift "where ..." part of method or fn definition down a line */ @@ -672,7 +660,6 @@ pre, .rustdoc.source .example-wrap { } #main-content > .item-info { - margin-top: 0; margin-left: 0; } @@ -701,8 +688,8 @@ a { } .small-section-header { - display: flex; - justify-content: space-between; + /* fields use tags, but should get their own lines */ + display: block; position: relative; } @@ -710,7 +697,7 @@ a { display: initial; } -.impl:hover > .anchor, .trait-impl:hover > .anchor { +.impl:hover > .anchor, .trait-impl:hover > .anchor, .variant:hover > .anchor { display: inline-block; position: absolute; } @@ -730,9 +717,6 @@ a { h2.small-section-header > .anchor { padding-right: 6px; } -.anchor::before { - content: '§'; -} .main-heading a:hover, .example-wrap > pre.rust a:hover, @@ -787,14 +771,12 @@ table, margin-top: 0; white-space: nowrap; /* flex layout allows shrinking the from overflowing the containing div in case it's shrunk */ max-width: 100%; /* contents can overflow because of max-width limit, then show ellipsis */ @@ -824,6 +804,9 @@ table, line-height: 1.5; font-weight: 500; } +#crate-search:hover, #crate-search:focus { + border-color: var(--crate-search-hover-border); +} /* cancel stylistic differences in padding in firefox for "appearance: none"-style (or equivalent) \ - ${option}\ - `; + output += `\ +`; }); output += ""; } else { // This is a toggle. const checked = setting["default"] === true ? " checked" : ""; - output += ``; + output += `\ +`; } output += ""; } @@ -154,30 +169,27 @@ * @return {HTMLElement} */ function buildSettingsPage() { - const themes = getVar("themes").split(","); + const theme_names = getVar("themes").split(",").filter(t => t); + theme_names.push("light", "dark", "ayu"); + const settings = [ - { - "name": "Use system theme", - "js_name": "use-system-theme", - "default": true, - }, { "name": "Theme", "js_name": "theme", - "default": "light", - "options": themes, + "default": "system preference", + "options": theme_names.concat("system preference"), }, { "name": "Preferred light theme", "js_name": "preferred-light-theme", "default": "light", - "options": themes, + "options": theme_names, }, { "name": "Preferred dark theme", "js_name": "preferred-dark-theme", "default": "dark", - "options": themes, + "options": theme_names, }, { "name": "Auto-hide item contents for large items", @@ -256,7 +268,7 @@ event.preventDefault(); const shouldDisplaySettings = settingsMenu.style.display === "none"; - window.hidePopoverMenus(); + window.hideAllModals(); if (shouldDisplaySettings) { displaySettings(); } diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js index 0b9368dd8..5db768c1c 100644 --- a/src/librustdoc/html/static/js/source-script.js +++ b/src/librustdoc/html/static/js/source-script.js @@ -157,7 +157,7 @@ function highlightSourceLines(match) { x.scrollIntoView(); } onEachLazy(document.getElementsByClassName("src-line-numbers"), e => { - onEachLazy(e.getElementsByTagName("span"), i_e => { + onEachLazy(e.getElementsByTagName("a"), i_e => { removeClass(i_e, "line-highlighted"); }); }); @@ -188,8 +188,13 @@ const handleSourceHighlight = (function() { return ev => { let cur_line_id = parseInt(ev.target.id, 10); - // It can happen when clicking not on a line number span. - if (isNaN(cur_line_id)) { + // This event handler is attached to the entire line number column, but it should only + // be run if one of the anchors is clicked. It also shouldn't do anything if the anchor + // is clicked with a modifier key (to open a new browser tab). + if (isNaN(cur_line_id) || + ev.ctrlKey || + ev.altKey || + ev.metaKey) { return; } ev.preventDefault(); diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index b462a2c50..db2db83ca 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -126,33 +126,29 @@ function getCurrentValue(name) { } } -function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) { - const newHref = mainStyleElem.href.replace( - /\/rustdoc([^/]*)\.css/, "/" + newTheme + "$1" + ".css"); - +function switchTheme(styleElem, mainStyleElem, newThemeName, saveTheme) { // If this new value comes from a system setting or from the previously // saved theme, no need to save it. if (saveTheme) { - updateLocalStorage("theme", newTheme); - } - - if (styleElem.href === newHref) { - return; + updateLocalStorage("theme", newThemeName); } - let found = false; if (savedHref.length === 0) { onEachLazy(document.getElementsByTagName("link"), el => { savedHref.push(el.href); }); } - onEach(savedHref, el => { - if (el === newHref) { - found = true; + const newHref = savedHref.find(url => { + const m = url.match(/static\.files\/(.*)-[a-f0-9]{16}\.css$/); + if (m && m[1] === newThemeName) { + return true; + } + const m2 = url.match(/\/([^/]*)\.css$/); + if (m2 && m2[1].startsWith(newThemeName)) { return true; } }); - if (found) { + if (newHref && newHref !== styleElem.href) { styleElem.href = newHref; } } diff --git a/src/librustdoc/html/static/scrape-examples-help.md b/src/librustdoc/html/static/scrape-examples-help.md index 035b2e18b..002d19ec9 100644 --- a/src/librustdoc/html/static/scrape-examples-help.md +++ b/src/librustdoc/html/static/scrape-examples-help.md @@ -1,4 +1,4 @@ -Rustdoc will automatically scrape examples of documented items from the `examples/` directory of a project. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: +Rustdoc will automatically scrape examples of documented items from a project's source code. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: ```rust // src/lib.rs @@ -16,6 +16,7 @@ fn main() { Then this code snippet will be included in the documentation for `a_func`. + ## How to read scraped examples Scraped examples are shown as blocks of code from a given file. The relevant item will be highlighted. If the file is larger than a couple lines, only a small window will be shown which you can expand by clicking ↕ in the top-right. If a file contains multiple instances of an item, you can use the ≺ and ≻ buttons to toggle through each instance. @@ -25,7 +26,7 @@ If there is more than one file that contains examples, then you should click "Mo ## How Rustdoc scrapes examples -When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items that occur in the crates being documented. Then Rustdoc will include the source code of these instances in the generated documentation. +When you run `cargo doc -Zunstable-options -Zrustdoc-scrape-examples`, Rustdoc will analyze all the documented crates for uses of documented items. Then Rustdoc will include the source code of these instances in the generated documentation. Rustdoc has a few techniques to ensure this doesn't overwhelm documentation readers, and that it doesn't blow up the page size: diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 75f2b7e35..b48b82307 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -2,167 +2,132 @@ //! //! All the static files are included here for centralized access in case anything other than the //! HTML rendering code (say, the theme checker) needs to access one of these files. -//! -//! Note about types: CSS and JavaScript files are included as `&'static str` to allow for the -//! minifier to run on them. All other files are included as `&'static [u8]` so they can be -//! directly written to a `Write` handle. - -/// The file contents of the main `rustdoc.css` file, responsible for the core layout of the page. -pub(crate) static RUSTDOC_CSS: &str = include_str!("static/css/rustdoc.css"); - -/// The file contents of `settings.css`, responsible for the items on the settings page. -pub(crate) static SETTINGS_CSS: &str = include_str!("static/css/settings.css"); - -/// The file contents of the `noscript.css` file, used in case JS isn't supported or is disabled. -pub(crate) static NOSCRIPT_CSS: &str = include_str!("static/css/noscript.css"); - -/// The file contents of `normalize.css`, included to even out standard elements between browser -/// implementations. -pub(crate) static NORMALIZE_CSS: &str = include_str!("static/css/normalize.css"); - -/// The file contents of `main.js`, which contains the core JavaScript used on documentation pages, -/// including search behavior and docblock folding, among others. -pub(crate) static MAIN_JS: &str = include_str!("static/js/main.js"); - -/// The file contents of `search.js`, which contains the search behavior. -pub(crate) static SEARCH_JS: &str = include_str!("static/js/search.js"); - -/// The file contents of `settings.js`, which contains the JavaScript used to handle the settings -/// page. -pub(crate) static SETTINGS_JS: &str = include_str!("static/js/settings.js"); - -/// The file contents of `storage.js`, which contains functionality related to browser Local -/// Storage, used to store documentation settings. -pub(crate) static STORAGE_JS: &str = include_str!("static/js/storage.js"); - -/// The file contents of `scraped-examples.js`, which contains functionality related to the -/// --scrape-examples flag that inserts automatically-found examples of usages of items. -pub(crate) static SCRAPE_EXAMPLES_JS: &str = include_str!("static/js/scrape-examples.js"); - -pub(crate) static SCRAPE_EXAMPLES_HELP_MD: &str = include_str!("static/scrape-examples-help.md"); - -/// The file contents of `wheel.svg`, the icon used for the settings button. -pub(crate) static WHEEL_SVG: &[u8] = include_bytes!("static/images/wheel.svg"); - -/// The file contents of `clipboard.svg`, the icon used for the "copy path" button. -pub(crate) static CLIPBOARD_SVG: &[u8] = include_bytes!("static/images/clipboard.svg"); - -/// The file contents of `down-arrow.svg`, the icon used for the crate choice combobox. -pub(crate) static DOWN_ARROW_SVG: &[u8] = include_bytes!("static/images/down-arrow.svg"); - -/// The file contents of `toggle-minus.svg`, the icon used for opened toggles. -pub(crate) static TOGGLE_MINUS_PNG: &[u8] = include_bytes!("static/images/toggle-minus.svg"); - -/// The file contents of `toggle-plus.svg`, the icon used for closed toggles. -pub(crate) static TOGGLE_PLUS_PNG: &[u8] = include_bytes!("static/images/toggle-plus.svg"); -/// The contents of `COPYRIGHT.txt`, the license listing for files distributed with documentation -/// output. -pub(crate) static COPYRIGHT: &[u8] = include_bytes!("static/COPYRIGHT.txt"); +use rustc_data_structures::fx::FxHasher; +use std::hash::Hasher; +use std::path::{Path, PathBuf}; +use std::{fmt, str}; -/// The contents of `LICENSE-APACHE.txt`, the text of the Apache License, version 2.0. -pub(crate) static LICENSE_APACHE: &[u8] = include_bytes!("static/LICENSE-APACHE.txt"); - -/// The contents of `LICENSE-MIT.txt`, the text of the MIT License. -pub(crate) static LICENSE_MIT: &[u8] = include_bytes!("static/LICENSE-MIT.txt"); - -/// The contents of `rust-logo.svg`, the default icon of the documentation. -pub(crate) static RUST_LOGO_SVG: &[u8] = include_bytes!("static/images/rust-logo.svg"); - -/// The default documentation favicons (SVG and PNG fallbacks) -pub(crate) static RUST_FAVICON_SVG: &[u8] = include_bytes!("static/images/favicon.svg"); -pub(crate) static RUST_FAVICON_PNG_16: &[u8] = include_bytes!("static/images/favicon-16x16.png"); -pub(crate) static RUST_FAVICON_PNG_32: &[u8] = include_bytes!("static/images/favicon-32x32.png"); - -/// The built-in themes given to every documentation site. -pub(crate) mod themes { - /// The "light" theme, selected by default when no setting is available. Used as the basis for - /// the `--check-theme` functionality. - pub(crate) static LIGHT: &str = include_str!("static/css/themes/light.css"); - - /// The "dark" theme. - pub(crate) static DARK: &str = include_str!("static/css/themes/dark.css"); - - /// The "ayu" theme. - pub(crate) static AYU: &str = include_str!("static/css/themes/ayu.css"); +pub(crate) struct StaticFile { + pub(crate) filename: PathBuf, + pub(crate) bytes: &'static [u8], } -/// Files related to the Fira Sans font. -pub(crate) mod fira_sans { - /// The file `FiraSans-Regular.woff2`, the Regular variant of the Fira Sans font in woff2. - pub(crate) static REGULAR: &[u8] = include_bytes!("static/fonts/FiraSans-Regular.woff2"); - - /// The file `FiraSans-Medium.woff2`, the Medium variant of the Fira Sans font in woff2. - pub(crate) static MEDIUM: &[u8] = include_bytes!("static/fonts/FiraSans-Medium.woff2"); - - /// The file `FiraSans-LICENSE.txt`, the license text for the Fira Sans font. - pub(crate) static LICENSE: &[u8] = include_bytes!("static/fonts/FiraSans-LICENSE.txt"); +impl StaticFile { + fn new(filename: &str, bytes: &'static [u8]) -> StaticFile { + Self { filename: static_filename(filename, bytes), bytes } + } + + pub(crate) fn minified(&self) -> Vec { + let extension = match self.filename.extension() { + Some(e) => e, + None => return self.bytes.to_owned(), + }; + if extension == "css" { + minifier::css::minify(str::from_utf8(self.bytes).unwrap()).unwrap().to_string().into() + } else if extension == "js" { + minifier::js::minify(str::from_utf8(self.bytes).unwrap()).to_string().into() + } else { + self.bytes.to_owned() + } + } + + pub(crate) fn output_filename(&self) -> &Path { + &self.filename + } } -/// Files related to the Source Serif 4 font. -pub(crate) mod source_serif_4 { - /// The file `SourceSerif4-Regular.ttf.woff2`, the Regular variant of the Source Serif 4 font in - /// woff2. - pub(crate) static REGULAR: &[u8] = - include_bytes!("static/fonts/SourceSerif4-Regular.ttf.woff2"); - - /// The file `SourceSerif4-Bold.ttf.woff2`, the Bold variant of the Source Serif 4 font in - /// woff2. - pub(crate) static BOLD: &[u8] = include_bytes!("static/fonts/SourceSerif4-Bold.ttf.woff2"); - - /// The file `SourceSerif4-It.ttf.woff2`, the Italic variant of the Source Serif 4 font in - /// woff2. - pub(crate) static ITALIC: &[u8] = include_bytes!("static/fonts/SourceSerif4-It.ttf.woff2"); - - /// The file `SourceSerif4-LICENSE.txt`, the license text for the Source Serif 4 font. - pub(crate) static LICENSE: &[u8] = include_bytes!("static/fonts/SourceSerif4-LICENSE.md"); +/// The Display implementation for a StaticFile outputs its filename. This makes it +/// convenient to interpolate static files into HTML templates. +impl fmt::Display for StaticFile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.output_filename().display()) + } } -/// Files related to the Source Code Pro font. -pub(crate) mod source_code_pro { - /// The file `SourceCodePro-Regular.ttf.woff2`, the Regular variant of the Source Code Pro font - /// in woff2. - pub(crate) static REGULAR: &[u8] = - include_bytes!("static/fonts/SourceCodePro-Regular.ttf.woff2"); - - /// The file `SourceCodePro-Semibold.ttf.woff2`, the Semibold variant of the Source Code Pro - /// font in woff2. - pub(crate) static SEMIBOLD: &[u8] = - include_bytes!("static/fonts/SourceCodePro-Semibold.ttf.woff2"); - - /// The file `SourceCodePro-It.ttf.woff2`, the Italic variant of the Source Code Pro font in - /// woff2. - pub(crate) static ITALIC: &[u8] = include_bytes!("static/fonts/SourceCodePro-It.ttf.woff2"); +/// Insert the provided suffix into a filename just before the extension. +pub(crate) fn suffix_path(filename: &str, suffix: &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, suffix, ext); + filename.into() +} - /// The file `SourceCodePro-LICENSE.txt`, the license text of the Source Code Pro font. - pub(crate) static LICENSE: &[u8] = include_bytes!("static/fonts/SourceCodePro-LICENSE.txt"); +pub(crate) fn static_filename(filename: &str, contents: &[u8]) -> PathBuf { + let filename = filename.rsplit('/').next().unwrap(); + suffix_path(filename, &static_suffix(contents)) } -/// Files related to the Nanum Barun Gothic font. -/// -/// These files are used to avoid some legacy CJK serif fonts in Windows. -/// -/// Note that the Noto Sans KR font, which was used previously but was not very readable on Windows, -/// has been replaced by the Nanum Barun Gothic font. This is due to Windows' implementation of font -/// rendering that distorts OpenType fonts too much. -/// -/// The font files were generated with these commands: -/// -/// ```sh -/// pyftsubset NanumBarunGothic.ttf \ -/// --unicodes=U+AC00-D7AF,U+1100-11FF,U+3130-318F,U+A960-A97F,U+D7B0-D7FF \ -/// --output-file=NanumBarunGothic.ttf.woff2 --flavor=woff2 -/// ``` -pub(crate) mod nanum_barun_gothic { - /// The file `NanumBarunGothic.ttf.woff2`, the Regular variant of the Nanum Barun Gothic font. - pub(crate) static REGULAR: &[u8] = include_bytes!("static/fonts/NanumBarunGothic.ttf.woff2"); +fn static_suffix(bytes: &[u8]) -> String { + let mut hasher = FxHasher::default(); + hasher.write(bytes); + format!("-{:016x}", hasher.finish()) +} - /// The file `NanumBarunGothic-LICENSE.txt`, the license text of the Nanum Barun Gothic font. - pub(crate) static LICENSE: &[u8] = include_bytes!("static/fonts/NanumBarunGothic-LICENSE.txt"); +macro_rules! static_files { + ($($field:ident => $file_path:literal,)+) => { + pub(crate) struct StaticFiles { + $(pub $field: StaticFile,)+ + } + + pub(crate) static STATIC_FILES: std::sync::LazyLock = std::sync::LazyLock::new(|| StaticFiles { + $($field: StaticFile::new($file_path, include_bytes!($file_path)),)+ + }); + + pub(crate) fn for_each(f: impl Fn(&StaticFile) -> Result<(), E>) -> Result<(), E> { + for sf in [ + $(&STATIC_FILES.$field,)+ + ] { + f(sf)? + } + Ok(()) + } + } } -/// Files related to the sidebar in rustdoc sources. -pub(crate) mod sidebar { - /// File script to handle sidebar. - pub(crate) static SOURCE_SCRIPT: &str = include_str!("static/js/source-script.js"); +static_files! { + rustdoc_css => "static/css/rustdoc.css", + settings_css => "static/css/settings.css", + noscript_css => "static/css/noscript.css", + normalize_css => "static/css/normalize.css", + main_js => "static/js/main.js", + search_js => "static/js/search.js", + settings_js => "static/js/settings.js", + source_script_js => "static/js/source-script.js", + storage_js => "static/js/storage.js", + scrape_examples_js => "static/js/scrape-examples.js", + wheel_svg => "static/images/wheel.svg", + clipboard_svg => "static/images/clipboard.svg", + down_arrow_svg => "static/images/down-arrow.svg", + toggle_minus_png => "static/images/toggle-minus.svg", + toggle_plus_png => "static/images/toggle-plus.svg", + copyright => "static/COPYRIGHT.txt", + license_apache => "static/LICENSE-APACHE.txt", + license_mit => "static/LICENSE-MIT.txt", + rust_logo_svg => "static/images/rust-logo.svg", + rust_favicon_svg => "static/images/favicon.svg", + rust_favicon_png_16 => "static/images/favicon-16x16.png", + rust_favicon_png_32 => "static/images/favicon-32x32.png", + theme_light_css => "static/css/themes/light.css", + theme_dark_css => "static/css/themes/dark.css", + theme_ayu_css => "static/css/themes/ayu.css", + fira_sans_regular => "static/fonts/FiraSans-Regular.woff2", + fira_sans_medium => "static/fonts/FiraSans-Medium.woff2", + fira_sans_license => "static/fonts/FiraSans-LICENSE.txt", + source_serif_4_regular => "static/fonts/SourceSerif4-Regular.ttf.woff2", + source_serif_4_bold => "static/fonts/SourceSerif4-Bold.ttf.woff2", + source_serif_4_italic => "static/fonts/SourceSerif4-It.ttf.woff2", + source_serif_4_license => "static/fonts/SourceSerif4-LICENSE.md", + source_code_pro_regular => "static/fonts/SourceCodePro-Regular.ttf.woff2", + source_code_pro_semibold => "static/fonts/SourceCodePro-Semibold.ttf.woff2", + source_code_pro_italic => "static/fonts/SourceCodePro-It.ttf.woff2", + source_code_pro_license => "static/fonts/SourceCodePro-LICENSE.txt", + nanum_barun_gothic_regular => "static/fonts/NanumBarunGothic.ttf.woff2", + nanum_barun_gothic_license => "static/fonts/NanumBarunGothic-LICENSE.txt", } + +pub(crate) static SCRAPE_EXAMPLES_HELP_MD: &str = include_str!("static/scrape-examples-help.md"); diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index c32386916..aa3bf827d 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -7,48 +7,44 @@ {#- -#} {#- -#} {{page.title}} {#- -#} - {#- -#} - {#- -#} - {#- -#} - {#- -#} - {#- -#} - {#- -#} + {#- -#} + {#- -#} + {#- -#} + {#- -#} + {#- -#} + {#- -#} {#- -#} + href="{{static_root_path|safe}}{{files.normalize_css}}"> {#- -#} {#- -#} + {#- -#} + {#- -#} + {#- -#} {%- for theme in themes -%} - + {#- -#} {%- endfor -%} {#- -#} - {#- -#} + {#- -#} {%- if page.css_class.contains("crate") -%} {#- -#} {%- else if page.css_class == "source" -%} - {#- -#} + {#- -#} {#- -#} {%- else if !page.css_class.contains("mod") -%} {#- -#} {%- endif -%} - {#- -#} + {#- -#} {%- if layout.scrape_examples_extension -%} - {#- -#} + {#- -#} {%- endif -%} {#- -#} {%- if layout.css_file_extension.is_some() -%} {#- -#} {%- else -%} {#- -#} + href="{{static_root_path|safe}}{{files.rust_favicon_png_16}}"> {#- -#} {#- -#} + href="{{static_root_path|safe}}{{files.rust_favicon_png_32}}"> {#- -#} {#- -#} + href="{{static_root_path|safe}}{{files.rust_favicon_svg}}"> {#- -#} {%- endif -%} {{- layout.external_html.in_header|safe -}} {#- -#} @@ -81,7 +77,7 @@ {%- if !layout.logo.is_empty() -%} logo {#- -#} {%- else -%} - {#- -#} + {#- -#} {%- endif -%} {#- -#} {#- -#} @@ -95,7 +91,7 @@ {%- if !layout.logo.is_empty() %} logo {#- -#} {%- else -%} - {#- -#} + {#- -#} {%- endif -%} {#- -#} {#- -#} @@ -110,7 +106,7 @@ {%- if !layout.logo.is_empty() %} logo {#- -#} {%- else -%} - {#- -#} + {#- -#} {%- endif -%} {#- -#} {%- endif -%} @@ -119,6 +115,7 @@ {#- -#} {#- -#} Change settings {#- -#} + src="{{static_root_path|safe}}{{files.wheel_svg}}"> {#- -#} {#- -#} {#- -#} {#- -#} @@ -140,10 +137,14 @@ {{- layout.external_html.after_content|safe -}}
    {#- -#}
    {#- -#} {#- -#} diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index b6ce3ea3d..611d124d4 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -7,7 +7,7 @@ {%- endfor -%} {{name}} {#- -#} {#- -#} @@ -21,8 +21,8 @@ source · {# -#} {%- else -%} {%- endmatch -%} - {#- -#} - [] {#- -#} - {#- -#} + {#- -#}
    {#- -#} {#- -#} diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index cdf59cdd3..d7184053c 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -48,7 +48,8 @@ impl JsonRenderer<'_> { .map(rustc_ast_pretty::pprust::attribute_to_string) .collect(); let span = item.span(self.tcx); - let clean::Item { name, attrs: _, kind: _, visibility, item_id, cfg: _ } = item; + let visibility = item.visibility(self.tcx); + let clean::Item { name, attrs: _, kind: _, item_id, cfg: _, .. } = item; let inner = match *item.kind { clean::KeywordItem => return None, clean::StrippedItem(ref inner) => { @@ -99,13 +100,12 @@ impl JsonRenderer<'_> { } } - fn convert_visibility(&self, v: clean::Visibility) -> Visibility { - use clean::Visibility::*; + fn convert_visibility(&self, v: Option>) -> Visibility { match v { - Public => Visibility::Public, - Inherited => Visibility::Default, - Restricted(did) if did.is_crate_root() => Visibility::Crate, - Restricted(did) => Visibility::Restricted { + None => Visibility::Default, + Some(ty::Visibility::Public) => Visibility::Public, + Some(ty::Visibility::Restricted(did)) if did.is_crate_root() => Visibility::Crate, + Some(ty::Visibility::Restricted(did)) => Visibility::Restricted { parent: from_item_id(did.into(), self.tcx), path: self.tcx.def_path(did).to_string_no_crate_verbose(), }, @@ -257,12 +257,12 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { StructFieldItem(f) => ItemEnum::StructField(f.into_tcx(tcx)), EnumItem(e) => ItemEnum::Enum(e.into_tcx(tcx)), VariantItem(v) => ItemEnum::Variant(v.into_tcx(tcx)), - FunctionItem(f) => ItemEnum::Function(from_function(f, header.unwrap(), tcx)), - ForeignFunctionItem(f) => ItemEnum::Function(from_function(f, header.unwrap(), tcx)), + FunctionItem(f) => ItemEnum::Function(from_function(f, true, header.unwrap(), tcx)), + ForeignFunctionItem(f) => ItemEnum::Function(from_function(f, false, header.unwrap(), tcx)), TraitItem(t) => ItemEnum::Trait((*t).into_tcx(tcx)), TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_tcx(tcx)), - MethodItem(m, _) => ItemEnum::Method(from_function_method(m, true, header.unwrap(), tcx)), - TyMethodItem(m) => ItemEnum::Method(from_function_method(m, false, header.unwrap(), tcx)), + MethodItem(m, _) => ItemEnum::Function(from_function(m, true, header.unwrap(), tcx)), + TyMethodItem(m) => ItemEnum::Function(from_function(m, false, header.unwrap(), tcx)), ImplItem(i) => ItemEnum::Impl((*i).into_tcx(tcx)), StaticItem(s) => ItemEnum::Static(s.into_tcx(tcx)), ForeignStaticItem(s) => ItemEnum::Static(s.into_tcx(tcx)), @@ -283,7 +283,7 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { ItemEnum::AssocConst { type_: ty.into_tcx(tcx), default: Some(default.expr(tcx)) } } TyAssocTypeItem(g, b) => ItemEnum::AssocType { - generics: (*g).into_tcx(tcx), + generics: g.into_tcx(tcx), bounds: b.into_tcx(tcx), default: None, }, @@ -315,15 +315,15 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { impl FromWithTcx for Struct { fn from_tcx(struct_: clean::Struct, tcx: TyCtxt<'_>) -> Self { let fields_stripped = struct_.has_stripped_entries(); - let clean::Struct { struct_type, generics, fields } = struct_; + let clean::Struct { ctor_kind, generics, fields } = struct_; - let kind = match struct_type { - CtorKind::Fn => StructKind::Tuple(ids_keeping_stripped(fields, tcx)), - CtorKind::Const => { + let kind = match ctor_kind { + Some(CtorKind::Fn) => StructKind::Tuple(ids_keeping_stripped(fields, tcx)), + Some(CtorKind::Const) => { assert!(fields.is_empty()); StructKind::Unit } - CtorKind::Fictive => StructKind::Plain { fields: ids(fields, tcx), fields_stripped }, + None => StructKind::Plain { fields: ids(fields, tcx), fields_stripped }, }; Struct { @@ -485,7 +485,7 @@ impl FromWithTcx for Type { BareFunction(f) => Type::FunctionPointer(Box::new((*f).into_tcx(tcx))), Tuple(t) => Type::Tuple(t.into_tcx(tcx)), Slice(t) => Type::Slice(Box::new((*t).into_tcx(tcx))), - Array(t, s) => Type::Array { type_: Box::new((*t).into_tcx(tcx)), len: s }, + Array(t, s) => Type::Array { type_: Box::new((*t).into_tcx(tcx)), len: s.to_string() }, ImplTrait(g) => Type::ImplTrait(g.into_tcx(tcx)), Infer => Type::Infer, RawPointer(mutability, type_) => Type::RawPointer { @@ -618,6 +618,7 @@ impl FromWithTcx for Impl { pub(crate) fn from_function( function: Box, + has_body: bool, header: rustc_hir::FnHeader, tcx: TyCtxt<'_>, ) -> Function { @@ -626,20 +627,6 @@ pub(crate) fn from_function( decl: decl.into_tcx(tcx), generics: generics.into_tcx(tcx), header: from_fn_header(&header), - } -} - -pub(crate) fn from_function_method( - function: Box, - has_body: bool, - header: rustc_hir::FnHeader, - tcx: TyCtxt<'_>, -) -> Method { - let clean::Function { decl, generics } = *function; - Method { - decl: decl.into_tcx(tcx), - generics: generics.into_tcx(tcx), - header: from_fn_header(&header), has_body, } } @@ -674,7 +661,7 @@ impl FromWithTcx for Variant { impl FromWithTcx for Discriminant { fn from_tcx(disr: clean::Discriminant, tcx: TyCtxt<'_>) -> Self { Discriminant { - // expr is only none if going throught the inlineing path, which gets + // expr is only none if going through the inlineing path, which gets // `rustc_middle` types, not `rustc_hir`, but because JSON never inlines // the expr is always some. expr: disr.expr(tcx).unwrap(), @@ -759,14 +746,13 @@ impl FromWithTcx for ItemKind { Struct => ItemKind::Struct, Union => ItemKind::Union, Enum => ItemKind::Enum, - Function => ItemKind::Function, + Function | TyMethod | Method => ItemKind::Function, Typedef => ItemKind::Typedef, OpaqueTy => ItemKind::OpaqueTy, Static => ItemKind::Static, Constant => ItemKind::Constant, Trait => ItemKind::Trait, Impl => ItemKind::Impl, - TyMethod | Method => ItemKind::Method, StructField => ItemKind::StructField, Variant => ItemKind::Variant, Macro => ItemKind::Macro, diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index d13efe6c1..1196f944f 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -99,53 +99,6 @@ impl<'tcx> JsonRenderer<'tcx> { }) .unwrap_or_default() } - - fn get_trait_items(&mut self) -> Vec<(types::Id, types::Item)> { - debug!("Adding foreign trait items"); - Rc::clone(&self.cache) - .traits - .iter() - .filter_map(|(&id, trait_item)| { - // only need to synthesize items for external traits - if !id.is_local() { - for item in &trait_item.items { - trace!("Adding subitem to {id:?}: {:?}", item.item_id); - self.item(item.clone()).unwrap(); - } - let item_id = from_item_id(id.into(), self.tcx); - Some(( - item_id.clone(), - types::Item { - id: item_id, - crate_id: id.krate.as_u32(), - name: self - .cache - .paths - .get(&id) - .unwrap_or_else(|| { - self.cache - .external_paths - .get(&id) - .expect("Trait should either be in local or external paths") - }) - .0 - .last() - .map(|s| s.to_string()), - visibility: types::Visibility::Public, - inner: types::ItemEnum::Trait(trait_item.clone().into_tcx(self.tcx)), - span: None, - docs: Default::default(), - links: Default::default(), - attrs: Default::default(), - deprecation: Default::default(), - }, - )) - } else { - None - } - }) - .collect() - } } impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { @@ -223,15 +176,14 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { false } - types::ItemEnum::Method(_) + types::ItemEnum::Function(_) | types::ItemEnum::Module(_) + | types::ItemEnum::Import(_) | types::ItemEnum::AssocConst { .. } | types::ItemEnum::AssocType { .. } => true, types::ItemEnum::ExternCrate { .. } - | types::ItemEnum::Import(_) | types::ItemEnum::StructField(_) | types::ItemEnum::Variant(_) - | types::ItemEnum::Function(_) | types::ItemEnum::TraitAlias(_) | types::ItemEnum::Impl(_) | types::ItemEnum::Typedef(_) @@ -277,11 +229,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { let e = ExternalCrate { crate_num: LOCAL_CRATE }; - // FIXME(adotinthevoid): Remove this, as it's not consistant with not - // inlining foreign items. - let foreign_trait_items = self.get_trait_items(); - let mut index = (*self.index).clone().into_inner(); - index.extend(foreign_trait_items); + let index = (*self.index).clone().into_inner(); debug!("Constructing Output"); // This needs to be the default HashMap for compatibility with the public interface for diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 4cf9435d9..ef1d7da5a 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -8,6 +8,7 @@ #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(drain_filter)] +#![feature(is_terminal)] #![feature(let_chains)] #![feature(test)] #![feature(never_type)] @@ -69,7 +70,7 @@ extern crate jemalloc_sys; use std::default::Default; use std::env::{self, VarError}; -use std::io; +use std::io::{self, IsTerminal}; use std::process; use rustc_driver::abort_on_err; @@ -179,7 +180,7 @@ fn init_logging() { let color_logs = match std::env::var("RUSTDOC_LOG_COLOR").as_deref() { Ok("always") => true, Ok("never") => false, - Ok("auto") | Err(VarError::NotPresent) => atty::is(atty::Stream::Stdout), + Ok("auto") | Err(VarError::NotPresent) => io::stdout().is_terminal(), Ok(value) => early_error( ErrorOutputType::default(), &format!("invalid log color value '{}': expected one of always, never, or auto", value), @@ -469,9 +470,6 @@ fn opts() -> Vec { stable("json", |o| { o.optopt("", "json", "Configure the structure of JSON diagnostics", "CONFIG") }), - unstable("disable-minification", |o| { - o.optflagmulti("", "disable-minification", "Disable minification applied on JS files") - }), stable("allow", |o| o.optmulti("A", "allow", "Set lint allowed", "LINT")), stable("warn", |o| o.optmulti("W", "warn", "Set lint warnings", "LINT")), stable("force-warn", |o| o.optmulti("", "force-warn", "Set lint force-warn", "LINT")), @@ -610,6 +608,7 @@ fn opts() -> Vec { ) }), // deprecated / removed options + unstable("disable-minification", |o| o.optflagmulti("", "disable-minification", "removed")), stable("plugin-path", |o| { o.optmulti( "", @@ -675,7 +674,7 @@ type MainResult = Result<(), ErrorGuaranteed>; fn wrap_return(diag: &rustc_errors::Handler, res: Result<(), String>) -> MainResult { match res { - Ok(()) => Ok(()), + Ok(()) => diag.has_errors().map_or(Ok(()), Err), Err(err) => { let reported = diag.struct_err(&err).emit(); Err(reported) @@ -690,7 +689,7 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>( tcx: TyCtxt<'tcx>, ) -> MainResult { match formats::run_format::(krate, renderopts, cache, tcx) { - Ok(_) => Ok(()), + Ok(_) => tcx.sess.has_errors().map_or(Ok(()), Err), Err(e) => { let mut msg = tcx.sess.struct_err(&format!("couldn't generate documentation: {}", e.error)); @@ -775,6 +774,7 @@ fn main_args(at_args: &[String]) -> MainResult { let output_format = options.output_format; let externs = options.externs.clone(); let scrape_examples_options = options.scrape_examples_options.clone(); + let bin_crate = options.bin_crate; let config = core::create_config(options); @@ -782,10 +782,7 @@ fn main_args(at_args: &[String]) -> MainResult { let sess = compiler.session(); if sess.opts.describe_lints { - let mut lint_store = rustc_lint::new_lint_store( - sess.opts.unstable_opts.no_interleave_lints, - sess.enable_internal_lints(), - ); + let mut lint_store = rustc_lint::new_lint_store(sess.enable_internal_lints()); let registered_lints = if let Some(register_lints) = compiler.register_lints() { register_lints(sess, &mut lint_store); true @@ -836,7 +833,14 @@ fn main_args(at_args: &[String]) -> MainResult { info!("finished with rustc"); if let Some(options) = scrape_examples_options { - return scrape_examples::run(krate, render_opts, cache, tcx, options); + return scrape_examples::run( + krate, + render_opts, + cache, + tcx, + options, + bin_crate, + ); } cache.crate_version = crate_version; diff --git a/src/librustdoc/passes/bare_urls.rs b/src/librustdoc/passes/bare_urls.rs deleted file mode 100644 index 7ff3ccef9..000000000 --- a/src/librustdoc/passes/bare_urls.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Detects links that are not linkified, e.g., in Markdown such as `Go to https://example.com/.` -//! Suggests wrapping the link with angle brackets: `Go to .` to linkify it. -use super::Pass; -use crate::clean::*; -use crate::core::DocContext; -use crate::html::markdown::main_body_opts; -use crate::visit::DocVisitor; -use core::ops::Range; -use pulldown_cmark::{Event, Parser, Tag}; -use regex::Regex; -use rustc_errors::Applicability; -use std::mem; -use std::sync::LazyLock; - -pub(crate) const CHECK_BARE_URLS: Pass = Pass { - name: "check-bare-urls", - run: check_bare_urls, - description: "detects URLs that are not hyperlinks", -}; - -static URL_REGEX: LazyLock = LazyLock::new(|| { - Regex::new(concat!( - r"https?://", // url scheme - r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains - r"[a-zA-Z]{2,63}", // root domain - r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)" // optional query or url fragments - )) - .expect("failed to build regex") -}); - -struct BareUrlsLinter<'a, 'tcx> { - cx: &'a mut DocContext<'tcx>, -} - -impl<'a, 'tcx> BareUrlsLinter<'a, 'tcx> { - fn find_raw_urls( - &self, - text: &str, - range: Range, - f: &impl Fn(&DocContext<'_>, &str, &str, Range), - ) { - trace!("looking for raw urls in {}", text); - // For now, we only check "full" URLs (meaning, starting with "http://" or "https://"). - for match_ in URL_REGEX.find_iter(text) { - let url = match_.as_str(); - let url_range = match_.range(); - f( - self.cx, - "this URL is not a hyperlink", - url, - Range { start: range.start + url_range.start, end: range.start + url_range.end }, - ); - } - } -} - -pub(crate) fn check_bare_urls(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - BareUrlsLinter { cx }.visit_crate(&krate); - krate -} - -impl<'a, 'tcx> DocVisitor for BareUrlsLinter<'a, 'tcx> { - fn visit_item(&mut self, item: &Item) { - let Some(hir_id) = DocContext::as_local_hir_id(self.cx.tcx, item.item_id) - else { - // If non-local, no need to check anything. - return; - }; - let dox = item.attrs.collapsed_doc_value().unwrap_or_default(); - if !dox.is_empty() { - let report_diag = |cx: &DocContext<'_>, msg: &str, url: &str, range: Range| { - let sp = super::source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs) - .unwrap_or_else(|| item.attr_span(cx.tcx)); - cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, msg, |lint| { - lint.note("bare URLs are not automatically turned into clickable links") - .span_suggestion( - sp, - "use an automatic link instead", - format!("<{}>", url), - Applicability::MachineApplicable, - ) - }); - }; - - let mut p = Parser::new_ext(&dox, main_body_opts()).into_offset_iter(); - - while let Some((event, range)) = p.next() { - match event { - Event::Text(s) => self.find_raw_urls(&s, range, &report_diag), - // We don't want to check the text inside code blocks or links. - Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link(..))) => { - while let Some((event, _)) = p.next() { - match event { - Event::End(end) - if mem::discriminant(&end) == mem::discriminant(&tag) => - { - break; - } - _ => {} - } - } - } - _ => {} - } - } - } - - self.visit_item_recur(item) - } -} diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index 48835abf9..02b227896 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -244,10 +244,10 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> { matches!( node, hir::Node::Variant(hir::Variant { - data: hir::VariantData::Tuple(_, _), + data: hir::VariantData::Tuple(_, _, _), .. }) | hir::Node::Item(hir::Item { - kind: hir::ItemKind::Struct(hir::VariantData::Tuple(_, _), _), + kind: hir::ItemKind::Struct(hir::VariantData::Tuple(_, _, _), _), .. }) ) diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs deleted file mode 100644 index 2e651b538..000000000 --- a/src/librustdoc/passes/check_code_block_syntax.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! Validates syntax inside Rust code blocks (\`\`\`rust). -use rustc_data_structures::sync::{Lock, Lrc}; -use rustc_errors::{ - emitter::Emitter, - translation::{to_fluent_args, Translate}, - Applicability, Diagnostic, Handler, LazyFallbackBundle, -}; -use rustc_parse::parse_stream_from_source_str; -use rustc_session::parse::ParseSess; -use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId}; -use rustc_span::source_map::{FilePathMapping, SourceMap}; -use rustc_span::{FileName, InnerSpan, DUMMY_SP}; - -use crate::clean; -use crate::core::DocContext; -use crate::html::markdown::{self, RustCodeBlock}; -use crate::passes::Pass; -use crate::visit::DocVisitor; - -pub(crate) const CHECK_CODE_BLOCK_SYNTAX: Pass = Pass { - name: "check-code-block-syntax", - run: check_code_block_syntax, - description: "validates syntax inside Rust code blocks", -}; - -pub(crate) fn check_code_block_syntax( - krate: clean::Crate, - cx: &mut DocContext<'_>, -) -> clean::Crate { - SyntaxChecker { cx }.visit_crate(&krate); - krate -} - -struct SyntaxChecker<'a, 'tcx> { - cx: &'a DocContext<'tcx>, -} - -impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { - fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) { - let buffer = Lrc::new(Lock::new(Buffer::default())); - let fallback_bundle = - rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); - let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle }; - - let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let handler = Handler::with_emitter(false, None, Box::new(emitter)); - let source = dox[code_block.code].to_owned(); - let sess = ParseSess::with_span_handler(handler, sm); - - let edition = code_block.lang_string.edition.unwrap_or_else(|| self.cx.tcx.sess.edition()); - let expn_data = ExpnData::default( - ExpnKind::AstPass(AstPass::TestHarness), - DUMMY_SP, - edition, - None, - None, - ); - let expn_id = - self.cx.tcx.with_stable_hashing_context(|hcx| LocalExpnId::fresh(expn_data, hcx)); - let span = DUMMY_SP.fresh_expansion(expn_id); - - let is_empty = rustc_driver::catch_fatal_errors(|| { - parse_stream_from_source_str( - FileName::Custom(String::from("doctest")), - source, - &sess, - Some(span), - ) - .is_empty() - }) - .unwrap_or(false); - let buffer = buffer.borrow(); - - if !buffer.has_errors && !is_empty { - // No errors in a non-empty program. - return; - } - - let Some(local_id) = item.item_id.as_def_id().and_then(|x| x.as_local()) - else { - // We don't need to check the syntax for other crates so returning - // without doing anything should not be a problem. - return; - }; - - let hir_id = self.cx.tcx.hir().local_def_id_to_hir_id(local_id); - let empty_block = code_block.lang_string == Default::default() && code_block.is_fenced; - let is_ignore = code_block.lang_string.ignore != markdown::Ignore::None; - - // The span and whether it is precise or not. - let (sp, precise_span) = match super::source_span_for_markdown_range( - self.cx.tcx, - dox, - &code_block.range, - &item.attrs, - ) { - Some(sp) => (sp, true), - None => (item.attr_span(self.cx.tcx), false), - }; - - let msg = if buffer.has_errors { - "could not parse code block as Rust code" - } else { - "Rust code block is empty" - }; - - // Finally build and emit the completed diagnostic. - // All points of divergence have been handled earlier so this can be - // done the same way whether the span is precise or not. - self.cx.tcx.struct_span_lint_hir( - crate::lint::INVALID_RUST_CODEBLOCKS, - hir_id, - sp, - msg, - |lint| { - let explanation = if is_ignore { - "`ignore` code blocks require valid Rust code for syntax highlighting; \ - mark blocks that do not contain Rust code as text" - } else { - "mark blocks that do not contain Rust code as text" - }; - - if precise_span { - if is_ignore { - // giving an accurate suggestion is hard because `ignore` might not have come first in the list. - // just give a `help` instead. - lint.span_help( - sp.from_inner(InnerSpan::new(0, 3)), - &format!("{}: ```text", explanation), - ); - } else if empty_block { - lint.span_suggestion( - sp.from_inner(InnerSpan::new(0, 3)).shrink_to_hi(), - explanation, - "text", - Applicability::MachineApplicable, - ); - } - } else if empty_block || is_ignore { - lint.help(&format!("{}: ```text", explanation)); - } - - // FIXME(#67563): Provide more context for these errors by displaying the spans inline. - for message in buffer.messages.iter() { - lint.note(message); - } - - lint - }, - ); - } -} - -impl<'a, 'tcx> DocVisitor for SyntaxChecker<'a, 'tcx> { - fn visit_item(&mut self, item: &clean::Item) { - if let Some(dox) = &item.attrs.collapsed_doc_value() { - let sp = item.attr_span(self.cx.tcx); - let extra = crate::html::markdown::ExtraInfo::new_did( - self.cx.tcx, - item.item_id.expect_def_id(), - sp, - ); - for code_block in markdown::rust_code_blocks(dox, &extra) { - self.check_rust_syntax(item, dox, code_block); - } - } - - self.visit_item_recur(item) - } -} - -#[derive(Default)] -struct Buffer { - messages: Vec, - has_errors: bool, -} - -struct BufferEmitter { - buffer: Lrc>, - fallback_bundle: LazyFallbackBundle, -} - -impl Translate for BufferEmitter { - fn fluent_bundle(&self) -> Option<&Lrc> { - None - } - - fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle { - &**self.fallback_bundle - } -} - -impl Emitter for BufferEmitter { - fn emit_diagnostic(&mut self, diag: &Diagnostic) { - let mut buffer = self.buffer.borrow_mut(); - - let fluent_args = to_fluent_args(diag.args()); - let translated_main_message = self.translate_message(&diag.message[0].0, &fluent_args); - - buffer.messages.push(format!("error from rustc: {}", translated_main_message)); - if diag.is_error() { - buffer.has_errors = true; - } - } - - fn source_map(&self) -> Option<&Lrc> { - None - } -} diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 7740c6d5b..057d2fdd9 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -56,7 +56,7 @@ impl crate::doctest::Tester for Tests { } pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool { - if !cx.cache.effective_visibilities.is_directly_public(item.item_id.expect_def_id()) + if !cx.cache.effective_visibilities.is_directly_public(cx.tcx, item.item_id.expect_def_id()) || matches!( *item.kind, clean::StructFieldItem(_) @@ -130,7 +130,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item ); } } else if tests.found_tests > 0 - && !cx.cache.effective_visibilities.is_exported(item.item_id.expect_def_id()) + && !cx.cache.effective_visibilities.is_exported(cx.tcx, item.item_id.expect_def_id()) { cx.tcx.struct_span_lint_hir( crate::lint::PRIVATE_DOC_TESTS, diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 8aa0abd36..37a28b6b7 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -402,6 +402,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { }) .and_then(|self_id| match tcx.def_kind(self_id) { DefKind::Impl => self.def_id_to_res(self_id), + DefKind::Use => None, def_kind => Some(Res::Def(def_kind, self_id)), }) } @@ -1772,7 +1773,6 @@ fn resolution_failure( // Otherwise, it must be an associated item or variant let res = partial_res.expect("None case was handled by `last_found_module`"); - let name = res.name(tcx); let kind = match res { Res::Def(kind, _) => Some(kind), Res::Primitive(_) => None, @@ -1814,6 +1814,7 @@ fn resolution_failure( } else { "associated item" }; + let name = res.name(tcx); let note = format!( "the {} `{}` has no {} named `{}`", res.descr(), @@ -1893,7 +1894,7 @@ fn disambiguator_error( diag_info.link_range = disambiguator_range; report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp| { let msg = format!( - "see {}/rustdoc/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators", + "see {}/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators", crate::DOC_RUST_LANG_ORG_CHANNEL ); diag.note(&msg); diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 6b699c790..d57f981d5 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -8,7 +8,7 @@ use crate::formats::cache::Cache; use crate::visit::DocVisitor; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def_id::DefId; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::ty::{self, DefIdTree}; use rustc_span::symbol::sym; @@ -25,7 +25,9 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> synth.impls }); - let prims: FxHashSet = krate.primitives.iter().map(|p| p.1).collect(); + let local_crate = ExternalCrate { crate_num: LOCAL_CRATE }; + let prims: FxHashSet = + local_crate.primitives(cx.tcx).iter().map(|p| p.1).collect(); let crate_items = { let mut coll = ItemCollector::new(); diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs deleted file mode 100644 index a89ed7c7e..000000000 --- a/src/librustdoc/passes/html_tags.rs +++ /dev/null @@ -1,430 +0,0 @@ -//! Detects invalid HTML (like an unclosed ``) in doc comments. -use super::Pass; -use crate::clean::*; -use crate::core::DocContext; -use crate::html::markdown::main_body_opts; -use crate::visit::DocVisitor; - -use pulldown_cmark::{BrokenLink, Event, LinkType, Parser, Tag}; - -use std::iter::Peekable; -use std::ops::Range; -use std::str::CharIndices; - -pub(crate) const CHECK_INVALID_HTML_TAGS: Pass = Pass { - name: "check-invalid-html-tags", - run: check_invalid_html_tags, - description: "detects invalid HTML tags in doc comments", -}; - -struct InvalidHtmlTagsLinter<'a, 'tcx> { - cx: &'a mut DocContext<'tcx>, -} - -pub(crate) fn check_invalid_html_tags(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - let mut coll = InvalidHtmlTagsLinter { cx }; - coll.visit_crate(&krate); - krate -} - -const ALLOWED_UNCLOSED: &[&str] = &[ - "area", "base", "br", "col", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", - "source", "track", "wbr", -]; - -fn drop_tag( - tags: &mut Vec<(String, Range)>, - tag_name: String, - range: Range, - f: &impl Fn(&str, &Range, bool), -) { - let tag_name_low = tag_name.to_lowercase(); - if let Some(pos) = tags.iter().rposition(|(t, _)| t.to_lowercase() == tag_name_low) { - // If the tag is nested inside a "

    ` (the `h2` tag isn't required - // but it helps for the visualization). - f(&format!("unopened HTML tag `{}`", tag_name), &range, false); - } -} - -fn extract_path_backwards(text: &str, end_pos: usize) -> Option { - use rustc_lexer::{is_id_continue, is_id_start}; - let mut current_pos = end_pos; - loop { - if current_pos >= 2 && text[..current_pos].ends_with("::") { - current_pos -= 2; - } - let new_pos = text[..current_pos] - .char_indices() - .rev() - .take_while(|(_, c)| is_id_start(*c) || is_id_continue(*c)) - .reduce(|_accum, item| item) - .and_then(|(new_pos, c)| is_id_start(c).then_some(new_pos)); - if let Some(new_pos) = new_pos { - if current_pos != new_pos { - current_pos = new_pos; - continue; - } - } - break; - } - if current_pos == end_pos { None } else { Some(current_pos) } -} - -fn extract_path_forward(text: &str, start_pos: usize) -> Option { - use rustc_lexer::{is_id_continue, is_id_start}; - let mut current_pos = start_pos; - loop { - if current_pos < text.len() && text[current_pos..].starts_with("::") { - current_pos += 2; - } else { - break; - } - let mut chars = text[current_pos..].chars(); - if let Some(c) = chars.next() { - if is_id_start(c) { - current_pos += c.len_utf8(); - } else { - break; - } - } - while let Some(c) = chars.next() { - if is_id_continue(c) { - current_pos += c.len_utf8(); - } else { - break; - } - } - } - if current_pos == start_pos { None } else { Some(current_pos) } -} - -fn is_valid_for_html_tag_name(c: char, is_empty: bool) -> bool { - // https://spec.commonmark.org/0.30/#raw-html - // - // > A tag name consists of an ASCII letter followed by zero or more ASCII letters, digits, or - // > hyphens (-). - c.is_ascii_alphabetic() || !is_empty && (c == '-' || c.is_ascii_digit()) -} - -fn extract_html_tag( - tags: &mut Vec<(String, Range)>, - text: &str, - range: &Range, - start_pos: usize, - iter: &mut Peekable>, - f: &impl Fn(&str, &Range, bool), -) { - let mut tag_name = String::new(); - let mut is_closing = false; - let mut prev_pos = start_pos; - - loop { - let (pos, c) = match iter.peek() { - Some((pos, c)) => (*pos, *c), - // In case we reached the of the doc comment, we want to check that it's an - // unclosed HTML tag. For example "/// (prev_pos, '\0'), - }; - prev_pos = pos; - // Checking if this is a closing tag (like `` for ``). - if c == '/' && tag_name.is_empty() { - is_closing = true; - } else if is_valid_for_html_tag_name(c, tag_name.is_empty()) { - tag_name.push(c); - } else { - if !tag_name.is_empty() { - let mut r = Range { start: range.start + start_pos, end: range.start + pos }; - if c == '>' { - // In case we have a tag without attribute, we can consider the span to - // refer to it fully. - r.end += 1; - } - if is_closing { - // In case we have "" or even "". - if c != '>' { - if !c.is_whitespace() { - // It seems like it's not a valid HTML tag. - break; - } - let mut found = false; - for (new_pos, c) in text[pos..].char_indices() { - if !c.is_whitespace() { - if c == '>' { - r.end = range.start + new_pos + 1; - found = true; - } - break; - } - } - if !found { - break; - } - } - drop_tag(tags, tag_name, r, f); - } else { - let mut is_self_closing = false; - let mut quote_pos = None; - if c != '>' { - let mut quote = None; - let mut after_eq = false; - for (i, c) in text[pos..].char_indices() { - if !c.is_whitespace() { - if let Some(q) = quote { - if c == q { - quote = None; - quote_pos = None; - after_eq = false; - } - } else if c == '>' { - break; - } else if c == '/' && !after_eq { - is_self_closing = true; - } else { - if is_self_closing { - is_self_closing = false; - } - if (c == '"' || c == '\'') && after_eq { - quote = Some(c); - quote_pos = Some(pos + i); - } else if c == '=' { - after_eq = true; - } - } - } else if quote.is_none() { - after_eq = false; - } - } - } - if let Some(quote_pos) = quote_pos { - let qr = Range { start: quote_pos, end: quote_pos }; - f( - &format!("unclosed quoted HTML attribute on tag `{}`", tag_name), - &qr, - false, - ); - } - if is_self_closing { - // https://html.spec.whatwg.org/#parse-error-non-void-html-element-start-tag-with-trailing-solidus - let valid = ALLOWED_UNCLOSED.contains(&&tag_name[..]) - || tags.iter().take(pos + 1).any(|(at, _)| { - let at = at.to_lowercase(); - at == "svg" || at == "math" - }); - if !valid { - f(&format!("invalid self-closing HTML tag `{}`", tag_name), &r, false); - } - } else { - tags.push((tag_name, r)); - } - } - } - break; - } - iter.next(); - } -} - -fn extract_tags( - tags: &mut Vec<(String, Range)>, - text: &str, - range: Range, - is_in_comment: &mut Option>, - f: &impl Fn(&str, &Range, bool), -) { - let mut iter = text.char_indices().peekable(); - - while let Some((start_pos, c)) = iter.next() { - if is_in_comment.is_some() { - if text[start_pos..].starts_with("-->") { - *is_in_comment = None; - } - } else if c == '<' { - if text[start_pos..].starts_with("") { + *is_in_comment = None; + } + } else if c == '<' { + if text[start_pos..].starts_with("