diff options
Diffstat (limited to 'src/librustdoc')
62 files changed, 2459 insertions, 2644 deletions
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<Item> { 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<Symbol, Lifetime>, - ) -> Vec<WherePredicate> { + ) -> ThinVec<WherePredicate> { // 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<WherePredicate>, + mut existing_predicates: ThinVec<WherePredicate>, vid_to_region: FxHashMap<ty::RegionVid, ty::Region<'tcx>>, ) -> 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<WherePredicate>) { + 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<T: Debug>(&self, vec: &mut Vec<T>) { + fn unstable_debug_sort<T: Debug>(&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<Option<Cfg>, 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<clean::Function> { 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<Lifetime> { 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<WherePredicate> { 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::<Vec<_>>(); - 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::<Vec<GenericParamDef>>(); + .collect::<ThinVec<GenericParamDef>>(); // 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<Function> { 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<GenericBound> = 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::<Vec<_>>(); + 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<Ty<'tcx>> } 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 `<X as Y>::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<DefId>) -> 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<ast::Attribute>, +) { + 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<Symbol>, + import_id: Option<hir::HirId>, ) -> Vec<Item> { 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,15 +2282,35 @@ 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<Item> { + 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>, kind: hir::UseKind, cx: &mut DocContext<'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<WP>) -> Vec<WP> { +pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> ThinVec<WP> { // 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<WP>) -> Vec<WP> { 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<DefId> 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<RefCell<FxHashMap<DefId, Trait>>>, } @@ -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<Symbol>, pub(crate) attrs: Box<Attributes>, - 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<ItemKind>, pub(crate) item_id: ItemId, - + /// This is the `DefId` of the `use` statement if the item was inlined. + pub(crate) inline_stmt_id: Option<DefId>, pub(crate) cfg: Option<Arc<Cfg>>, } @@ -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<Stability> { 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<Symbol>, kind: ItemKind, attrs: Box<Attributes>, - cx: &mut DocContext<'_>, cfg: Option<Arc<Cfg>>, ) -> 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<Visibility<DefId>> { + 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<Generics>, Vec<GenericBound>), + TyAssocTypeItem(Generics, Vec<GenericBound>), /// An associated type in a trait impl or a provided one in a trait declaration. AssocTypeItem(Box<Typedef>, Vec<GenericBound>), /// 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<Generic>? #[derive(Clone, Debug, Default)] pub(crate) struct Generics { - pub(crate) params: Vec<GenericParamDef>, - pub(crate) where_predicates: Vec<WherePredicate>, + pub(crate) params: ThinVec<GenericParamDef>, + pub(crate) where_predicates: ThinVec<WherePredicate>, } 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<Type>, String), + Array(Box<Type>, Box<str>), /// A raw pointer type: `*const i32`, `*mut i32` RawPointer(Mutability, Box<Type>), /// A reference type: `&i32`, `&'a mut Foo` @@ -2030,27 +2081,9 @@ impl From<hir::PrimTy> 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<CtorKind>, pub(crate) generics: Generics, pub(crate) fields: Vec<Item>, } @@ -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<CtorKind>, pub(crate) fields: Vec<Item>, } @@ -2179,7 +2212,7 @@ impl Span { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) struct Path { pub(crate) res: Res, - pub(crate) segments: Vec<PathSegment>, + pub(crate) segments: ThinVec<PathSegment>, } 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<str> }, /// 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::<Vec<_>>().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<DefId>, ) -> 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::<String>(), ) } 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<String>, + /// 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<String, String>, /// 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<String>, } +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<DefId>, + pub(crate) effective_visibilities: RustdocEffectiveVisibilities, /// The version of the crate being documented, if given from the `--crate-version` flag. pub(crate) crate_version: Option<String>, @@ -132,11 +132,8 @@ struct CacheBuilder<'a, 'tcx> { } impl Cache { - pub(crate) fn new( - effective_visibilities: EffectiveVisibilities<DefId>, - 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<ty::Visibility<DefId>>, + 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<ty::Visibility<DefId>>, + 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; } </style> -<pre><code><span class="attribute">#![crate_type = <span class="string">"lib"</span>] +<pre><code><span class="attr">#![crate_type = <span class="string">"lib"</span>] </span><span class="kw">use </span>std::path::{Path, PathBuf}; -<span class="attribute">#[cfg(target_os = <span class="string">"linux"</span>)] +<span class="attr">#[cfg(target_os = <span class="string">"linux"</span>)] #[cfg(target_os = <span class="string">"windows"</span>)] </span><span class="kw">fn </span>main() -> () { <span class="kw">let </span>foo = <span class="bool-val">true </span>&& <span class="bool-val">false </span>|| <span class="bool-val">true</span>; @@ -23,7 +23,7 @@ <span class="macro">mac!</span>(foo, <span class="kw-2">&mut </span>bar); <span class="macro">assert!</span>(<span class="self">self</span>.length < N && index <= <span class="self">self</span>.length); ::std::env::var(<span class="string">"gateau"</span>).is_ok(); - <span class="attribute">#[rustfmt::skip] + <span class="attr">#[rustfmt::skip] </span><span class="kw">let </span>s:std::path::PathBuf = std::path::PathBuf::new(); <span class="kw">let </span><span class="kw-2">mut </span>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<String>, sidebar: String, content: String, @@ -61,19 +68,17 @@ pub(crate) fn render<T: Print, S: Print>( ) -> 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<String> = style_files - .iter() - .map(StylePath::basename) - .collect::<Result<_, Error>>() - .unwrap_or_default(); + let mut themes: Vec<String> = 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<clean::Type>, } // `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> { </section>\ </noscript>\ <link rel=\"stylesheet\" type=\"text/css\" \ - href=\"{root_path}settings{suffix}.css\">\ - <script defer src=\"{root_path}settings{suffix}.js\"></script>", - root_path = page.static_root_path.unwrap_or(""), - suffix = page.resource_suffix, + href=\"{static_root_path}{settings_css}\">\ + <script defer src=\"{static_root_path}{settings_js}\"></script>", + 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 <a{href} class=\"constant\">{name}</a>: {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 + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len()); + w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len()); write!( w, - "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a{href} class=\"fnname\">{name}</a>\ + "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a{href} class=\"fn\">{name}</a>\ {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, "<h2 id=\"{id}\" class=\"small-section-header\">\ {title}\ - <a href=\"#{id}\" class=\"anchor\"></a>\ + <a href=\"#{id}\" class=\"anchor\">§</a>\ </h2>" ); } @@ -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<String> { + 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, - "<span class=\"notable\">Notable traits for {}</span>\ - <code class=\"content\">", - 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!( + " <a href=\"#\" class=\"notable-traits\" data-ty=\"{ty}\">ⓘ</a>", + 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, + "<h3>Notable traits for <code>{}</code></h3>\ + <pre class=\"content\"><code>", + impl_.for_.print(cx) + ); + } + + //use the "where" class here to make it small + write!( + &mut out, + "<span class=\"where fmt-newline\">{}</span>", + impl_.print(false, cx) + ); + for it in &impl_.items { + if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind { + out.push_str("<span class=\"where fmt-newline\"> "); + let empty_set = FxHashSet::default(); + let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); + assoc_type( &mut out, - "<span class=\"where fmt-newline\">{}</span>", - 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("<span class=\"where fmt-newline\"> "); - 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(";</span>"); - } - } + out.push_str(";</span>"); } } } } } - - if !out.is_empty() { - out.insert_str( - 0, - "<span class=\"notable-traits\"><span class=\"notable-traits-tooltip\">ⓘ\ - <span class=\"notable-traits-tooltiptext\"><span class=\"docblock\">", - ); - out.push_str("</code></span></span></span></span>"); + if out.is_empty() { + write!(&mut out, "</code></pre>",); } - out.into_inner() + (format!("{:#}", ty.print(cx)), out.into_inner()) +} + +pub(crate) fn notable_traits_json<'a>( + tys: impl Iterator<Item = &'a clean::Type>, + 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + 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, "<a href=\"#{}\" class=\"anchor\"></a>", id); + write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id); } w.write_str("<h4 class=\"code-header\">"); 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, "<a href=\"#{}\" class=\"anchor\"></a>", id); + write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id); } w.write_str("<h4 class=\"code-header\">"); assoc_const( @@ -1538,7 +1587,7 @@ fn render_impl( write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); + write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id); } w.write_str("<h4 class=\"code-header\">"); assoc_type( @@ -1564,7 +1613,7 @@ fn render_impl( ); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); + write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id); } w.write_str("<h4 class=\"code-header\">"); assoc_type( @@ -1797,7 +1846,7 @@ pub(crate) fn render_impl_summary( }; write!(w, "<section id=\"{}\" class=\"impl has-srclink\"{}>", id, aliases); render_rightside(w, cx, &i.impl_item, containing_item, RenderMode::Normal); - write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); + write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id); write!(w, "<h3 class=\"code-header\">"); 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#"<span class="prev">≺</span> <span class="next">≻</span>"#); - } - - if needs_expansion { - write!(w, r#"<span class="expand">↕</span>"#); + write!(w, r#"<button class="prev">≺</button> <button class="next">≻</button>"#); } // 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, "</div></div>"); @@ -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::<Vec<_>>(); 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#"<script type="text/json" id="notable-traits-data">{}</script>"#, + 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, "<div class=\"item-left\"><code>{}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, "<div class=\"item-left\"><code>{}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: </div>\ {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(_)) => { "<span title=\"Restricted Visibility\"> 🔒</span> " } _ => "", @@ -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::<Vec<_>>(); let provided_types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>(); @@ -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, "<h2 id=\"{0}\" class=\"small-section-header\">\ - {1}<a href=\"#{0}\" class=\"anchor\"></a>\ + {1}<a href=\"#{0}\" class=\"anchor\">§</a>\ </h2>{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::<Vec<_>>() .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, "<h2 id=\"fields\" class=\"fields small-section-header\">\ - Fields<a href=\"#fields\" class=\"anchor\"></a>\ + Fields<a href=\"#fields\" class=\"anchor\">§</a>\ </h2>" ); 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, "<span id=\"{id}\" class=\"{shortty} small-section-header\">\ - <a href=\"#{id}\" class=\"anchor field\"></a>\ + <a href=\"#{id}\" class=\"anchor field\">§</a>\ <code>{name}: {ty}</code>\ </span>", 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, "<h2 id=\"variants\" class=\"variants small-section-header\">\ - Variants{}<a href=\"#variants\" class=\"anchor\"></a>\ + Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\ </h2>", document_non_exhaustive_header(it) ); document_non_exhaustive(w, it); + write!(w, "<div class=\"variants\">"); for variant in e.variants() { let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); write!( w, - "<h3 id=\"{id}\" class=\"variant small-section-header\">\ - <a href=\"#{id}\" class=\"anchor field\"></a>\ - <code>{name}", + "<section id=\"{id}\" class=\"variant\">\ + <a href=\"#{id}\" class=\"anchor\">§</a>", 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, "<h3 class=\"code-header\">{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("</code>"); - 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("</h3>"); + w.write_str("</h3></section>"); use crate::clean::Variant; @@ -1307,8 +1324,8 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: write!( w, "<div class=\"sub-variant-field\">\ - <span id=\"{id}\" class=\"variant small-section-header\">\ - <a href=\"#{id}\" class=\"anchor field\"></a>\ + <span id=\"{id}\" class=\"small-section-header\">\ + <a href=\"#{id}\" class=\"anchor field\">§</a>\ <code>{f}: {t}</code>\ </span>", 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, "</div>"); } 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, "<h2 id=\"fields\" class=\"fields small-section-header\">\ - {}{}<a href=\"#fields\" class=\"anchor\"></a>\ + {}{}<a href=\"#fields\" class=\"anchor\">§</a>\ </h2>", - 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, "<span id=\"{id}\" class=\"{item_type} small-section-header\">\ - <a href=\"#{id}\" class=\"anchor field\"></a>\ + <a href=\"#{id}\" class=\"anchor field\">§</a>\ <code>{name}: {ty}</code>\ </span>", 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<CtorKind>, 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<VariantIdx>, 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, "<h2 id=\"layout\" class=\"small-section-header\"> \ - Layout<a href=\"#layout\" class=\"anchor\"></a></h2>" + Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>" ); writeln!(w, "<div class=\"docblock\">"); @@ -1901,7 +1927,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) { chapter for details on type layout guarantees.</p></div>" ); w.write_str("<p><strong>Size:</strong> "); - write_size_of_layout(w, ty_layout.layout, 0); + write_size_of_layout(w, &ty_layout.layout.0, 0); writeln!(w, "</p>"); 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, "<li><code>{name}</code>: ", name = name); - write_size_of_layout(w, *layout, tag_size); + write_size_of_layout(w, layout, tag_size); writeln!(w, "</li>"); } w.write_str("</ul>"); 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<FxHashMap<&str, &[u8]>> = 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<str> + 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<str> + 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<Vec<u8>, Error>| { + // InvocationSpecific resources should always be dynamic. + let write_invocation_specific = |p: &str, make_content: &dyn Fn() -> Result<Vec<u8>, 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<String> = 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("<pre class=\"src-line-numbers\">"); + 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, "<span id=\"{0}\">{0}</span>", line) + writeln!(line_numbers, "<a href=\"#{line}\" id=\"{line}\">{line}</a>") } } - SourceContext::Embedded { offset } => { - for line in 1..=lines { - writeln!(line_numbers, "<span>{0}</span>", line + offset) + SourceContext::Embedded { offset, needs_expansion } => { + extra = if needs_expansion { + Some(r#"<button class="expand">↕</button>"#) + } else { + None + }; + for line_number in 1..=lines { + let line = line_number + offset; + writeln!(line_numbers, "<span>{line}</span>") } } } line_numbers.write_str("</pre>"); - 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 <span> 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 <select> appropriately if it becomes too large */ - display: inline-flex; - max-width: 100%; + display: flex; /* make things look like in a line, despite the fact that we're using a layout with boxes (i.e. from the flex layout) */ align-items: baseline; } #crate-search-div { - display: inline-block; /* ensures that 100% in properties of #crate-search-div:after are relative to the size of this div */ position: relative; @@ -803,10 +785,8 @@ table, } #crate-search { min-width: 115px; - padding: 0; /* keep these two in sync with "@-moz-document url-prefix()" below */ - padding-left: 4px; - padding-right: 23px; + padding: 0 23px 0 4px; /* prevents the <select> 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) <select>s */ @-moz-document url-prefix() { @@ -847,8 +830,13 @@ so that we can apply CSS-filters to change the arrow color in themes */ background-repeat: no-repeat; background-size: 20px; background-position: calc(100% - 2px) 56%; - /* image is black color, themes should apply a "filter" property to change the color */ - background-image: /* AUTOREPLACE: */url("down-arrow.svg"); + /* image is black color */ + background-image: url("down-arrow-927217e04c7463ac.svg"); + /* changes the arrow image color */ + filter: var(--crate-search-div-filter); +} +#crate-search-div:hover::after, #crate-search-div:focus-within::after { + filter: var(--crate-search-div-hover-filter); } #crate-search > option { font-size: 1rem; @@ -873,40 +861,29 @@ so that we can apply CSS-filters to change the arrow color in themes */ .search-results { display: none; - padding-bottom: 2em; } .search-results.active { display: block; - /* prevent overhanging tabs from moving the first result */ - clear: both; -} - -.search-results .desc > span { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - display: block; } .search-results > a { - display: block; + display: flex; /* A little margin ensures the browser's outlining of focused links has room to display. */ margin-left: 2px; margin-right: 2px; - border-bottom: 1px solid #aaa3; + border-bottom: 1px solid var(--search-result-border-color); + gap: 1em; } .search-results > a > div { - display: flex; - flex-flow: row wrap; + flex: 1; } -.search-results .result-name, .search-results div.desc { - width: 50%; -} -.search-results .result-name { - padding-right: 1em; +.search-results > a > div.desc { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } .search-results a:hover, @@ -914,26 +891,32 @@ so that we can apply CSS-filters to change the arrow color in themes */ background-color: var(--search-result-link-focus-background-color); } +.search-results .result-name span.alias { + color: var(--search-results-alias-color); +} +.search-results .result-name span.grey { + color: var(--search-results-grey-color); +} + .popover { - font-size: 1rem; position: absolute; + top: 100%; right: 0; z-index: 2; display: block; margin-top: 7px; border-radius: 3px; border: 1px solid var(--border-color); - font-size: 1rem; + --popover-arrow-offset: 11px; } /* This rule is to draw the little arrow connecting the settings menu to the gear icon. */ .popover::before { content: ''; position: absolute; - right: 11px; + right: var(--popover-arrow-offset); border: solid var(--border-color); border-width: 1px 1px 0 0; - display: inline-block; padding: 4px; transform: rotate(-45deg); top: -5px; @@ -947,16 +930,12 @@ so that we can apply CSS-filters to change the arrow color in themes */ /* use larger max-width for help popover, but not for help.html */ #help.popover { max-width: 600px; -} - -#help.popover::before { - right: 48px; + --popover-arrow-offset: 48px; } #help dt { float: left; clear: left; - display: block; margin-right: 0.5rem; } #help span.top, #help span.bottom { @@ -1024,11 +1003,9 @@ so that we can apply CSS-filters to change the arrow color in themes */ 0 -1px 0 black; } -.module-item .stab, -.import-item .stab { +.item-left .stab { border-radius: 3px; display: inline-block; - font-size: 0.875rem; line-height: 1.2; margin-bottom: 0; margin-left: 0.3125em; @@ -1048,7 +1025,6 @@ so that we can apply CSS-filters to change the arrow color in themes */ .rightside { padding-left: 12px; - padding-right: 2px; float: right; } @@ -1090,7 +1066,7 @@ pre.rust .bool-val { pre.rust .self { color: var(--code-highlight-self-color); } -pre.rust .attribute { +pre.rust .attr { color: var(--code-highlight-attribute-color); } pre.rust .macro, @@ -1147,7 +1123,6 @@ pre.rust .doccomment { .example-wrap .tooltip { position: absolute; display: block; - cursor: pointer; left: -25px; top: 5px; } @@ -1164,6 +1139,8 @@ pre.rust .doccomment { width: max-content; top: -2px; z-index: 1; + background-color: var(--tooltip-background-color); + color: var(--tooltip-color); } .example-wrap .tooltip::before { @@ -1172,10 +1149,10 @@ pre.rust .doccomment { top: 50%; left: 16px; margin-top: -5px; - border-width: 5px; - border-style: solid; display: none; z-index: 1; + border: 5px solid transparent; + border-right-color: var(--tooltip-background-color); } .example-wrap.ignore .tooltip::after { @@ -1203,7 +1180,6 @@ pre.rust .doccomment { } a.test-arrow { - display: inline-block; visibility: hidden; position: absolute; padding: 5px 10px 5px 10px; @@ -1212,6 +1188,12 @@ a.test-arrow { top: 5px; right: 5px; z-index: 1; + color: var(--test-arrow-color); + background-color: var(--test-arrow-background-color); +} +a.test-arrow:hover { + color: var(--test-arrow-hover-color); + background-color: var(--test-arrow-hover-background-color); } .example-wrap:hover .test-arrow { visibility: visible; @@ -1231,12 +1213,6 @@ a.test-arrow { font-size: 1.25rem; } -h3.variant { - font-weight: 600; - font-size: 1.125rem; - margin-bottom: 10px; -} - .sub-variant h4 { font-size: 1rem; font-weight: 400; @@ -1253,59 +1229,40 @@ h3.variant { margin-left: 24px; } -:target > code, :target > .code-header { - opacity: 1; -} - :target { padding-right: 3px; + background-color: var(--target-background-color); + border-right: 3px solid var(--target-border-color); } -.notable-traits-tooltip { - display: inline-block; - cursor: pointer; -} - -.notable-traits:hover .notable-traits-tooltiptext, -.notable-traits .notable-traits-tooltiptext.force-tooltip { - display: inline-block; +.notable-traits { + color: inherit; + margin-right: 15px; + position: relative; } -.notable-traits .notable-traits-tooltiptext { - display: none; - padding: 5px 3px 3px 3px; - border-radius: 6px; - margin-left: 5px; - z-index: 10; - font-size: 1rem; - cursor: default; +/* placeholder thunk so that the mouse can easily travel from "(i)" to popover + the resulting "hover tunnel" is a stepped triangle, approximating + https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown */ +.notable-traits:hover::after { position: absolute; - border: 1px solid; -} - -.notable-traits-tooltip::after { - /* The margin on the tooltip does not capture hover events, - this extends the area of hover enough so that mouse hover is not - lost when moving the mouse to the tooltip */ - content: "\00a0\00a0\00a0"; -} - -.notable-traits .docblock { - margin: 0; + top: calc(100% - 10px); + left: -15px; + right: -15px; + height: 20px; + content: "\00a0"; } -.notable-traits .notable { - margin: 0; - margin-bottom: 13px; - font-size: 1.1875rem; - font-weight: 600; - display: block; +.notable .docblock { + margin: 0.25em 0.5em; } -.notable-traits .docblock code.content { +.notable .docblock pre, .notable .docblock code { + background: transparent; margin: 0; padding: 0; font-size: 1.25rem; + white-space: pre-wrap; } .search-failed { @@ -1335,7 +1292,6 @@ h3.variant { #titles > button { text-align: center; font-size: 1.125rem; - cursor: pointer; border: 0; border-top: 2px solid; flex: 1; @@ -1348,12 +1304,6 @@ h3.variant { font-size: 1rem; } -.notable-traits { - cursor: pointer; - z-index: 2; - margin-left: 5px; -} - #sidebar-toggle { position: sticky; top: 0; @@ -1362,8 +1312,8 @@ h3.variant { border-bottom: 1px solid; display: flex; height: 40px; - justify-content: center; - align-items: center; + justify-content: stretch; + align-items: stretch; z-index: 10; } #source-sidebar { @@ -1376,44 +1326,51 @@ h3.variant { border-bottom: 1px solid var(--border-color); margin-bottom: 6px; } +#source-sidebar div.files > a:hover, details.dir-entry summary:hover, +#source-sidebar div.files > a:focus, details.dir-entry summary:focus { + background-color: var(--source-sidebar-background-hover); +} +#source-sidebar div.files > a.selected { + background-color: var(--source-sidebar-background-selected); +} #sidebar-toggle > button { font-size: inherit; font-weight: bold; background: none; color: inherit; - cursor: pointer; text-align: center; border: none; outline: none; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - /* work around button layout strangeness: https://stackoverflow.com/q/7271561 */ - width: 100%; + flex: 1 1; /* iOS button gradient: https://stackoverflow.com/q/5438567 */ -webkit-appearance: none; opacity: 1; } #settings-menu, #help-button { margin-left: 4px; - outline: none; + display: flex; } #settings-menu > a, #help-button > a, #copy-path { width: 33px; - cursor: pointer; - line-height: 1.5; } #settings-menu > a, #help-button > a { - padding: 5px; - height: 100%; - display: block; + display: flex; + align-items: center; + justify-content: center; background-color: var(--button-background-color); border: 1px solid var(--border-color); border-radius: 2px; + color: var(--settings-button-color); + /* Rare exception to specifying font sizes in rem. Since this is acting + as an icon, it's okay to specify their sizes in pixels. */ + font-size: 20px; +} + +#settings-menu > a:hover, #settings-menu > a:focus, +#help-button > a:hover, #help-button > a:focus { + border-color: var(--settings-button-border-focus); } #copy-path { @@ -1444,14 +1401,6 @@ h3.variant { animation: rotating 2s linear infinite; } -#help-button > a { - text-align: center; - /* Rare exception to specifying font sizes in rem. Since this is acting - as an icon, it's okay to specify their sizes in pixels. */ - font-size: 20px; - padding-top: 2px; -} - kbd { display: inline-block; padding: 3px 5px; @@ -1461,6 +1410,9 @@ kbd { border: solid 1px var(--border-color); border-radius: 3px; cursor: default; + color: var(--kbd--color); + background-color: var(--kbd-background); + box-shadow: inset 0 -1px 0 var(--kbd-box-shadow-color); } ul.all-items > li { @@ -1522,6 +1474,7 @@ details.rustdoc-toggle { "Expand description" or "Show methods". */ details.rustdoc-toggle > summary.hideme { cursor: pointer; + font-size: 1rem; } details.rustdoc-toggle > summary { @@ -1539,15 +1492,15 @@ details.rustdoc-toggle > summary.hideme > span { } details.rustdoc-toggle > summary::before { + background: url("toggle-plus-1092eb4930d581b0.svg") no-repeat top left; content: ""; cursor: pointer; width: 16px; height: 16px; - background-repeat: no-repeat; - background-position: top left; display: inline-block; vertical-align: middle; opacity: .5; + filter: var(--toggle-filter); } details.rustdoc-toggle > summary.hideme > span, @@ -1584,13 +1537,6 @@ details.rustdoc-toggle > summary:focus-visible::before { outline-offset: 1px; } -details.rustdoc-toggle.top-doc > summary, -details.rustdoc-toggle.top-doc > summary::before, -details.rustdoc-toggle.non-exhaustive > summary, -details.rustdoc-toggle.non-exhaustive > summary::before { - font-size: 1rem; -} - details.non-exhaustive { margin-bottom: 8px; } @@ -1623,27 +1569,11 @@ details.rustdoc-toggle[open] > summary.hideme > span { display: none; } -details.rustdoc-toggle[open] > summary::before, -details.rustdoc-toggle[open] > summary.hideme::before { - background-image: /* AUTOREPLACE: */url("toggle-minus.svg"); +details.rustdoc-toggle[open] > summary::before { + background: url("toggle-minus-31bbd6e4c77f5c96.svg") no-repeat top left; } -details.rustdoc-toggle > summary::before { - background-image: /* AUTOREPLACE: */url("toggle-plus.svg"); -} - -details.rustdoc-toggle[open] > summary::before, -details.rustdoc-toggle[open] > summary.hideme::before { - width: 16px; - height: 16px; - background-repeat: no-repeat; - background-position: top left; - display: inline-block; - content: ""; -} - -details.rustdoc-toggle[open] > summary::after, -details.rustdoc-toggle[open] > summary.hideme::after { +details.rustdoc-toggle[open] > summary::after { content: "Collapse"; } @@ -1677,7 +1607,6 @@ in storage.js } .rustdoc { - padding-top: 0px; /* Sidebar should overlay main content, rather than pushing main content to the right. Turn off `display: flex` on the body element. */ display: block; @@ -1702,10 +1631,6 @@ in storage.js content: "Since "; } - #copy-path { - display: none; - } - /* Hide the logo and item name from the sidebar. Those are displayed in the mobile-topbar instead. */ .sidebar .sidebar-logo, @@ -1719,12 +1644,10 @@ in storage.js /* Hide the sidebar offscreen while not in use. Doing this instead of display: none means the sidebar stays visible for screen readers, which is useful for navigation. */ left: -1000px; - margin-left: 0; - margin: 0; - padding: 0; z-index: 11; /* Reduce height slightly to account for mobile topbar. */ height: calc(100vh - 45px); + width: 200px; } /* The source view uses a different design for the sidebar toggle, and doesn't have a topbar, @@ -1765,16 +1688,10 @@ in storage.js white-space: nowrap; } - .mobile-topbar .logo-container { - max-height: 45px; - } - .mobile-topbar .logo-container > img { max-width: 35px; max-height: 35px; - margin-left: 20px; - margin-top: 5px; - margin-bottom: 5px; + margin: 5px 0 5px 20px; } .mobile-topbar { @@ -1800,7 +1717,6 @@ in storage.js .sidebar-elems { margin-top: 1em; - background-color: var(--sidebar-background-color); } .content { @@ -1815,21 +1731,6 @@ in storage.js display: block; } - /* Because of ios, we need to actually have a full height sidebar title so the - * actual sidebar can show up. But then we need to make it transparent so we don't - * hide content. The filler just allows to create the background for the sidebar - * title. But because of the absolute position, I had to lower the z-index. - */ - #sidebar-filler { - position: fixed; - left: 45px; - width: calc(100% - 45px); - top: 0; - height: 45px; - z-index: -1; - border-bottom: 1px solid; - } - #main-content > details.rustdoc-toggle > summary::before, #main-content > div > details.rustdoc-toggle > summary::before { left: -11px; @@ -1846,7 +1747,6 @@ in storage.js z-index: 10; border-top-right-radius: 3px; border-bottom-right-radius: 3px; - cursor: pointer; border: 1px solid; border-left: 0; } @@ -1862,37 +1762,22 @@ in storage.js border-bottom: 1px solid; } - .notable-traits .notable-traits-tooltiptext { - left: 0; - top: 100%; - } - - /* We don't display the help button on mobile devices. */ - #help-button { + /* We don't display these buttons on mobile devices. */ + #copy-path, #help-button { display: none; } /* Display an alternating layout on tablets and phones */ - .item-table { + .item-table, .item-row, .item-left, .item-right, + .search-results > a, .search-results > a > div { display: block; } - .item-row { - display: flex; - flex-flow: column wrap; - } - .item-left, .item-right { - width: 100%; - } /* Display an alternating layout on tablets and phones */ .search-results > a { - border-bottom: 1px solid #aaa9; padding: 5px 0px; } - .search-results .result-name, .search-results div.desc { - width: 100%; - } - .search-results div.desc, .item-right { + .search-results > a > div.desc, .item-right { padding-left: 2em; } @@ -1922,6 +1807,22 @@ in storage.js } } +/* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */ +@media (min-width: 701px) { + /* Places file-link for a scraped example on top of the example to save space. + We only do this on large screens so the file-link doesn't overlap too much + with the example's content. */ + .scraped-example-title { + position: absolute; + z-index: 10; + background: var(--main-background-color); + bottom: 8px; + right: 5px; + padding: 2px 4px; + box-shadow: 0 0 4px var(--main-background-color); + } +} + @media print { nav.sidebar, nav.sub, .out-of-band, a.srclink, #copy-path, details.rustdoc-toggle[open] > summary::before, details.rustdoc-toggle > summary::before, @@ -1969,24 +1870,28 @@ in storage.js } } -.method-toggle > summary, +.variant, .implementors-toggle > summary, .impl, #implementors-list > .docblock, .impl-items > section, -.methods > section +.impl-items > .rustdoc-toggle > summary, +.methods > section, +.methods > .rustdoc-toggle > summary { margin-bottom: 0.75em; } -.method-toggle[open]:not(:last-child), +.variants > .docblock, +.impl-items > .rustdoc-toggle[open]:not(:last-child), +.methods > .rustdoc-toggle[open]:not(:last-child), .implementors-toggle[open]:not(:last-child) { margin-bottom: 2em; } -#trait-implementations-list .method-toggle:not(:last-child), -#synthetic-implementations-list .method-toggle:not(:last-child), -#blanket-implementations-list .method-toggle:not(:last-child) { +#trait-implementations-list .impl-items > .rustdoc-toggle:not(:last-child), +#synthetic-implementations-list .impl-items > .rustdoc-toggle:not(:last-child), +#blanket-implementations-list .impl-items > .rustdoc-toggle:not(:last-child) { margin-bottom: 1em; } @@ -1999,12 +1904,16 @@ in storage.js font-size: 12px; position: relative; bottom: 1px; - background: transparent; border-width: 1px; border-style: solid; border-radius: 50px; } +.scraped-example { + /* So .scraped-example-title can be positioned absolutely */ + position: relative; +} + .scraped-example .code-wrapper { position: relative; display: flex; @@ -2014,54 +1923,62 @@ in storage.js } .scraped-example:not(.expanded) .code-wrapper { - max-height: 240px; + /* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number + * of lines shown in the un-expanded example code viewer. This pre needs to have + * a max-height equal to line-height * N. The line-height is currently 1.5em, + * and we include additional 10px for padding. */ + max-height: calc(1.5em * 5 + 10px); } .scraped-example:not(.expanded) .code-wrapper pre { overflow-y: hidden; - max-height: 240px; padding-bottom: 0; + /* See above comment, should be the same max-height. */ + max-height: calc(1.5em * 5 + 10px); } -.scraped-example .code-wrapper .prev { +.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper, +.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre { + /* See above comment, except this height is based on HIDDEN_MAX_LINES. */ + max-height: calc(1.5em * 10 + 10px); +} + +.scraped-example .code-wrapper .next, +.scraped-example .code-wrapper .prev, +.scraped-example .code-wrapper .expand { + color: var(--main-color); position: absolute; top: 0.25em; + z-index: 1; + padding: 0; + background: none; + border: none; + /* iOS button gradient: https://stackoverflow.com/q/5438567 */ + -webkit-appearance: none; + opacity: 1; +} +.scraped-example .code-wrapper .prev { right: 2.25em; - z-index: 100; - cursor: pointer; } - .scraped-example .code-wrapper .next { - position: absolute; - top: 0.25em; right: 1.25em; - z-index: 100; - cursor: pointer; } - .scraped-example .code-wrapper .expand { - position: absolute; - top: 0.25em; right: 0.25em; - z-index: 100; - cursor: pointer; } -.scraped-example:not(.expanded) .code-wrapper:before { +.scraped-example:not(.expanded) .code-wrapper:before, +.scraped-example:not(.expanded) .code-wrapper:after { content: " "; width: 100%; height: 5px; position: absolute; - z-index: 100; + z-index: 1; +} +.scraped-example:not(.expanded) .code-wrapper:before { top: 0; } - .scraped-example:not(.expanded) .code-wrapper:after { - content: " "; - width: 100%; - height: 5px; - position: absolute; - z-index: 100; bottom: 0; } @@ -2070,12 +1987,15 @@ in storage.js padding: 14px 0; } +.scraped-example .code-wrapper .src-line-numbers a, .scraped-example .code-wrapper .src-line-numbers span { padding: 0 14px; } .scraped-example .code-wrapper .example-wrap { - flex: 1; + display: grid; + grid-template-columns: max-content auto; + width: 100%; overflow-x: auto; overflow-y: hidden; margin-bottom: 0; diff --git a/src/librustdoc/html/static/css/settings.css b/src/librustdoc/html/static/css/settings.css index 83939f63b..1f6fb961e 100644 --- a/src/librustdoc/html/static/css/settings.css +++ b/src/librustdoc/html/static/css/settings.css @@ -8,7 +8,8 @@ flex-wrap: wrap; } -.setting-line .radio-line input { +.setting-line .radio-line input, +.setting-line .toggle input { margin-right: 0.3em; height: 1.2rem; width: 1.2rem; @@ -17,9 +18,18 @@ outline: none; -webkit-appearance: none; cursor: pointer; +} +.setting-line .radio-line input { border-radius: 50%; } -.setting-line .radio-line input + span { +.setting-line .toggle input:checked { + content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">\ + <path d="M7,25L17,32L33,12" fill="none" stroke="black" stroke-width="5"/>\ + <path d="M7,23L17,30L33,10" fill="none" stroke="white" stroke-width="5"/></svg>'); +} + +.setting-line .radio-line input + span, +.setting-line .toggle span { padding-bottom: 1px; } @@ -49,37 +59,6 @@ cursor: pointer; } -.toggle input { - opacity: 0; - position: absolute; -} - -.slider { - position: relative; - width: 45px; - min-width: 45px; - display: block; - height: 28px; - margin-right: 20px; - cursor: pointer; - background-color: #ccc; - transition: .3s; -} - -.slider:before { - position: absolute; - content: ""; - height: 19px; - width: 19px; - left: 4px; - bottom: 4px; - transition: .3s; -} - -input:checked + .slider:before { - transform: translateX(19px); -} - .setting-line > .sub-settings { padding-left: 42px; width: 100%; @@ -94,7 +73,11 @@ input:checked + .slider:before { box-shadow: inset 0 0 0 3px var(--main-background-color); background-color: var(--settings-input-color); } -.setting-line .radio-line input:focus { +.setting-line .toggle input:checked { + background-color: var(--settings-input-color); +} +.setting-line .radio-line input:focus, +.setting-line .toggle input:focus { box-shadow: 0 0 1px 1px var(--settings-input-color); } /* In here we combine both `:focus` and `:checked` properties. */ @@ -102,9 +85,7 @@ input:checked + .slider:before { box-shadow: inset 0 0 0 3px var(--main-background-color), 0 0 2px 2px var(--settings-input-color); } -.setting-line .radio-line input:hover { +.setting-line .radio-line input:hover, +.setting-line .toggle input:hover { border-color: var(--settings-input-color) !important; } -input:checked + .slider { - background-color: var(--settings-input-color); -} diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index fdfdb3e19..0436fe013 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -7,6 +7,8 @@ Original by Dempfi (https://github.com/dempfi/ayu) --main-background-color: #0f1419; --main-color: #c5c5c5; --settings-input-color: #ffb454; + --settings-button-color: #fff; + --settings-button-border-focus: #e0e0e0; --sidebar-background-color: #14191f; --sidebar-background-color-hover: rgba(70, 70, 70, 0.33); --code-block-background-color: #191f26; @@ -19,6 +21,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) --right-side-color: grey; --code-attribute-color: #999; --toggles-color: #999; + --toggle-filter: invert(100%); --search-input-focused-border-color: #5c6773; /* Same as `--border-color`. */ --copy-path-button-color: #fff; --copy-path-img-filter: invert(70%); @@ -38,9 +41,12 @@ Original by Dempfi (https://github.com/dempfi/ayu) --sidebar-link-color: #53b1db; --sidebar-current-link-background-color: transparent; --search-result-link-focus-background-color: #3c3c3c; + --search-result-border-color: #aaa3; + --search-color: #fff; + --search-results-alias-color: #c5c5c5; + --search-results-grey-color: #999; --stab-background-color: #314559; --stab-code-color: #e6e1cf; - --search-color: #fff; --code-highlight-kw-color: #ff7733; --code-highlight-kw-2-color: #ff7733; --code-highlight-lifetime-color: #ff7733; @@ -58,16 +64,30 @@ Original by Dempfi (https://github.com/dempfi/ayu) --example-line-numbers-border-color: none; --src-line-numbers-span-color: #5c6773; --src-line-number-highlighted-background-color: rgba(255, 236, 164, 0.06); -} - -.slider { - background-color: #ccc; -} -.slider:before { - background-color: white; -} -input:focus + .slider { - box-shadow: 0 0 0 2px #0a84ff, 0 0 0 6px rgba(10, 132, 255, 0.3); + --test-arrow-color: #788797; + --test-arrow-background-color: rgba(57, 175, 215, 0.09); + --test-arrow-hover-color: #c5c5c5; + --test-arrow-hover-background-color: rgba(57, 175, 215, 0.368); + --target-background-color: rgba(255, 236, 164, 0.06); + --target-border-color: rgba(255, 180, 76, 0.85); + --tooltip-background-color: #314559; + --tooltip-color: #c5c5c5; + --kbd-color: #c5c5c5; + --kbd-background: #314559; + --kbd-box-shadow-color: #5c6773; + --rust-logo-filter: drop-shadow(1px 0 0px #fff) + drop-shadow(0 1px 0 #fff) + drop-shadow(-1px 0 0 #fff) + drop-shadow(0 -1px 0 #fff); + /* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */ + --crate-search-div-filter: invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg) + brightness(94%) contrast(94%); + --crate-search-div-hover-filter: invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) + brightness(113%) contrast(76%); + --crate-search-hover-border: #e0e0e0; + --source-sidebar-background-selected: #14191f; + --source-sidebar-background-hover: #14191f; + --table-alt-row-background-color: #191f26; } h1, h2, h3, h4 { @@ -99,13 +119,6 @@ pre, .rustdoc.source .example-wrap { color: #e6e1cf; } -.rust-logo { - filter: drop-shadow(1px 0 0px #fff) - drop-shadow(0 1px 0 #fff) - drop-shadow(-1px 0 0 #fff) - drop-shadow(0 -1px 0 #fff); -} - .sidebar .current, .sidebar a:hover { color: #ffb44c; @@ -147,21 +160,6 @@ body.source .example-wrap pre.rust a { background: #333; } -details.rustdoc-toggle > summary::before { - filter: invert(100%); -} - -#crate-search-div::after { - /* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */ - filter: invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg) brightness(94%) contrast(94%); -} -#crate-search:hover, #crate-search:focus { - border-color: #e0e0e0 !important; -} -#crate-search-div:hover::after, #crate-search-div:focus-within::after { - filter: invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) brightness(113%) contrast(76%); -} - .module-item .stab, .import-item .stab { color: #000; @@ -171,40 +169,6 @@ details.rustdoc-toggle > summary::before { color: #788797; } -a.test-arrow { - font-size: 100%; - color: #788797; - border-radius: 4px; - background-color: rgba(57, 175, 215, 0.09); -} - -a.test-arrow:hover { - background-color: rgba(57, 175, 215, 0.368); - color: #c5c5c5; -} - -:target { - background: rgba(255, 236, 164, 0.06); - border-right: 3px solid rgba(255, 180, 76, 0.85); -} - -.search-failed a { - color: #39AFD7; -} - -.tooltip::after { - background-color: #314559; - color: #c5c5c5; -} - -.tooltip::before { - border-color: transparent #314559 transparent transparent; -} - -.notable-traits-tooltiptext { - background-color: #314559; -} - #titles > button.selected { background-color: #141920 !important; border-bottom: 1px solid #ffb44c !important; @@ -236,42 +200,16 @@ pre.rust .kw {} pre.rust .self, pre.rust .bool-val, pre.rust .prelude-val, pre.rust .attribute {} pre.rust .kw-2, pre.rust .prelude-ty {} -kbd { - color: #c5c5c5; - background-color: #314559; - box-shadow: inset 0 -1px 0 #5c6773; -} - -#settings-menu > a, #help-button > a { - color: #fff; -} - #settings-menu > a img { filter: invert(100); } -#settings-menu > a:hover, #settings-menu > a:focus, -#help-button > a:hover, #help-button > a:focus { - border-color: #e0e0e0; -} - -.search-results .result-name span.alias { - color: #c5c5c5; -} -.search-results .result-name span.grey { - color: #999; -} - #source-sidebar > .title { color: #fff; } #source-sidebar div.files > a:hover, details.dir-entry summary:hover, -#source-sidebar div.files > a:focus, details.dir-entry summary:focus { - background-color: #14191f; - color: #ffb44c; -} +#source-sidebar div.files > a:focus, details.dir-entry summary:focus, #source-sidebar div.files > a.selected { - background-color: #14191f; color: #ffb44c; } diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index 361d3d4a2..d945e956c 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -2,6 +2,8 @@ --main-background-color: #353535; --main-color: #ddd; --settings-input-color: #2196f3; + --settings-button-color: #000; + --settings-button-border-focus: #ffb900; --sidebar-background-color: #505050; --sidebar-background-color-hover: #676767; --code-block-background-color: #2A2A2A; @@ -14,6 +16,7 @@ --right-side-color: grey; --code-attribute-color: #999; --toggles-color: #999; + --toggle-filter: invert(100%); --search-input-focused-border-color: #008dfd; --copy-path-button-color: #999; --copy-path-img-filter: invert(50%); @@ -33,9 +36,12 @@ --sidebar-link-color: #fdbf35; --sidebar-current-link-background-color: #444; --search-result-link-focus-background-color: #616161; + --search-result-border-color: #aaa3; + --search-color: #111; + --search-results-alias-color: #fff; + --search-results-grey-color: #ccc; --stab-background-color: #314559; --stab-code-color: #e6e1cf; - --search-color: #111; --code-highlight-kw-color: #ab8ac1; --code-highlight-kw-2-color: #769acb; --code-highlight-lifetime-color: #d97f26; @@ -53,23 +59,30 @@ --example-line-numbers-border-color: #4a4949; --src-line-numbers-span-color: #3b91e2; --src-line-number-highlighted-background-color: #0a042f; -} - -.slider { - background-color: #ccc; -} -.slider:before { - background-color: white; -} -input:focus + .slider { - box-shadow: 0 0 0 2px #0a84ff, 0 0 0 6px rgba(10, 132, 255, 0.3); -} - -.rust-logo { - filter: drop-shadow(1px 0 0px #fff) + --test-arrow-color: #dedede; + --test-arrow-background-color: rgba(78, 139, 202, 0.2); + --test-arrow-hover-color: #dedede; + --test-arrow-hover-background-color: #4e8bca; + --target-background-color: #494a3d; + --target-border-color: #bb7410; + --tooltip-background-color: #000; + --tooltip-color: #fff; + --kbd-color: #000; + --kbd-background: #fafbfc; + --kbd-box-shadow-color: #c6cbd1; + --rust-logo-filter: drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) - drop-shadow(0 -1px 0 #fff) + drop-shadow(0 -1px 0 #fff); + /* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */ + --crate-search-div-filter: invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) + brightness(90%) contrast(90%); + --crate-search-div-hover-filter: invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) + brightness(100%) contrast(91%); + --crate-search-hover-border: #2196f3; + --source-sidebar-background-selected: #333; + --source-sidebar-background-hover: #444; + --table-alt-row-background-color: #2A2A2A; } .content .item-info::before { color: #ccc; } @@ -78,53 +91,6 @@ body.source .example-wrap pre.rust a { background: #333; } -details.rustdoc-toggle > summary::before { - filter: invert(100%); -} - -#crate-search-div::after { - /* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */ - filter: invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) brightness(90%) contrast(90%); -} -#crate-search:hover, #crate-search:focus { - border-color: #2196f3 !important; -} -#crate-search-div:hover::after, #crate-search-div:focus-within::after { - filter: invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%); -} - -a.test-arrow { - color: #dedede; - background-color: rgba(78, 139, 202, 0.2); -} - -a.test-arrow:hover{ - background-color: #4e8bca; -} - -:target { - background-color: #494a3d; - border-right: 3px solid #bb7410; -} - -.search-failed a { - color: #0089ff; -} - -.tooltip::after { - background-color: #000; - color: #fff; - border-color: #000; -} - -.tooltip::before { - border-color: transparent black transparent transparent; -} - -.notable-traits-tooltiptext { - background-color: #111; -} - #titles > button:not(.selected) { background-color: #252525; border-top-color: #252525; @@ -139,36 +105,6 @@ a.test-arrow:hover{ color: #888; } -kbd { - color: #000; - background-color: #fafbfc; - box-shadow: inset 0 -1px 0 #c6cbd1; -} - -#settings-menu > a, #help-button > a { - color: #000; -} - -#settings-menu > a:hover, #settings-menu > a:focus, -#help-button > a:hover, #help-button > a:focus { - border-color: #ffb900; -} - -.search-results .result-name span.alias { - color: #fff; -} -.search-results .result-name span.grey { - color: #ccc; -} - -#source-sidebar div.files > a:hover, details.dir-entry summary:hover, -#source-sidebar div.files > a:focus, details.dir-entry summary:focus { - background-color: #444; -} -#source-sidebar div.files > a.selected { - background-color: #333; -} - .scraped-example-list .scrape-help { border-color: #aaa; color: #eee; diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index 5eb4bbcf8..58955a793 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -2,6 +2,8 @@ --main-background-color: white; --main-color: black; --settings-input-color: #2196f3; + --settings-button-color: #000; + --settings-button-border-focus: #717171; --sidebar-background-color: #F5F5F5; --sidebar-background-color-hover: #E0E0E0; --code-block-background-color: #F5F5F5; @@ -14,6 +16,7 @@ --right-side-color: grey; --code-attribute-color: #999; --toggles-color: #999; + --toggle-filter: none; --search-input-focused-border-color: #66afe9; --copy-path-button-color: #999; --copy-path-img-filter: invert(50%); @@ -33,9 +36,12 @@ --sidebar-link-color: #356da4; --sidebar-current-link-background-color: #fff; --search-result-link-focus-background-color: #ccc; + --search-result-border-color: #aaa3; + --search-color: #000; + --search-results-alias-color: #000; + --search-results-grey-color: #999; --stab-background-color: #fff5d6; --stab-code-color: #000; - --search-color: #000; --code-highlight-kw-color: #8959a8; --code-highlight-kw-2-color: #4271ae; --code-highlight-lifetime-color: #b76514; @@ -53,22 +59,27 @@ --example-line-numbers-border-color: #c7c7c7; --src-line-numbers-span-color: #c67e2d; --src-line-number-highlighted-background-color: #fdffd3; -} - -.slider { - background-color: #ccc; -} -.slider:before { - background-color: white; -} -input:focus + .slider { - box-shadow: 0 0 0 2px #0a84ff, 0 0 0 6px rgba(10, 132, 255, 0.3); -} - -.rust-logo { - /* This rule exists to force other themes to explicitly style the logo. - * Rustdoc has a custom linter for this purpose. - */ + --test-arrow-color: #f5f5f5; + --test-arrow-background-color: rgba(78, 139, 202, 0.2); + --test-arrow-hover-color: #f5f5f5; + --test-arrow-hover-background-color: #4e8bca; + --target-background-color: #fdffd3; + --target-border-color: #ad7c37; + --tooltip-background-color: #000; + --tooltip-color: #fff; + --kbd-color: #000; + --kbd-background: #fafbfc; + --kbd-box-shadow-color: #c6cbd1; + --rust-logo-filter: initial; + /* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */ + --crate-search-div-filter: invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) + brightness(114%) contrast(76%); + --crate-search-div-hover-filter: invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) + brightness(96%) contrast(93%); + --crate-search-hover-border: #717171; + --source-sidebar-background-selected: #fff; + --source-sidebar-background-hover: #e0e0e0; + --table-alt-row-background-color: #F5F5F5; } .content .item-info::before { color: #ccc; } @@ -77,48 +88,6 @@ body.source .example-wrap pre.rust a { background: #eee; } -#crate-search-div::after { - /* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */ - filter: invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) brightness(114%) contrast(76%); -} -#crate-search:hover, #crate-search:focus { - border-color: #717171 !important; -} -#crate-search-div:hover::after, #crate-search-div:focus-within::after { - filter: invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%); -} - -a.test-arrow { - color: #f5f5f5; - background-color: rgba(78, 139, 202, 0.2); -} - -a.test-arrow:hover{ - background-color: #4e8bca; -} - -:target { - background: #FDFFD3; - border-right: 3px solid #AD7C37; -} - -.search-failed a { - color: #3873AD; -} - -.tooltip::after { - background-color: #000; - color: #fff; -} - -.tooltip::before { - border-color: transparent black transparent transparent; -} - -.notable-traits-tooltiptext { - background-color: #eee; -} - #titles > button:not(.selected) { background-color: #e6e6e6; border-top-color: #e6e6e6; @@ -133,35 +102,6 @@ a.test-arrow:hover{ color: #888; } -kbd { - color: #000; - background-color: #fafbfc; - box-shadow: inset 0 -1px 0 #c6cbd1; -} - -#settings-menu > a, #help-button > a { - color: #000; -} - -#settings-menu > a:hover, #settings-menu > a:focus, -#help-button > a:hover, #help-button > a:focus { - border-color: #717171; -} - -.search-results .result-name span.alias { - color: #000; -} -.search-results .result-name span.grey { - color: #999; -} - -#source-sidebar div.files > a:hover, details.dir-entry summary:hover, -#source-sidebar div.files > a:focus, details.dir-entry summary:focus { - background-color: #E0E0E0; -} -#source-sidebar div.files > a.selected { - background-color: #fff; -} .scraped-example-list .scrape-help { border-color: #555; color: #333; diff --git a/src/librustdoc/html/static/fonts/README.txt b/src/librustdoc/html/static/fonts/README.txt new file mode 100644 index 000000000..0db15996d --- /dev/null +++ b/src/librustdoc/html/static/fonts/README.txt @@ -0,0 +1,12 @@ +The Nanum Barun Gothic fonts are shipped with rustdoc because the default fonts +on many Windows installs render Korean very badly. See: + - https://github.com/rust-lang/rust/pull/84048, + - https://github.com/rust-lang/rust/issues/84035 + - https://github.com/rust-lang/rust/pull/90232 + +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 diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 33480fa41..152116089 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -47,10 +47,8 @@ function blurHandler(event, parentElem, hideCallback) { } } -(function() { - window.rootPath = getVar("root-path"); - window.currentCrate = getVar("current-crate"); -}()); +window.rootPath = getVar("root-path"); +window.currentCrate = getVar("current-crate"); function setMobileTopbar() { // FIXME: It would be nicer to generate this text content directly in HTML, @@ -183,9 +181,9 @@ function browserSupportsHistoryApi() { } // eslint-disable-next-line no-unused-vars -function loadCss(cssFileName) { +function loadCss(cssUrl) { const link = document.createElement("link"); - link.href = resourcePath(cssFileName, ".css"); + link.href = cssUrl; link.type = "text/css"; link.rel = "stylesheet"; document.getElementsByTagName("head")[0].appendChild(link); @@ -204,12 +202,13 @@ function loadCss(cssFileName) { if (event.ctrlKey || event.altKey || event.metaKey) { return; } + window.hideAllModals(false); addClass(getSettingsButton(), "rotate"); event.preventDefault(); // Sending request for the CSS and the JS files at the same time so it will // hopefully be loaded when the JS will generate the settings content. - loadCss("settings"); - loadScript(resourcePath("settings", ".js")); + loadCss(getVar("static-root-path") + getVar("settings-css")); + loadScript(getVar("static-root-path") + getVar("settings-js")); }; window.searchState = { @@ -286,7 +285,7 @@ function loadCss(cssFileName) { function loadSearch() { if (!searchLoaded) { searchLoaded = true; - loadScript(resourcePath("search", ".js")); + loadScript(getVar("static-root-path") + getVar("search-js")); loadScript(resourcePath("search-index", ".js")); } } @@ -303,13 +302,15 @@ function loadCss(cssFileName) { const params = searchState.getQueryStringParams(); if (params.search !== undefined) { - const search = searchState.outputElement(); - search.innerHTML = "<h3 class=\"search-loading\">" + - searchState.loadingText + "</h3>"; - searchState.showResults(search); + searchState.setLoadingSearch(); loadSearch(); } }, + setLoadingSearch: () => { + const search = searchState.outputElement(); + search.innerHTML = "<h3 class=\"search-loading\">" + searchState.loadingText + "</h3>"; + searchState.showResults(search); + }, }; function getPageId() { @@ -379,7 +380,7 @@ function loadCss(cssFileName) { } ev.preventDefault(); searchState.defocus(); - window.hidePopoverMenus(); + window.hideAllModals(true); // true = reset focus for notable traits } function handleShortcut(ev) { @@ -621,7 +622,7 @@ function loadCss(cssFileName) { const innerToggle = document.getElementById(toggleAllDocsId); removeClass(innerToggle, "will-expand"); onEachLazy(document.getElementsByClassName("rustdoc-toggle"), e => { - if (!hasClass(e, "type-contents-toggle")) { + if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) { e.open = true; } }); @@ -725,12 +726,9 @@ function loadCss(cssFileName) { }); }; - (function() { - // To avoid checking on "rustdoc-line-numbers" value on every loop... - if (getSettingValue("line-numbers") === "true") { - window.rustdoc_add_line_numbers_to_examples(); - } - }()); + if (getSettingValue("line-numbers") === "true") { + window.rustdoc_add_line_numbers_to_examples(); + } let oldSidebarScrollPosition = null; @@ -772,6 +770,7 @@ function loadCss(cssFileName) { }; function showSidebar() { + window.hideAllModals(false); window.rustdocMobileScrollLock(); const sidebar = document.getElementsByClassName("sidebar")[0]; addClass(sidebar, "shown"); @@ -790,6 +789,19 @@ function loadCss(cssFileName) { // we need to switch away from mobile mode and make the main content area scrollable. hideSidebar(); } + if (window.CURRENT_NOTABLE_ELEMENT) { + // As a workaround to the behavior of `contains: layout` used in doc togglers, the + // notable traits popup is positioned using javascript. + // + // This means when the window is resized, we need to redo the layout. + const base = window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE; + const force_visible = base.NOTABLE_FORCE_VISIBLE; + hideNotable(false); + if (force_visible) { + showNotable(base); + base.NOTABLE_FORCE_VISIBLE = true; + } + } }); function handleClick(id, f) { @@ -822,10 +834,123 @@ function loadCss(cssFileName) { }); }); + function showNotable(e) { + if (!window.NOTABLE_TRAITS) { + const data = document.getElementById("notable-traits-data"); + if (data) { + window.NOTABLE_TRAITS = JSON.parse(data.innerText); + } else { + throw new Error("showNotable() called on page without any notable traits!"); + } + } + if (window.CURRENT_NOTABLE_ELEMENT && window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE === e) { + // Make this function idempotent. + return; + } + window.hideAllModals(false); + const ty = e.getAttribute("data-ty"); + const wrapper = document.createElement("div"); + wrapper.innerHTML = "<div class=\"docblock\">" + window.NOTABLE_TRAITS[ty] + "</div>"; + wrapper.className = "notable popover"; + const focusCatcher = document.createElement("div"); + focusCatcher.setAttribute("tabindex", "0"); + focusCatcher.onfocus = hideNotable; + wrapper.appendChild(focusCatcher); + const pos = e.getBoundingClientRect(); + // 5px overlap so that the mouse can easily travel from place to place + wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px"; + wrapper.style.left = 0; + wrapper.style.right = "auto"; + wrapper.style.visibility = "hidden"; + const body = document.getElementsByTagName("body")[0]; + body.appendChild(wrapper); + const wrapperPos = wrapper.getBoundingClientRect(); + // offset so that the arrow points at the center of the "(i)" + const finalPos = pos.left + window.scrollX - wrapperPos.width + 24; + if (finalPos > 0) { + wrapper.style.left = finalPos + "px"; + } else { + wrapper.style.setProperty( + "--popover-arrow-offset", + (wrapperPos.right - pos.right + 4) + "px" + ); + } + wrapper.style.visibility = ""; + window.CURRENT_NOTABLE_ELEMENT = wrapper; + window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE = e; + wrapper.onpointerleave = function(ev) { + // If this is a synthetic touch event, ignore it. A click event will be along shortly. + if (ev.pointerType !== "mouse") { + return; + } + if (!e.NOTABLE_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) { + hideNotable(true); + } + }; + } + + function notableBlurHandler(event) { + if (window.CURRENT_NOTABLE_ELEMENT && + !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT) && + !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT) && + !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE) && + !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE) + ) { + // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari. + // When I click the button on an already-opened notable trait popover, Safari + // hides the popover and then immediately shows it again, while everyone else hides it + // and it stays hidden. + // + // To work around this, make sure the click finishes being dispatched before + // hiding the popover. Since `hideNotable()` is idempotent, this makes Safari behave + // consistently with the other two. + setTimeout(() => hideNotable(false), 0); + } + } + + function hideNotable(focus) { + if (window.CURRENT_NOTABLE_ELEMENT) { + if (window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE) { + if (focus) { + window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.focus(); + } + window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE = false; + } + const body = document.getElementsByTagName("body")[0]; + body.removeChild(window.CURRENT_NOTABLE_ELEMENT); + window.CURRENT_NOTABLE_ELEMENT = null; + } + } + onEachLazy(document.getElementsByClassName("notable-traits"), e => { e.onclick = function() { - this.getElementsByClassName("notable-traits-tooltiptext")[0] - .classList.toggle("force-tooltip"); + this.NOTABLE_FORCE_VISIBLE = this.NOTABLE_FORCE_VISIBLE ? false : true; + if (window.CURRENT_NOTABLE_ELEMENT && !this.NOTABLE_FORCE_VISIBLE) { + hideNotable(true); + } else { + showNotable(this); + window.CURRENT_NOTABLE_ELEMENT.setAttribute("tabindex", "0"); + window.CURRENT_NOTABLE_ELEMENT.focus(); + window.CURRENT_NOTABLE_ELEMENT.onblur = notableBlurHandler; + } + return false; + }; + e.onpointerenter = function(ev) { + // If this is a synthetic touch event, ignore it. A click event will be along shortly. + if (ev.pointerType !== "mouse") { + return; + } + showNotable(this); + }; + e.onpointerleave = function(ev) { + // If this is a synthetic touch event, ignore it. A click event will be along shortly. + if (ev.pointerType !== "mouse") { + return; + } + if (!this.NOTABLE_FORCE_VISIBLE && + !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT)) { + hideNotable(true); + } }; }); @@ -929,6 +1054,17 @@ function loadCss(cssFileName) { } /** + * Hide popover menus, notable trait tooltips, and the sidebar (if applicable). + * + * Pass "true" to reset focus for notable traits. + */ + window.hideAllModals = function(switchFocus) { + hideSidebar(); + window.hidePopoverMenus(); + hideNotable(switchFocus); + }; + + /** * Hide all the popover menus. */ window.hidePopoverMenus = function() { @@ -959,7 +1095,7 @@ function loadCss(cssFileName) { function showHelp() { const menu = getHelpMenu(true); if (menu.style.display === "none") { - window.hidePopoverMenus(); + window.hideAllModals(); menu.style.display = ""; } } diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index d0fd115fd..7a3a9c5f3 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -3,25 +3,33 @@ "use strict"; (function() { - // Number of lines shown when code viewer is not expanded - const MAX_LINES = 10; + // Number of lines shown when code viewer is not expanded. + // DEFAULT is the first example shown by default, while HIDDEN is + // the examples hidden beneath the "More examples" toggle. + // + // NOTE: these values MUST be synchronized with certain rules in rustdoc.css! + const DEFAULT_MAX_LINES = 5; + const HIDDEN_MAX_LINES = 10; // Scroll code block to the given code location - function scrollToLoc(elt, loc) { + function scrollToLoc(elt, loc, isHidden) { const lines = elt.querySelector(".src-line-numbers"); let scrollOffset; // If the block is greater than the size of the viewer, // then scroll to the top of the block. Otherwise scroll // to the middle of the block. - if (loc[1] - loc[0] > MAX_LINES) { + const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES; + if (loc[1] - loc[0] > maxLines) { const line = Math.max(0, loc[0] - 1); scrollOffset = lines.children[line].offsetTop; } else { const wrapper = elt.querySelector(".code-wrapper"); const halfHeight = wrapper.offsetHeight / 2; - const offsetMid = (lines.children[loc[0]].offsetTop - + lines.children[loc[1]].offsetTop) / 2; + const offsetTop = lines.children[loc[0]].offsetTop; + const lastLine = lines.children[loc[1]]; + const offsetBot = lastLine.offsetTop + lastLine.offsetHeight; + const offsetMid = (offsetTop + offsetBot) / 2; scrollOffset = offsetMid - halfHeight; } @@ -29,7 +37,7 @@ elt.querySelector(".rust").scrollTo(0, scrollOffset); } - function updateScrapedExample(example) { + function updateScrapedExample(example, isHidden) { const locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); let locIndex = 0; const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight")); @@ -40,7 +48,7 @@ const onChangeLoc = changeIndex => { removeClass(highlights[locIndex], "focus"); changeIndex(); - scrollToLoc(example, locs[locIndex][0]); + scrollToLoc(example, locs[locIndex][0], isHidden); addClass(highlights[locIndex], "focus"); const url = locs[locIndex][1]; @@ -57,7 +65,7 @@ }); }); - example.querySelector("next") + example.querySelector(".next") .addEventListener("click", () => { onChangeLoc(() => { locIndex = (locIndex + 1) % locs.length; @@ -70,7 +78,7 @@ expandButton.addEventListener("click", () => { if (hasClass(example, "expanded")) { removeClass(example, "expanded"); - scrollToLoc(example, locs[0][0]); + scrollToLoc(example, locs[0][0], isHidden); } else { addClass(example, "expanded"); } @@ -78,11 +86,11 @@ } // Start with the first example in view - scrollToLoc(example, locs[0][0]); + scrollToLoc(example, locs[0][0], isHidden); } const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example"); - onEachLazy(firstExamples, updateScrapedExample); + onEachLazy(firstExamples, el => updateScrapedExample(el, false)); onEachLazy(document.querySelectorAll(".more-examples-toggle"), toggle => { // Allow users to click the left border of the <details> section to close it, // since the section can be large and finding the [+] button is annoying. @@ -99,7 +107,7 @@ // depends on offsetHeight, a property that requires an element to be visible to // compute correctly. setTimeout(() => { - onEachLazy(moreExamples, updateScrapedExample); + onEachLazy(moreExamples, el => updateScrapedExample(el, true)); }); }, {once: true}); }); diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index d04ec357c..23ae4e970 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1491,6 +1491,7 @@ function initSearch(rawSearchIndex) { const target = searchState.focusedByTab[searchState.currentTab] || document.querySelectorAll(".search-results.active a").item(0) || document.querySelectorAll("#titles > button").item(searchState.currentTab); + searchState.focusedByTab[searchState.currentTab] = null; if (target) { target.focus(); } @@ -1593,7 +1594,6 @@ function initSearch(rawSearchIndex) { link.className = "result-" + type; link.href = item.href; - const wrapper = document.createElement("div"); const resultName = document.createElement("div"); resultName.className = "result-name"; @@ -1614,16 +1614,13 @@ function initSearch(rawSearchIndex) { resultName.insertAdjacentHTML( "beforeend", item.displayPath + "<span class=\"" + type + "\">" + name + extra + "</span>"); - wrapper.appendChild(resultName); + link.appendChild(resultName); const description = document.createElement("div"); description.className = "desc"; - const spanDesc = document.createElement("span"); - spanDesc.insertAdjacentHTML("beforeend", item.desc); + description.insertAdjacentHTML("beforeend", item.desc); - description.appendChild(spanDesc); - wrapper.appendChild(description); - link.appendChild(wrapper); + link.appendChild(description); output.appendChild(link); }); } else if (query.error === null) { @@ -1769,13 +1766,13 @@ function initSearch(rawSearchIndex) { * @param {boolean} [forced] */ function search(e, forced) { - const params = searchState.getQueryStringParams(); - const query = parseQuery(searchState.input.value.trim()); - if (e) { e.preventDefault(); } + const query = parseQuery(searchState.input.value.trim()); + let filterCrates = getFilterCrates(); + if (!forced && query.userQuery === currentResults) { if (query.userQuery.length > 0) { putBackSearch(); @@ -1783,7 +1780,9 @@ function initSearch(rawSearchIndex) { return; } - let filterCrates = getFilterCrates(); + searchState.setLoadingSearch(); + + const params = searchState.getQueryStringParams(); // In case we have no information about the saved crate and there is a URL query parameter, // we override it with the URL query parameter. diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 5e1c7e6f0..589bfc793 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -9,13 +9,16 @@ const isSettingsPage = window.location.pathname.endsWith("/settings.html"); function changeSetting(settingName, value) { + if (settingName === "theme") { + const useSystem = value === "system preference" ? "true" : "false"; + updateLocalStorage("use-system-theme", useSystem); + } updateLocalStorage(settingName, value); switch (settingName) { case "theme": case "preferred-dark-theme": case "preferred-light-theme": - case "use-system-theme": updateSystemTheme(); updateLightAndDark(); break; @@ -45,7 +48,6 @@ } function showLightAndDark() { - addClass(document.getElementById("theme").parentElement, "hidden"); removeClass(document.getElementById("preferred-light-theme").parentElement, "hidden"); removeClass(document.getElementById("preferred-dark-theme").parentElement, "hidden"); } @@ -53,11 +55,11 @@ function hideLightAndDark() { addClass(document.getElementById("preferred-light-theme").parentElement, "hidden"); addClass(document.getElementById("preferred-dark-theme").parentElement, "hidden"); - removeClass(document.getElementById("theme").parentElement, "hidden"); } function updateLightAndDark() { - if (getSettingValue("use-system-theme") !== "false") { + const useSystem = getSettingValue("use-system-theme"); + if (useSystem === "true" || (useSystem === null && getSettingValue("theme") === null)) { showLightAndDark(); } else { hideLightAndDark(); @@ -66,8 +68,7 @@ function setEvents(settingsElement) { updateLightAndDark(); - onEachLazy(settingsElement.getElementsByClassName("slider"), elem => { - const toggle = elem.previousElementSibling; + onEachLazy(settingsElement.querySelectorAll("input[type=\"checkbox\"]"), toggle => { const settingId = toggle.id; const settingValue = getSettingValue(settingId); if (settingValue !== null) { @@ -92,7 +93,18 @@ }); onEachLazy(settingsElement.querySelectorAll("input[type=\"radio\"]"), elem => { const settingId = elem.name; - const settingValue = getSettingValue(settingId); + let settingValue = getSettingValue(settingId); + if (settingId === "theme") { + const useSystem = getSettingValue("use-system-theme"); + if (useSystem === "true" || settingValue === null) { + if (useSystem !== "false") { + settingValue = "system preference"; + } else { + // This is the default theme. + settingValue = "light"; + } + } + } if (settingValue !== null && settingValue !== "null") { elem.checked = settingValue === elem.value; } @@ -121,27 +133,30 @@ if (setting["options"] !== undefined) { // This is a select setting. - output += `<div class="radio-line" id="${js_data_name}">\ - <span class="setting-name">${setting_name}</span>\ - <div class="choices">`; + output += `\ +<div class="radio-line" id="${js_data_name}"> + <span class="setting-name">${setting_name}</span> +<div class="choices">`; onEach(setting["options"], option => { const checked = option === setting["default"] ? " checked" : ""; + const full = `${js_data_name}-${option.replace(/ /g,"-")}`; - output += `<label for="${js_data_name}-${option}" class="choice">\ - <input type="radio" name="${js_data_name}" \ - id="${js_data_name}-${option}" value="${option}"${checked}>\ - <span>${option}</span>\ - </label>`; + output += `\ +<label for="${full}" class="choice"> + <input type="radio" name="${js_data_name}" + id="${full}" value="${option}"${checked}> + <span>${option}</span> +</label>`; }); output += "</div></div>"; } else { // This is a toggle. const checked = setting["default"] === true ? " checked" : ""; - output += `<label class="toggle">\ - <input type="checkbox" id="${js_data_name}"${checked}>\ - <span class="slider"></span>\ - <span class="label">${setting_name}</span>\ - </label>`; + output += `\ +<label class="toggle">\ + <input type="checkbox" id="${js_data_name}"${checked}>\ + <span class="label">${setting_name}</span>\ +</label>`; } output += "</div>"; } @@ -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<u8> { + 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<StaticFiles> = std::sync::LazyLock::new(|| StaticFiles { + $($field: StaticFile::new($file_path, include_bytes!($file_path)),)+ + }); + + pub(crate) fn for_each<E>(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 @@ <meta name="description" content="{{page.description}}"> {#- -#} <meta name="keywords" content="{{page.keywords}}"> {#- -#} <title>{{page.title}}</title> {#- -#} - <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}SourceSerif4-Regular.ttf.woff2"> {#- -#} - <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}FiraSans-Regular.woff2"> {#- -#} - <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}FiraSans-Medium.woff2"> {#- -#} - <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}SourceCodePro-Regular.ttf.woff2"> {#- -#} - <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}SourceSerif4-Bold.ttf.woff2"> {#- -#} - <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}SourceCodePro-Semibold.ttf.woff2"> {#- -#} + <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_serif_4_regular}}"> {#- -#} + <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.fira_sans_regular}}"> {#- -#} + <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.fira_sans_medium}}"> {#- -#} + <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_code_pro_regular}}"> {#- -#} + <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_serif_4_bold}}"> {#- -#} + <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_code_pro_semibold}}"> {#- -#} <link rel="stylesheet" {# -#} - href="{{static_root_path|safe}}normalize{{page.resource_suffix}}.css"> {#- -#} + href="{{static_root_path|safe}}{{files.normalize_css}}"> {#- -#} <link rel="stylesheet" {# -#} - href="{{static_root_path|safe}}rustdoc{{page.resource_suffix}}.css" {# -#} + href="{{static_root_path|safe}}{{files.rustdoc_css}}" {# -#} id="mainThemeStyle"> {#- -#} + <link rel="stylesheet" id="themeStyle" href="{{static_root_path|safe}}{{files.theme_light_css}}"> {#- -#} + <link rel="stylesheet" disabled href="{{static_root_path|safe}}{{files.theme_dark_css}}"> {#- -#} + <link rel="stylesheet" disabled href="{{static_root_path|safe}}{{files.theme_ayu_css}}"> {#- -#} {%- for theme in themes -%} - <link rel="stylesheet" {# -#} - href="{{static_root_path|safe}}{{theme}}{{page.resource_suffix}}.css" {# -#} - {%- if theme == "light" -%} - id="themeStyle" - {%- else -%} - disabled - {%- endif -%} - > + <link rel="stylesheet" disabled href="{{page.root_path|safe}}{{theme}}{{page.resource_suffix}}.css"> {#- -#} {%- endfor -%} <script id="default-settings" {# -#} {% for (k, v) in layout.default_settings %} data-{{k}}="{{v}}" {%- endfor -%} ></script> {#- -#} - <script src="{{static_root_path|safe}}storage{{page.resource_suffix}}.js"></script> {#- -#} + <script src="{{static_root_path|safe}}{{files.storage_js}}"></script> {#- -#} {%- if page.css_class.contains("crate") -%} <script defer src="{{page.root_path|safe}}crates{{page.resource_suffix}}.js"></script> {#- -#} {%- else if page.css_class == "source" -%} - <script defer src="{{static_root_path|safe}}source-script{{page.resource_suffix}}.js"></script> {#- -#} + <script defer src="{{static_root_path|safe}}{{files.source_script_js}}"></script> {#- -#} <script defer src="{{page.root_path|safe}}source-files{{page.resource_suffix}}.js"></script> {#- -#} {%- else if !page.css_class.contains("mod") -%} <script defer src="sidebar-items{{page.resource_suffix}}.js"></script> {#- -#} {%- endif -%} - <script defer src="{{static_root_path|safe}}main{{page.resource_suffix}}.js"></script> {#- -#} + <script defer src="{{static_root_path|safe}}{{files.main_js}}"></script> {#- -#} {%- if layout.scrape_examples_extension -%} - <script defer src="{{page.root_path|safe}}scrape-examples{{page.resource_suffix}}.js"></script> {#- -#} + <script defer src="{{static_root_path|safe}}{{files.scrape_examples_js}}"></script> {#- -#} {%- endif -%} <noscript> {#- -#} <link rel="stylesheet" {# -#} - href="{{static_root_path|safe}}noscript{{page.resource_suffix}}.css"> {#- -#} + href="{{static_root_path|safe}}{{files.noscript_css}}"> {#- -#} </noscript> {#- -#} {%- if layout.css_file_extension.is_some() -%} <link rel="stylesheet" {# -#} @@ -58,11 +54,11 @@ <link rel="icon" href="{{layout.favicon}}"> {#- -#} {%- else -%} <link rel="alternate icon" type="image/png" {# -#} - href="{{static_root_path|safe}}favicon-16x16{{page.resource_suffix}}.png"> {#- -#} + href="{{static_root_path|safe}}{{files.rust_favicon_png_16}}"> {#- -#} <link rel="alternate icon" type="image/png" {# -#} - href="{{static_root_path|safe}}favicon-32x32{{page.resource_suffix}}.png"> {#- -#} + href="{{static_root_path|safe}}{{files.rust_favicon_png_32}}"> {#- -#} <link rel="icon" type="image/svg+xml" {# -#} - href="{{static_root_path|safe}}favicon{{page.resource_suffix}}.svg"> {#- -#} + href="{{static_root_path|safe}}{{files.rust_favicon_svg}}"> {#- -#} {%- endif -%} {{- layout.external_html.in_header|safe -}} </head> {#- -#} @@ -81,7 +77,7 @@ {%- if !layout.logo.is_empty() -%} <img src="{{layout.logo}}" alt="logo"> {#- -#} {%- else -%} - <img class="rust-logo" src="{{static_root_path|safe}}rust-logo{{page.resource_suffix}}.svg" alt="logo"> {#- -#} + <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {#- -#} {%- endif -%} </div> {#- -#} </a> {#- -#} @@ -95,7 +91,7 @@ {%- if !layout.logo.is_empty() %} <img src="{{layout.logo}}" alt="logo"> {#- -#} {%- else -%} - <img class="rust-logo" src="{{static_root_path|safe}}rust-logo{{page.resource_suffix}}.svg" alt="logo"> {#- -#} + <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {#- -#} {%- endif -%} </div> {#- -#} </a> {#- -#} @@ -110,7 +106,7 @@ {%- if !layout.logo.is_empty() %} <img src="{{layout.logo}}" alt="logo"> {#- -#} {%- else -%} - <img class="rust-logo" src="{{static_root_path|safe}}rust-logo{{page.resource_suffix}}.svg" alt="logo"> {#- -#} + <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {#- -#} {%- endif -%} </a> {#- -#} {%- endif -%} @@ -119,6 +115,7 @@ <input {# -#} class="search-input" {# -#} name="search" {# -#} + aria-label="Run search in the documentation" {# -#} autocomplete="off" {# -#} spellcheck="false" {# -#} placeholder="Click or press ‘S’ to search, ‘?’ for more options…" {# -#} @@ -129,7 +126,7 @@ <div id="settings-menu" tabindex="-1"> {#- -#} <a href="{{page.root_path|safe}}settings.html" title="settings"> {#- -#} <img width="22" height="22" alt="Change settings" {# -#} - src="{{static_root_path|safe}}wheel{{page.resource_suffix}}.svg"> {#- -#} + src="{{static_root_path|safe}}{{files.wheel_svg}}"> {#- -#} </a> {#- -#} </div> {#- -#} </form> {#- -#} @@ -140,10 +137,14 @@ {{- layout.external_html.after_content|safe -}} <div id="rustdoc-vars" {# -#} data-root-path="{{page.root_path|safe}}" {# -#} + data-static-root-path="{{static_root_path|safe}}" {# -#} data-current-crate="{{layout.krate}}" {# -#} data-themes="{{themes|join(",") }}" {# -#} data-resource-suffix="{{page.resource_suffix}}" {# -#} data-rustdoc-version="{{rustdoc_version}}" {# -#} + data-search-js="{{files.search_js}}" {# -#} + data-settings-js="{{files.settings_js}}" {# -#} + data-settings-css="{{files.settings_css}}" {# -#} > {#- -#} </div> {#- -#} </body> {#- -#} 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 -%} <a class="{{item_type}}" href="#">{{name}}</a> {#- -#} <button id="copy-path" onclick="copy_path(this)" title="Copy item path to clipboard"> {#- -#} - <img src="{{static_root_path|safe}}clipboard{{page.resource_suffix}}.svg" {# -#} + <img src="{{static_root_path|safe}}{{clipboard_svg}}" {# -#} width="19" height="18" {# -#} alt="Copy item path"> {#- -#} </button> {#- -#} @@ -21,8 +21,8 @@ <a class="srclink" href="{{href|safe}}">source</a> · {# -#} {%- else -%} {%- endmatch -%} - <a id="toggle-all-docs" href="javascript:void(0)" title="collapse all docs"> {#- -#} - [<span class="inner">−</span>] {#- -#} - </a> {#- -#} + <button id="toggle-all-docs" title="collapse all docs"> {#- -#} + [<span>−</span>] {#- -#} + </button> {#- -#} </span> {#- -#} </div> {#- -#} 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<ty::Visibility<DefId>>) -> 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<clean::Struct> 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<clean::Type> 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<clean::Impl> for Impl { pub(crate) fn from_function( function: Box<clean::Function>, + 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<clean::Function>, - 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<clean::Variant> for Variant { impl FromWithTcx<clean::Discriminant> 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<ItemType> 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<RustcOptGroup> { 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<RustcOptGroup> { ) }), // 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::<T>(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 <https://example.com/>.` 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<Regex> = 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<usize>, - f: &impl Fn(&DocContext<'_>, &str, &str, Range<usize>), - ) { - 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<usize>| { - 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<String>, - has_errors: bool, -} - -struct BufferEmitter { - buffer: Lrc<Lock<Buffer>>, - fallback_bundle: LazyFallbackBundle, -} - -impl Translate for BufferEmitter { - fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> { - 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<SourceMap>> { - 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<PrimitiveType> = krate.primitives.iter().map(|p| p.1).collect(); + let local_crate = ExternalCrate { crate_num: LOCAL_CRATE }; + let prims: FxHashSet<PrimitiveType> = + 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/lint.rs b/src/librustdoc/passes/lint.rs new file mode 100644 index 000000000..97031c4f0 --- /dev/null +++ b/src/librustdoc/passes/lint.rs @@ -0,0 +1,33 @@ +//! Runs several rustdoc lints, consolidating them into a single pass for +//! efficiency and simplicity. + +mod bare_urls; +mod check_code_block_syntax; +mod html_tags; + +use super::Pass; +use crate::clean::*; +use crate::core::DocContext; +use crate::visit::DocVisitor; + +pub(crate) const RUN_LINTS: Pass = + Pass { name: "run-lints", run: run_lints, description: "runs some of rustdoc's lints" }; + +struct Linter<'a, 'tcx> { + cx: &'a mut DocContext<'tcx>, +} + +pub(crate) fn run_lints(krate: Crate, cx: &mut DocContext<'_>) -> Crate { + Linter { cx }.visit_crate(&krate); + krate +} + +impl<'a, 'tcx> DocVisitor for Linter<'a, 'tcx> { + fn visit_item(&mut self, item: &Item) { + bare_urls::visit_item(self.cx, item); + check_code_block_syntax::visit_item(self.cx, item); + html_tags::visit_item(self.cx, item); + + self.visit_item_recur(item) + } +} diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs new file mode 100644 index 000000000..423230cfe --- /dev/null +++ b/src/librustdoc/passes/lint/bare_urls.rs @@ -0,0 +1,89 @@ +//! 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 <https://example.com/>.` to linkify it. + +use crate::clean::*; +use crate::core::DocContext; +use crate::html::markdown::main_body_opts; +use crate::passes::source_span_for_markdown_range; +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(super) fn visit_item(cx: &DocContext<'_>, item: &Item) { + let Some(hir_id) = DocContext::as_local_hir_id(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<usize>| { + let sp = 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) => find_raw_urls(cx, &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; + } + _ => {} + } + } + } + _ => {} + } + } + } +} + +static URL_REGEX: LazyLock<Regex> = 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") +}); + +fn find_raw_urls( + cx: &DocContext<'_>, + text: &str, + range: Range<usize>, + f: &impl Fn(&DocContext<'_>, &str, &str, Range<usize>), +) { + 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( + cx, + "this URL is not a hyperlink", + url, + Range { start: range.start + url_range.start, end: range.start + url_range.end }, + ); + } +} diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs new file mode 100644 index 000000000..5aa4f238b --- /dev/null +++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs @@ -0,0 +1,170 @@ +//! 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::source_span_for_markdown_range; + +pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item) { + if let Some(dox) = &item.attrs.collapsed_doc_value() { + let sp = item.attr_span(cx.tcx); + let extra = + crate::html::markdown::ExtraInfo::new_did(cx.tcx, item.item_id.expect_def_id(), sp); + for code_block in markdown::rust_code_blocks(dox, &extra) { + check_rust_syntax(cx, item, dox, code_block); + } + } +} + +fn check_rust_syntax( + cx: &DocContext<'_>, + 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(|| cx.tcx.sess.edition()); + let expn_data = + ExpnData::default(ExpnKind::AstPass(AstPass::TestHarness), DUMMY_SP, edition, None, None); + let expn_id = 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 = 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 source_span_for_markdown_range(cx.tcx, dox, &code_block.range, &item.attrs) { + Some(sp) => (sp, true), + None => (item.attr_span(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. + 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 + }); +} + +#[derive(Default)] +struct Buffer { + messages: Vec<String>, + has_errors: bool, +} + +struct BufferEmitter { + buffer: Lrc<Lock<Buffer>>, + fallback_bundle: LazyFallbackBundle, +} + +impl Translate for BufferEmitter { + fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> { + 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<SourceMap>> { + None + } +} diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index a89ed7c7e..070c0aab5 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -1,9 +1,8 @@ //! Detects invalid HTML (like an unclosed `<span>`) 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 crate::passes::source_span_for_markdown_range; use pulldown_cmark::{BrokenLink, Event, LinkType, Parser, Tag}; @@ -11,20 +10,150 @@ 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", -}; +pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { + let tcx = cx.tcx; + let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) + // If non-local, no need to check anything. + else { return }; + let dox = item.attrs.collapsed_doc_value().unwrap_or_default(); + if !dox.is_empty() { + let report_diag = |msg: &str, range: &Range<usize>, is_open_tag: bool| { + let sp = match source_span_for_markdown_range(tcx, &dox, range, &item.attrs) { + Some(sp) => sp, + None => item.attr_span(tcx), + }; + tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, msg, |lint| { + use rustc_lint_defs::Applicability; + // If a tag looks like `<this>`, it might actually be a generic. + // We don't try to detect stuff `<like, this>` because that's not valid HTML, + // and we don't try to detect stuff `<like this>` because that's not valid Rust. + let mut generics_end = range.end; + if let Some(Some(mut generics_start)) = (is_open_tag + && dox[..generics_end].ends_with('>')) + .then(|| extract_path_backwards(&dox, range.start)) + { + while generics_start != 0 + && generics_end < dox.len() + && dox.as_bytes()[generics_start - 1] == b'<' + && dox.as_bytes()[generics_end] == b'>' + { + generics_end += 1; + generics_start -= 1; + if let Some(new_start) = extract_path_backwards(&dox, generics_start) { + generics_start = new_start; + } + if let Some(new_end) = extract_path_forward(&dox, generics_end) { + generics_end = new_end; + } + } + if let Some(new_end) = extract_path_forward(&dox, generics_end) { + generics_end = new_end; + } + let generics_sp = match source_span_for_markdown_range( + tcx, + &dox, + &(generics_start..generics_end), + &item.attrs, + ) { + Some(sp) => sp, + None => item.attr_span(tcx), + }; + // Sometimes, we only extract part of a path. For example, consider this: + // + // <[u32] as IntoIter<u32>>::Item + // ^^^^^ unclosed HTML tag `u32` + // + // We don't have any code for parsing fully-qualified trait paths. + // In theory, we could add it, but doing it correctly would require + // parsing the entire path grammar, which is problematic because of + // overlap between the path grammar and Markdown. + // + // The example above shows that ambiguity. Is `[u32]` intended to be an + // intra-doc link to the u32 primitive, or is it intended to be a slice? + // + // If the below conditional were removed, we would suggest this, which is + // not what the user probably wants. + // + // <[u32] as `IntoIter<u32>`>::Item + // + // We know that the user actually wants to wrap the whole thing in a code + // block, but the only reason we know that is because `u32` does not, in + // fact, implement IntoIter. If the example looks like this: + // + // <[Vec<i32>] as IntoIter<i32>::Item + // + // The ideal fix would be significantly different. + if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<') + || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>') + { + return lint; + } + // multipart form is chosen here because ``Vec<i32>`` would be confusing. + lint.multipart_suggestion( + "try marking as source code", + vec![ + (generics_sp.shrink_to_lo(), String::from("`")), + (generics_sp.shrink_to_hi(), String::from("`")), + ], + Applicability::MaybeIncorrect, + ); + } -struct InvalidHtmlTagsLinter<'a, 'tcx> { - cx: &'a mut DocContext<'tcx>, -} + lint + }); + }; + + let mut tags = Vec::new(); + let mut is_in_comment = None; + let mut in_code_block = false; + + let link_names = item.link_names(&cx.cache); -pub(crate) fn check_invalid_html_tags(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - let mut coll = InvalidHtmlTagsLinter { cx }; - coll.visit_crate(&krate); - krate + let mut replacer = |broken_link: BrokenLink<'_>| { + if let Some(link) = + link_names.iter().find(|link| *link.original_text == *broken_link.reference) + { + Some((link.href.as_str().into(), link.new_text.as_str().into())) + } else if matches!( + &broken_link.link_type, + LinkType::Reference | LinkType::ReferenceUnknown + ) { + // If the link is shaped [like][this], suppress any broken HTML in the [this] part. + // The `broken_intra_doc_links` will report typos in there anyway. + Some(( + broken_link.reference.to_string().into(), + broken_link.reference.to_string().into(), + )) + } else { + None + } + }; + + let p = Parser::new_with_broken_link_callback(&dox, main_body_opts(), Some(&mut replacer)) + .into_offset_iter(); + + for (event, range) in p { + match event { + Event::Start(Tag::CodeBlock(_)) => in_code_block = true, + Event::Html(text) if !in_code_block => { + extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag) + } + Event::End(Tag::CodeBlock(_)) => in_code_block = false, + _ => {} + } + } + + for (tag, range) in tags.iter().filter(|(t, _)| { + let t = t.to_lowercase(); + !ALLOWED_UNCLOSED.contains(&t.as_str()) + }) { + report_diag(&format!("unclosed HTML tag `{}`", tag), range, true); + } + + if let Some(range) = is_in_comment { + report_diag("Unclosed HTML comment", &range, false); + } + } } const ALLOWED_UNCLOSED: &[&str] = &[ @@ -276,155 +405,3 @@ fn extract_tags( } } } - -impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { - fn visit_item(&mut self, item: &Item) { - let tcx = self.cx.tcx; - let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) - // If non-local, no need to check anything. - else { return }; - let dox = item.attrs.collapsed_doc_value().unwrap_or_default(); - if !dox.is_empty() { - let report_diag = |msg: &str, range: &Range<usize>, is_open_tag: bool| { - let sp = match super::source_span_for_markdown_range(tcx, &dox, range, &item.attrs) - { - Some(sp) => sp, - None => item.attr_span(tcx), - }; - tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, msg, |lint| { - use rustc_lint_defs::Applicability; - // If a tag looks like `<this>`, it might actually be a generic. - // We don't try to detect stuff `<like, this>` because that's not valid HTML, - // and we don't try to detect stuff `<like this>` because that's not valid Rust. - let mut generics_end = range.end; - if let Some(Some(mut generics_start)) = (is_open_tag - && dox[..generics_end].ends_with('>')) - .then(|| extract_path_backwards(&dox, range.start)) - { - while generics_start != 0 - && generics_end < dox.len() - && dox.as_bytes()[generics_start - 1] == b'<' - && dox.as_bytes()[generics_end] == b'>' - { - generics_end += 1; - generics_start -= 1; - if let Some(new_start) = extract_path_backwards(&dox, generics_start) { - generics_start = new_start; - } - if let Some(new_end) = extract_path_forward(&dox, generics_end) { - generics_end = new_end; - } - } - if let Some(new_end) = extract_path_forward(&dox, generics_end) { - generics_end = new_end; - } - let generics_sp = match super::source_span_for_markdown_range( - tcx, - &dox, - &(generics_start..generics_end), - &item.attrs, - ) { - Some(sp) => sp, - None => item.attr_span(tcx), - }; - // Sometimes, we only extract part of a path. For example, consider this: - // - // <[u32] as IntoIter<u32>>::Item - // ^^^^^ unclosed HTML tag `u32` - // - // We don't have any code for parsing fully-qualified trait paths. - // In theory, we could add it, but doing it correctly would require - // parsing the entire path grammar, which is problematic because of - // overlap between the path grammar and Markdown. - // - // The example above shows that ambiguity. Is `[u32]` intended to be an - // intra-doc link to the u32 primitive, or is it intended to be a slice? - // - // If the below conditional were removed, we would suggest this, which is - // not what the user probably wants. - // - // <[u32] as `IntoIter<u32>`>::Item - // - // We know that the user actually wants to wrap the whole thing in a code - // block, but the only reason we know that is because `u32` does not, in - // fact, implement IntoIter. If the example looks like this: - // - // <[Vec<i32>] as IntoIter<i32>::Item - // - // The ideal fix would be significantly different. - if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<') - || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>') - { - return lint; - } - // multipart form is chosen here because ``Vec<i32>`` would be confusing. - lint.multipart_suggestion( - "try marking as source code", - vec![ - (generics_sp.shrink_to_lo(), String::from("`")), - (generics_sp.shrink_to_hi(), String::from("`")), - ], - Applicability::MaybeIncorrect, - ); - } - - lint - }); - }; - - let mut tags = Vec::new(); - let mut is_in_comment = None; - let mut in_code_block = false; - - let link_names = item.link_names(&self.cx.cache); - - let mut replacer = |broken_link: BrokenLink<'_>| { - if let Some(link) = - link_names.iter().find(|link| *link.original_text == *broken_link.reference) - { - Some((link.href.as_str().into(), link.new_text.as_str().into())) - } else if matches!( - &broken_link.link_type, - LinkType::Reference | LinkType::ReferenceUnknown - ) { - // If the link is shaped [like][this], suppress any broken HTML in the [this] part. - // The `broken_intra_doc_links` will report typos in there anyway. - Some(( - broken_link.reference.to_string().into(), - broken_link.reference.to_string().into(), - )) - } else { - None - } - }; - - let p = - Parser::new_with_broken_link_callback(&dox, main_body_opts(), Some(&mut replacer)) - .into_offset_iter(); - - for (event, range) in p { - match event { - Event::Start(Tag::CodeBlock(_)) => in_code_block = true, - Event::Html(text) if !in_code_block => { - extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag) - } - Event::End(Tag::CodeBlock(_)) => in_code_block = false, - _ => {} - } - } - - for (tag, range) in tags.iter().filter(|(t, _)| { - let t = t.to_lowercase(); - !ALLOWED_UNCLOSED.contains(&t.as_str()) - }) { - report_diag(&format!("unclosed HTML tag `{}`", tag), range, true); - } - - if let Some(range) = is_in_comment { - report_diag("Unclosed HTML comment", &range, false); - } - } - - self.visit_item_recur(item) - } -} diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index f81b38ea3..634e70ec9 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -12,9 +12,6 @@ use crate::core::DocContext; mod stripper; pub(crate) use stripper::*; -mod bare_urls; -pub(crate) use self::bare_urls::CHECK_BARE_URLS; - mod strip_hidden; pub(crate) use self::strip_hidden::STRIP_HIDDEN; @@ -36,14 +33,11 @@ pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY; mod collect_trait_impls; pub(crate) use self::collect_trait_impls::COLLECT_TRAIT_IMPLS; -mod check_code_block_syntax; -pub(crate) use self::check_code_block_syntax::CHECK_CODE_BLOCK_SYNTAX; - mod calculate_doc_coverage; pub(crate) use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE; -mod html_tags; -pub(crate) use self::html_tags::CHECK_INVALID_HTML_TAGS; +mod lint; +pub(crate) use self::lint::RUN_LINTS; /// A single pass over the cleaned documentation. /// @@ -82,11 +76,9 @@ pub(crate) const PASSES: &[Pass] = &[ STRIP_PRIV_IMPORTS, PROPAGATE_DOC_CFG, COLLECT_INTRA_DOC_LINKS, - CHECK_CODE_BLOCK_SYNTAX, COLLECT_TRAIT_IMPLS, CALCULATE_DOC_COVERAGE, - CHECK_INVALID_HTML_TAGS, - CHECK_BARE_URLS, + RUN_LINTS, ]; /// The list of passes run by default. @@ -97,10 +89,8 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), - ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX), - ConditionalPass::always(CHECK_INVALID_HTML_TAGS), ConditionalPass::always(PROPAGATE_DOC_CFG), - ConditionalPass::always(CHECK_BARE_URLS), + ConditionalPass::always(RUN_LINTS), ]; /// The list of default passes run when `--doc-coverage` is passed to rustdoc. diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index 9914edf30..e07a788a7 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -27,6 +27,7 @@ pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clea // strip all impls referencing stripped items let mut stripper = ImplStripper { + tcx: cx.tcx, retained: &retained, cache: &cx.cache, is_json_output, diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs index 85be8fa10..3bac5a8e5 100644 --- a/src/librustdoc/passes/strip_priv_imports.rs +++ b/src/librustdoc/passes/strip_priv_imports.rs @@ -11,6 +11,6 @@ pub(crate) const STRIP_PRIV_IMPORTS: Pass = Pass { description: "strips all private import statements (`use`, `extern crate`) from a crate", }; -pub(crate) fn strip_priv_imports(krate: clean::Crate, _: &mut DocContext<'_>) -> clean::Crate { - ImportStripper.fold_crate(krate) +pub(crate) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate { + ImportStripper { tcx: cx.tcx }.fold_crate(krate) } diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index 450f69e15..8fc42462d 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -26,12 +26,14 @@ pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> effective_visibilities: &cx.cache.effective_visibilities, update_retained: true, is_json_output, + tcx: cx.tcx, }; - krate = ImportStripper.fold_crate(stripper.fold_crate(krate)); + krate = ImportStripper { tcx: cx.tcx }.fold_crate(stripper.fold_crate(krate)); } // strip all impls referencing private items let mut stripper = ImplStripper { + tcx: cx.tcx, retained: &retained, cache: &cx.cache, is_json_output, diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 0089ce63d..995fb5dcc 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -1,19 +1,20 @@ //! A collection of utility functions for the `strip_*` passes. use rustc_hir::def_id::DefId; -use rustc_middle::middle::privacy::EffectiveVisibilities; +use rustc_middle::ty::{TyCtxt, Visibility}; use rustc_span::symbol::sym; - use std::mem; use crate::clean::{self, Item, ItemId, ItemIdSet, NestedAttributesExt}; use crate::fold::{strip_item, DocFolder}; use crate::formats::cache::Cache; +use crate::visit_lib::RustdocEffectiveVisibilities; -pub(crate) struct Stripper<'a> { +pub(crate) struct Stripper<'a, 'tcx> { pub(crate) retained: &'a mut ItemIdSet, - pub(crate) effective_visibilities: &'a EffectiveVisibilities<DefId>, + pub(crate) effective_visibilities: &'a RustdocEffectiveVisibilities, pub(crate) update_retained: bool, pub(crate) is_json_output: bool, + pub(crate) tcx: TyCtxt<'tcx>, } // We need to handle this differently for the JSON output because some non exported items could @@ -21,18 +22,19 @@ pub(crate) struct Stripper<'a> { // are in the public API, which is not enough. #[inline] fn is_item_reachable( + tcx: TyCtxt<'_>, is_json_output: bool, - effective_visibilities: &EffectiveVisibilities<DefId>, + effective_visibilities: &RustdocEffectiveVisibilities, item_id: ItemId, ) -> bool { if is_json_output { - effective_visibilities.is_reachable(item_id.expect_def_id()) + effective_visibilities.is_reachable(tcx, item_id.expect_def_id()) } else { - effective_visibilities.is_exported(item_id.expect_def_id()) + effective_visibilities.is_exported(tcx, item_id.expect_def_id()) } } -impl<'a> DocFolder for Stripper<'a> { +impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> { fn fold_item(&mut self, i: Item) -> Option<Item> { match *i.kind { clean::StrippedItem(..) => { @@ -66,7 +68,12 @@ impl<'a> DocFolder for Stripper<'a> { | clean::ForeignTypeItem => { let item_id = i.item_id; if item_id.is_local() - && !is_item_reachable(self.is_json_output, self.effective_visibilities, item_id) + && !is_item_reachable( + self.tcx, + self.is_json_output, + self.effective_visibilities, + item_id, + ) { debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name); return None; @@ -74,13 +81,13 @@ impl<'a> DocFolder for Stripper<'a> { } clean::StructFieldItem(..) => { - if !i.visibility.is_public() { + if i.visibility(self.tcx) != Some(Visibility::Public) { return Some(strip_item(i)); } } clean::ModuleItem(..) => { - if i.item_id.is_local() && !i.visibility.is_public() { + if i.item_id.is_local() && i.visibility(self.tcx) != Some(Visibility::Public) { debug!("Stripper: stripping module {:?}", i.name); let old = mem::replace(&mut self.update_retained, false); let ret = strip_item(self.fold_item_recur(i)); @@ -146,14 +153,15 @@ impl<'a> DocFolder for Stripper<'a> { } /// This stripper discards all impls which reference stripped items -pub(crate) struct ImplStripper<'a> { +pub(crate) struct ImplStripper<'a, 'tcx> { + pub(crate) tcx: TyCtxt<'tcx>, pub(crate) retained: &'a ItemIdSet, pub(crate) cache: &'a Cache, pub(crate) is_json_output: bool, pub(crate) document_private: bool, } -impl<'a> ImplStripper<'a> { +impl<'a> ImplStripper<'a, '_> { #[inline] fn should_keep_impl(&self, item: &Item, for_def_id: DefId) -> bool { if !for_def_id.is_local() || self.retained.contains(&for_def_id.into()) { @@ -161,7 +169,7 @@ impl<'a> ImplStripper<'a> { } else if self.is_json_output { // If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we // need to keep it. - self.cache.effective_visibilities.is_exported(for_def_id) + self.cache.effective_visibilities.is_exported(self.tcx, for_def_id) && !item.attrs.lists(sym::doc).has_word(sym::hidden) } else { false @@ -169,7 +177,7 @@ impl<'a> ImplStripper<'a> { } } -impl<'a> DocFolder for ImplStripper<'a> { +impl<'a> DocFolder for ImplStripper<'a, '_> { fn fold_item(&mut self, i: Item) -> Option<Item> { if let clean::ImplItem(ref imp) = *i.kind { // Impl blocks can be skipped if they are: empty; not a trait impl; and have no @@ -185,6 +193,7 @@ impl<'a> DocFolder for ImplStripper<'a> { let item_id = i.item_id; item_id.is_local() && !is_item_reachable( + self.tcx, self.is_json_output, &self.cache.effective_visibilities, item_id, @@ -229,12 +238,16 @@ impl<'a> DocFolder for ImplStripper<'a> { } /// This stripper discards all private import statements (`use`, `extern crate`) -pub(crate) struct ImportStripper; +pub(crate) struct ImportStripper<'tcx> { + pub(crate) tcx: TyCtxt<'tcx>, +} -impl DocFolder for ImportStripper { +impl<'tcx> DocFolder for ImportStripper<'tcx> { fn fold_item(&mut self, i: Item) -> Option<Item> { match *i.kind { - clean::ExternCrateItem { .. } | clean::ImportItem(..) if !i.visibility.is_public() => { + clean::ExternCrateItem { .. } | clean::ImportItem(..) + if i.visibility(self.tcx) != Some(Visibility::Public) => + { None } _ => Some(self.fold_item_recur(i)), diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index dfa6ba38b..f2ee99cd9 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -110,6 +110,7 @@ pub(crate) struct CallData { pub(crate) url: String, pub(crate) display_name: String, pub(crate) edition: Edition, + pub(crate) is_bin: bool, } pub(crate) type FnCallLocations = FxHashMap<PathBuf, CallData>; @@ -122,6 +123,7 @@ struct FindCalls<'a, 'tcx> { cx: Context<'tcx>, target_crates: Vec<CrateNum>, calls: &'a mut AllCallLocations, + bin_crate: bool, } impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx> @@ -245,7 +247,9 @@ where let mk_call_data = || { let display_name = file_path.display().to_string(); let edition = call_span.edition(); - CallData { locations: Vec::new(), url, display_name, edition } + let is_bin = self.bin_crate; + + CallData { locations: Vec::new(), url, display_name, edition, is_bin } }; let fn_key = tcx.def_path_hash(*def_id); @@ -274,6 +278,7 @@ pub(crate) fn run( cache: formats::cache::Cache, tcx: TyCtxt<'_>, options: ScrapeExamplesOptions, + bin_crate: bool, ) -> interface::Result<()> { let inner = move || -> Result<(), String> { // Generates source files for examples @@ -300,7 +305,8 @@ pub(crate) fn run( // Run call-finder on all items let mut calls = FxHashMap::default(); - let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates }; + let mut finder = + FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, bin_crate }; tcx.hir().visit_all_item_likes_in_crate(&mut finder); // The visitor might have found a type error, which we need to diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 06dffce55..22068ebe0 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -7,15 +7,14 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::Node; use rustc_hir::CRATE_HIR_ID; -use rustc_middle::middle::privacy::Level; -use rustc_middle::ty::{TyCtxt, Visibility}; +use rustc_middle::ty::TyCtxt; use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::Span; use std::mem; -use crate::clean::{self, cfg::Cfg, AttributesExt, NestedAttributesExt}; +use crate::clean::{cfg::Cfg, AttributesExt, NestedAttributesExt}; use crate::core; /// This module is used to store stuff from Rust's AST in a more convenient @@ -26,8 +25,8 @@ pub(crate) struct Module<'hir> { pub(crate) where_inner: Span, pub(crate) mods: Vec<Module<'hir>>, pub(crate) id: hir::HirId, - // (item, renamed) - pub(crate) items: Vec<(&'hir hir::Item<'hir>, Option<Symbol>)>, + // (item, renamed, import_id) + pub(crate) items: Vec<(&'hir hir::Item<'hir>, Option<Symbol>, Option<hir::HirId>)>, pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>)>, } @@ -94,6 +93,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { hir::CRATE_HIR_ID, self.cx.tcx.hir().root_module(), self.cx.tcx.crate_name(LOCAL_CRATE), + None, ); // `#[macro_export] macro_rules!` items are reexported at the top level of the @@ -114,7 +114,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { if self.cx.tcx.has_attr(def_id, sym::macro_export) { if inserted.insert(def_id) { let item = self.cx.tcx.hir().expect_item(local_def_id); - top_level_module.items.push((item, None)); + top_level_module.items.push((item, None, None)); } } } @@ -156,6 +156,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { id: hir::HirId, m: &'tcx hir::Mod<'tcx>, name: Symbol, + parent_id: Option<hir::HirId>, ) -> Module<'tcx> { let mut om = Module::new(name, id, m.spans.inner_span); let def_id = self.cx.tcx.hir().local_def_id(id).to_def_id(); @@ -167,7 +168,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { continue; } - self.visit_item(item, None, &mut om); + self.visit_item(item, None, &mut om, parent_id); } for &i in m.item_ids { let item = self.cx.tcx.hir().item(i); @@ -175,7 +176,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // Later passes in rustdoc will de-duplicate by name and kind, so if glob- // imported items appear last, then they'll be the ones that get discarded. if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { - self.visit_item(item, None, &mut om); + self.visit_item(item, None, &mut om, parent_id); } } self.inside_public_path = orig_inside_public_path; @@ -221,23 +222,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // made reachable by cross-crate inlining which we're checking here. // (this is done here because we need to know this upfront). if !res_did.is_local() && !is_no_inline { - let attrs = clean::inline::load_attrs(self.cx, res_did); - let self_is_hidden = attrs.lists(sym::doc).has_word(sym::hidden); - if !self_is_hidden { - if let Res::Def(kind, did) = res { - if kind == DefKind::Mod { - crate::visit_lib::LibEmbargoVisitor::new(self.cx).visit_mod(did) - } else { - // All items need to be handled here in case someone wishes to link - // to them with intra-doc links - self.cx.cache.effective_visibilities.set_public_at_level( - did, - || Visibility::Restricted(CRATE_DEF_ID), - Level::Direct, - ); - } - } - } + crate::visit_lib::lib_embargo_visit_item(self.cx, res_did); return false; } @@ -246,7 +231,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { None => return false, }; - let is_private = !self.cx.cache.effective_visibilities.is_directly_public(res_did); + let is_private = + !self.cx.cache.effective_visibilities.is_directly_public(self.cx.tcx, res_did); let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id); // Only inline if requested or if the item would otherwise be stripped. @@ -263,14 +249,14 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let prev = mem::replace(&mut self.inlining, true); for &i in m.item_ids { let i = self.cx.tcx.hir().item(i); - self.visit_item(i, None, om); + self.visit_item(i, None, om, Some(id)); } self.inlining = prev; true } Node::Item(it) if !glob => { let prev = mem::replace(&mut self.inlining, true); - self.visit_item(it, renamed, om); + self.visit_item(it, renamed, om, Some(id)); self.inlining = prev; true } @@ -291,6 +277,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { item: &'tcx hir::Item<'_>, renamed: Option<Symbol>, om: &mut Module<'tcx>, + parent_id: Option<hir::HirId>, ) { debug!("visiting item {:?}", item); let name = renamed.unwrap_or(item.ident.name); @@ -314,39 +301,40 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { hir::ItemKind::GlobalAsm(..) => {} hir::ItemKind::Use(_, hir::UseKind::ListStem) => {} hir::ItemKind::Use(path, kind) => { - let is_glob = kind == hir::UseKind::Glob; - - // Struct and variant constructors and proc macro stubs always show up alongside - // their definitions, we've already processed them so just discard these. - if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = path.res { - return; - } - - let attrs = self.cx.tcx.hir().attrs(item.hir_id()); + for &res in &path.res { + // Struct and variant constructors and proc macro stubs always show up alongside + // their definitions, we've already processed them so just discard these. + if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = res { + continue; + } - // If there was a private module in the current path then don't bother inlining - // anything as it will probably be stripped anyway. - if is_pub && self.inside_public_path { - let please_inline = attrs.iter().any(|item| match item.meta_item_list() { - Some(ref list) if item.has_name(sym::doc) => { - list.iter().any(|i| i.has_name(sym::inline)) + let attrs = self.cx.tcx.hir().attrs(item.hir_id()); + + // If there was a private module in the current path then don't bother inlining + // anything as it will probably be stripped anyway. + if is_pub && self.inside_public_path { + let please_inline = attrs.iter().any(|item| match item.meta_item_list() { + Some(ref list) if item.has_name(sym::doc) => { + list.iter().any(|i| i.has_name(sym::inline)) + } + _ => false, + }); + let is_glob = kind == hir::UseKind::Glob; + let ident = if is_glob { None } else { Some(name) }; + if self.maybe_inline_local( + item.hir_id(), + res, + ident, + is_glob, + om, + please_inline, + ) { + continue; } - _ => false, - }); - let ident = if is_glob { None } else { Some(name) }; - if self.maybe_inline_local( - item.hir_id(), - path.res, - ident, - is_glob, - om, - please_inline, - ) { - return; } - } - om.items.push((item, renamed)) + om.items.push((item, renamed, parent_id)) + } } hir::ItemKind::Macro(ref macro_def, _) => { // `#[macro_export] macro_rules!` items are handled separately in `visit()`, @@ -365,11 +353,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let nonexported = !self.cx.tcx.has_attr(def_id, sym::macro_export); if is_macro_2_0 || nonexported || self.inlining { - om.items.push((item, renamed)); + om.items.push((item, renamed, None)); } } hir::ItemKind::Mod(ref m) => { - om.mods.push(self.visit_mod_contents(item.hir_id(), m, name)); + om.mods.push(self.visit_mod_contents(item.hir_id(), m, name, parent_id)); } hir::ItemKind::Fn(..) | hir::ItemKind::ExternCrate(..) @@ -380,19 +368,19 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { | hir::ItemKind::OpaqueTy(..) | hir::ItemKind::Static(..) | hir::ItemKind::Trait(..) - | hir::ItemKind::TraitAlias(..) => om.items.push((item, renamed)), + | hir::ItemKind::TraitAlias(..) => om.items.push((item, renamed, parent_id)), hir::ItemKind::Const(..) => { // Underscore constants do not correspond to a nameable item and // so are never useful in documentation. if name != kw::Underscore { - om.items.push((item, renamed)); + om.items.push((item, renamed, parent_id)); } } hir::ItemKind::Impl(impl_) => { // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick // them up regardless of where they're located. if !self.inlining && impl_.of_trait.is_none() { - om.items.push((item, None)); + om.items.push((item, None, None)); } } } diff --git a/src/librustdoc/visit_lib.rs b/src/librustdoc/visit_lib.rs index 70214e2ad..e490559b0 100644 --- a/src/librustdoc/visit_lib.rs +++ b/src/librustdoc/visit_lib.rs @@ -1,86 +1,74 @@ +use crate::core::DocContext; use rustc_data_structures::fx::FxHashSet; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{CrateNum, DefId, CRATE_DEF_ID}; -use rustc_middle::middle::privacy::{EffectiveVisibilities, Level}; -use rustc_middle::ty::{TyCtxt, Visibility}; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::DefId; +use rustc_middle::ty::TyCtxt; // FIXME: this may not be exhaustive, but is sufficient for rustdocs current uses +#[derive(Default)] +pub(crate) struct RustdocEffectiveVisibilities { + extern_public: FxHashSet<DefId>, +} + +macro_rules! define_method { + ($method:ident) => { + pub(crate) fn $method(&self, tcx: TyCtxt<'_>, def_id: DefId) -> bool { + match def_id.as_local() { + Some(def_id) => tcx.effective_visibilities(()).$method(def_id), + None => self.extern_public.contains(&def_id), + } + } + }; +} + +impl RustdocEffectiveVisibilities { + define_method!(is_directly_public); + define_method!(is_exported); + define_method!(is_reachable); +} + +pub(crate) fn lib_embargo_visit_item(cx: &mut DocContext<'_>, def_id: DefId) { + assert!(!def_id.is_local()); + LibEmbargoVisitor { + tcx: cx.tcx, + extern_public: &mut cx.cache.effective_visibilities.extern_public, + visited_mods: Default::default(), + } + .visit_item(def_id) +} + /// Similar to `librustc_privacy::EmbargoVisitor`, but also takes /// specific rustdoc annotations into account (i.e., `doc(hidden)`) -pub(crate) struct LibEmbargoVisitor<'a, 'tcx> { +struct LibEmbargoVisitor<'a, 'tcx> { tcx: TyCtxt<'tcx>, // Effective visibilities for reachable nodes - effective_visibilities: &'a mut EffectiveVisibilities<DefId>, - // Previous level, None means unreachable - prev_level: Option<Level>, + extern_public: &'a mut FxHashSet<DefId>, // Keeps track of already visited modules, in case a module re-exports its parent visited_mods: FxHashSet<DefId>, } -impl<'a, 'tcx> LibEmbargoVisitor<'a, 'tcx> { - pub(crate) fn new(cx: &'a mut crate::core::DocContext<'tcx>) -> LibEmbargoVisitor<'a, 'tcx> { - LibEmbargoVisitor { - tcx: cx.tcx, - effective_visibilities: &mut cx.cache.effective_visibilities, - prev_level: Some(Level::Direct), - visited_mods: FxHashSet::default(), - } - } - - pub(crate) fn visit_lib(&mut self, cnum: CrateNum) { - let did = cnum.as_def_id(); - self.update(did, Some(Level::Direct)); - self.visit_mod(did); - } - - // Updates node level and returns the updated level - fn update(&mut self, did: DefId, level: Option<Level>) -> Option<Level> { - let is_hidden = self.tcx.is_doc_hidden(did); - - let old_level = self.effective_visibilities.public_at_level(did); - // Visibility levels can only grow - if level > old_level && !is_hidden { - self.effective_visibilities.set_public_at_level( - did, - || Visibility::Restricted(CRATE_DEF_ID), - level.unwrap(), - ); - level - } else { - old_level - } - } - - pub(crate) fn visit_mod(&mut self, def_id: DefId) { +impl LibEmbargoVisitor<'_, '_> { + fn visit_mod(&mut self, def_id: DefId) { if !self.visited_mods.insert(def_id) { return; } for item in self.tcx.module_children(def_id).iter() { if let Some(def_id) = item.res.opt_def_id() { - if self.tcx.def_key(def_id).parent.map_or(false, |d| d == def_id.index) - || item.vis.is_public() - { - self.visit_item(item.res); + if item.vis.is_public() { + self.visit_item(def_id); } } } } - fn visit_item(&mut self, res: Res<!>) { - let def_id = res.def_id(); - let vis = self.tcx.visibility(def_id); - let inherited_item_level = if vis.is_public() { self.prev_level } else { None }; - - let item_level = self.update(def_id, inherited_item_level); - - if let Res::Def(DefKind::Mod, _) = res { - let orig_level = self.prev_level; - - self.prev_level = item_level; - self.visit_mod(def_id); - self.prev_level = orig_level; + fn visit_item(&mut self, def_id: DefId) { + if !self.tcx.is_doc_hidden(def_id) { + self.extern_public.insert(def_id); + if self.tcx.def_kind(def_id) == DefKind::Mod { + self.visit_mod(def_id); + } } } } |