diff options
Diffstat (limited to 'src/librustdoc')
46 files changed, 1619 insertions, 560 deletions
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index f9d16669771..ba701f42c66 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -136,7 +136,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let f = auto_trait::AutoTraitFinder::new(tcx); debug!("get_auto_trait_impls({:?})", ty); - let auto_traits: Vec<_> = self.cx.auto_traits.iter().cloned().collect(); + let auto_traits: Vec<_> = self.cx.auto_traits.iter().copied().collect(); let mut auto_traits: Vec<Item> = auto_traits .into_iter() .filter_map(|trait_def_id| { @@ -193,8 +193,8 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // to its smaller and larger regions. Note that 'larger' regions correspond // to sub-regions in Rust code (e.g., in 'a: 'b, 'a is the larger region). for constraint in regions.constraints.keys() { - match constraint { - &Constraint::VarSubVar(r1, r2) => { + match *constraint { + Constraint::VarSubVar(r1, r2) => { { let deps1 = vid_map.entry(RegionTarget::RegionVid(r1)).or_default(); deps1.larger.insert(RegionTarget::RegionVid(r2)); @@ -203,15 +203,15 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let deps2 = vid_map.entry(RegionTarget::RegionVid(r2)).or_default(); deps2.smaller.insert(RegionTarget::RegionVid(r1)); } - &Constraint::RegSubVar(region, vid) => { + Constraint::RegSubVar(region, vid) => { let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default(); deps.smaller.insert(RegionTarget::Region(region)); } - &Constraint::VarSubReg(vid, region) => { + Constraint::VarSubReg(vid, region) => { let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default(); deps.larger.insert(RegionTarget::Region(region)); } - &Constraint::RegSubReg(r1, r2) => { + Constraint::RegSubReg(r1, r2) => { // The constraint is already in the form that we want, so we're done with it // Desired order is 'larger, smaller', so flip then if region_name(r1) != region_name(r2) { @@ -513,8 +513,8 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // as we want to combine them with any 'Output' qpaths // later - let is_fn = match &mut b { - &mut GenericBound::TraitBound(ref mut p, _) => { + let is_fn = match b { + GenericBound::TraitBound(ref mut p, _) => { // Insert regions into the for_generics hash map first, to ensure // that we don't end up with duplicate bounds (e.g., for<'b, 'b>) for_generics.extend(p.generic_params.clone()); @@ -576,7 +576,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { rhs, }); continue; // If something other than a Fn ends up - // with parenthesis, leave it alone + // with parentheses, leave it alone } } @@ -699,8 +699,8 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } fn region_name(region: Region<'_>) -> Option<Symbol> { - match region { - &ty::ReEarlyBound(r) => Some(r.name), + match *region { + ty::ReEarlyBound(r) => Some(r.name), _ => None, } } @@ -717,8 +717,8 @@ impl<'a, 'tcx> TypeFolder<'tcx> for RegionReplacer<'a, 'tcx> { } fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { - (match r { - &ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(), + (match *r { + ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(), _ => None, }) .unwrap_or_else(|| r.super_fold_with(self)) diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index e11b802a09a..c5e05875819 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -14,9 +14,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Symbol}; -use crate::clean::{ - self, utils, Attributes, AttributesExt, GetDefId, ItemId, NestedAttributesExt, Type, -}; +use crate::clean::{self, utils, Attributes, AttributesExt, ItemId, NestedAttributesExt, Type}; use crate::core::DocContext; use crate::formats::item_type::ItemType; @@ -325,7 +323,7 @@ fn merge_attrs( } } -/// Builds a specific implementation of a type. The `did` could be a type method or trait method. +/// Inline an `impl`, inherent or of a trait. The `did` must be for an `impl`. crate fn build_impl( cx: &mut DocContext<'_>, parent_module: impl Into<Option<DefId>>, @@ -376,7 +374,7 @@ crate fn build_impl( // Only inline impl if the implementing type is // reachable in rustdoc generated documentation if !did.is_local() { - if let Some(did) = for_.def_id() { + if let Some(did) = for_.def_id(&cx.cache) { if !cx.cache.access_levels.is_public(did) { return; } @@ -464,7 +462,7 @@ crate fn build_impl( } while let Some(ty) = stack.pop() { - if let Some(did) = ty.def_id() { + if let Some(did) = ty.def_id(&cx.cache) { if tcx.get_attrs(did).lists(sym::doc).has_word(sym::hidden) { return; } @@ -481,7 +479,11 @@ crate fn build_impl( let (merged_attrs, cfg) = merge_attrs(cx, parent_module.into(), load_attrs(cx, did), attrs); trace!("merged_attrs={:?}", merged_attrs); - trace!("build_impl: impl {:?} for {:?}", trait_.as_ref().map(|t| t.def_id()), for_.def_id()); + trace!( + "build_impl: impl {:?} for {:?}", + trait_.as_ref().map(|t| t.def_id()), + for_.def_id(&cx.cache) + ); ret.push(clean::Item::from_def_id_and_attrs_and_parts( did, None, diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 9d102d68783..9ea3112f178 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -216,17 +216,15 @@ impl<'tcx> Clean<GenericBound> for ty::PolyTraitRef<'tcx> { impl Clean<Lifetime> for hir::Lifetime { fn clean(&self, cx: &mut DocContext<'_>) -> Lifetime { let def = cx.tcx.named_region(self.hir_id); - match def { - Some( - rl::Region::EarlyBound(_, node_id, _) - | rl::Region::LateBound(_, _, node_id, _) - | rl::Region::Free(_, node_id), - ) => { - if let Some(lt) = cx.lt_substs.get(&node_id).cloned() { - return lt; - } + if let Some( + rl::Region::EarlyBound(_, node_id, _) + | rl::Region::LateBound(_, _, node_id, _) + | rl::Region::Free(_, node_id), + ) = def + { + if let Some(lt) = cx.lt_substs.get(&node_id).cloned() { + return lt; } - _ => {} } Lifetime(self.name.ident().name) } @@ -385,7 +383,7 @@ impl<'tcx> Clean<Type> for ty::ProjectionTy<'tcx> { let self_type = self.self_ty().clean(cx); Type::QPath { name: cx.tcx.associated_item(self.item_def_id).ident.name, - self_def_id: self_type.def_id(), + self_def_id: self_type.def_id(&cx.cache), self_type: box self_type, trait_, } @@ -421,7 +419,7 @@ impl Clean<GenericParamDef> for ty::GenericParamDef { GenericParamDefKind::Type { did: self.def_id, bounds: vec![], // These are filled in from the where-clauses. - default, + default: default.map(Box::new), synthetic, }, ) @@ -430,9 +428,9 @@ impl Clean<GenericParamDef> for ty::GenericParamDef { self.name, GenericParamDefKind::Const { did: self.def_id, - ty: cx.tcx.type_of(self.def_id).clean(cx), + ty: Box::new(cx.tcx.type_of(self.def_id).clean(cx)), default: match has_default { - true => Some(cx.tcx.const_param_default(self.def_id).to_string()), + true => Some(Box::new(cx.tcx.const_param_default(self.def_id).to_string())), false => None, }, }, @@ -462,7 +460,7 @@ impl Clean<GenericParamDef> for hir::GenericParam<'_> { GenericParamDefKind::Type { did: cx.tcx.hir().local_def_id(self.hir_id).to_def_id(), bounds: self.bounds.clean(cx), - default: default.clean(cx), + default: default.clean(cx).map(Box::new), synthetic, }, ), @@ -470,10 +468,10 @@ impl Clean<GenericParamDef> for hir::GenericParam<'_> { self.name.ident().name, GenericParamDefKind::Const { did: cx.tcx.hir().local_def_id(self.hir_id).to_def_id(), - ty: ty.clean(cx), + ty: Box::new(ty.clean(cx)), default: default.map(|ct| { let def_id = cx.tcx.hir().local_def_id(ct.hir_id); - ty::Const::from_anon_const(cx.tcx, def_id).to_string() + Box::new(ty::Const::from_anon_const(cx.tcx, def_id).to_string()) }), }, ), @@ -828,7 +826,7 @@ impl<'a> Clean<Arguments> for (&'a [hir::Ty<'a>], hir::BodyId) { .iter() .enumerate() .map(|(i, ty)| Argument { - name: name_from_pat(&body.params[i].pat), + name: name_from_pat(body.params[i].pat), type_: ty.clean(cx), }) .collect(), @@ -924,7 +922,7 @@ impl Clean<Item> for hir::TraitItem<'_> { } MethodItem(m, None) } - hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(ref names)) => { + hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(names)) => { let (generics, decl) = enter_impl_trait(cx, |cx| { (self.generics.clean(cx), (&*sig.decl, &names[..]).clean(cx)) }); @@ -936,7 +934,7 @@ impl Clean<Item> for hir::TraitItem<'_> { } TyMethodItem(t) } - hir::TraitItemKind::Type(ref bounds, ref default) => { + hir::TraitItemKind::Type(bounds, ref default) => { AssocTypeItem(bounds.clean(cx), default.clean(cx)) } }; @@ -1260,7 +1258,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type { let path = path.clean(cx); resolve_type(cx, path) } - hir::QPath::Resolved(Some(ref qself), ref p) => { + hir::QPath::Resolved(Some(ref qself), p) => { // Try to normalize `<X as Y>::T` to a type let ty = hir_ty_to_ty(cx.tcx, hir_ty); if let Some(normalized_value) = normalize(cx, ty) { @@ -1281,7 +1279,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type { trait_, } } - hir::QPath::TypeRelative(ref qself, ref segment) => { + hir::QPath::TypeRelative(ref qself, segment) => { let ty = hir_ty_to_ty(cx.tcx, hir_ty); let res = match ty.kind() { ty::Projection(proj) => Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id), @@ -1337,7 +1335,7 @@ impl Clean<Type> for hir::Ty<'_> { let length = print_const(cx, ct.eval(cx.tcx, param_env)); Array(box ty.clean(cx), length) } - TyKind::Tup(ref tys) => Tuple(tys.clean(cx)), + TyKind::Tup(tys) => Tuple(tys.clean(cx)), TyKind::OpaqueDef(item_id, _) => { let item = cx.tcx.hir().item(item_id); if let hir::ItemKind::OpaqueTy(ref ty) = item.kind { @@ -1346,8 +1344,8 @@ impl Clean<Type> for hir::Ty<'_> { unreachable!() } } - TyKind::Path(_) => clean_qpath(&self, cx), - TyKind::TraitObject(ref bounds, ref lifetime, _) => { + TyKind::Path(_) => clean_qpath(self, cx), + TyKind::TraitObject(bounds, ref lifetime, _) => { let bounds = bounds.iter().map(|bound| bound.clean(cx)).collect(); let lifetime = if !lifetime.is_elided() { Some(lifetime.clean(cx)) } else { None }; DynTrait(bounds, lifetime) @@ -1441,7 +1439,7 @@ impl<'tcx> Clean<Type> for Ty<'tcx> { let path = external_path(cx, did, false, vec![], InternalSubsts::empty()); ResolvedPath { path, did } } - ty::Dynamic(ref obj, ref reg) => { + ty::Dynamic(obj, ref reg) => { // HACK: pick the first `did` as the `did` of the trait object. Someone // might want to implement "native" support for marker-trait-only // trait objects. @@ -1481,9 +1479,7 @@ impl<'tcx> Clean<Type> for Ty<'tcx> { DynTrait(bounds, lifetime) } - ty::Tuple(ref t) => { - Tuple(t.iter().map(|t| t.expect_ty()).collect::<Vec<_>>().clean(cx)) - } + ty::Tuple(t) => Tuple(t.iter().map(|t| t.expect_ty()).collect::<Vec<_>>().clean(cx)), ty::Projection(ref data) => data.clean(cx), @@ -1821,9 +1817,9 @@ impl Clean<Vec<Item>> for (&hir::Item<'_>, Option<Symbol>) { clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx) } ItemKind::Macro(ref macro_def) => MacroItem(Macro { - source: display_macro_source(cx, name, ¯o_def, def_id, &item.vis), + source: display_macro_source(cx, name, macro_def, def_id, &item.vis), }), - ItemKind::Trait(is_auto, unsafety, ref generics, ref bounds, ref item_ids) => { + ItemKind::Trait(is_auto, unsafety, ref generics, bounds, item_ids) => { let items = item_ids .iter() .map(|ti| cx.tcx.hir().trait_item(ti.id).clean(cx)) @@ -1887,7 +1883,7 @@ fn clean_impl(impl_: &hir::Impl<'_>, hir_id: hir::HirId, cx: &mut DocContext<'_> } let for_ = impl_.self_ty.clean(cx); - let type_alias = for_.def_id().and_then(|did| match tcx.def_kind(did) { + let type_alias = for_.def_id(&cx.cache).and_then(|did| match tcx.def_kind(did) { DefKind::TyAlias => Some(tcx.type_of(did).clean(cx)), _ => None, }); @@ -2062,12 +2058,13 @@ fn clean_use_statement( impl Clean<Item> for (&hir::ForeignItem<'_>, Option<Symbol>) { fn clean(&self, cx: &mut DocContext<'_>) -> Item { let (item, renamed) = self; - cx.with_param_env(item.def_id.to_def_id(), |cx| { + let def_id = item.def_id.to_def_id(); + cx.with_param_env(def_id, |cx| { let kind = match item.kind { - hir::ForeignItemKind::Fn(ref decl, ref names, ref generics) => { + hir::ForeignItemKind::Fn(decl, names, ref generics) => { let abi = cx.tcx.hir().get_foreign_abi(item.hir_id()); let (generics, decl) = enter_impl_trait(cx, |cx| { - (generics.clean(cx), (&**decl, &names[..]).clean(cx)) + (generics.clean(cx), (&*decl, &names[..]).clean(cx)) }); ForeignFunctionItem(Function { decl, @@ -2112,7 +2109,7 @@ impl Clean<TypeBindingKind> for hir::TypeBindingKind<'_> { hir::TypeBindingKind::Equality { ref ty } => { TypeBindingKind::Equality { ty: ty.clean(cx) } } - hir::TypeBindingKind::Constraint { ref bounds } => { + hir::TypeBindingKind::Constraint { bounds } => { TypeBindingKind::Constraint { bounds: bounds.iter().map(|b| b.clean(cx)).collect() } } } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index d4cea8b4a9d..6ae057abb3d 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -204,7 +204,7 @@ impl ExternalCrate { .filter_map(|a| a.value_str()) .map(to_remote) .next() - .or(extern_url.map(to_remote)) // NOTE: only matters if `extern_url_takes_precedence` is false + .or_else(|| extern_url.map(to_remote)) // NOTE: only matters if `extern_url_takes_precedence` is false .unwrap_or(Unknown) // Well, at least we tried. } @@ -238,7 +238,7 @@ impl ExternalCrate { hir::ItemKind::Mod(_) => { as_keyword(Res::Def(DefKind::Mod, id.def_id.to_def_id())) } - hir::ItemKind::Use(ref path, hir::UseKind::Single) + hir::ItemKind::Use(path, hir::UseKind::Single) if item.vis.node.is_pub() => { as_keyword(path.res.expect_non_local()) @@ -304,7 +304,7 @@ impl ExternalCrate { hir::ItemKind::Mod(_) => { as_primitive(Res::Def(DefKind::Mod, id.def_id.to_def_id())) } - hir::ItemKind::Use(ref path, hir::UseKind::Single) + hir::ItemKind::Use(path, hir::UseKind::Single) if item.vis.node.is_pub() => { as_primitive(path.res.expect_non_local()).map(|(_, prim)| { @@ -381,7 +381,7 @@ impl Item { { *span } else { - self.def_id.as_def_id().map(|did| rustc_span(did, tcx)).unwrap_or_else(|| Span::dummy()) + self.def_id.as_def_id().map(|did| rustc_span(did, tcx)).unwrap_or_else(Span::dummy) } } @@ -562,7 +562,7 @@ impl Item { } crate fn stability_class(&self, tcx: TyCtxt<'_>) -> Option<String> { - self.stability(tcx).as_ref().and_then(|ref s| { + self.stability(tcx).as_ref().and_then(|s| { let mut classes = Vec::with_capacity(2); if s.level.is_unstable() { @@ -820,9 +820,9 @@ impl AttributesExt for [ast::Attribute] { // #[doc(cfg(...))] if let Some(cfg_mi) = item .meta_item() - .and_then(|item| rustc_expand::config::parse_cfg(&item, sess)) + .and_then(|item| rustc_expand::config::parse_cfg(item, sess)) { - match Cfg::parse(&cfg_mi) { + match Cfg::parse(cfg_mi) { Ok(new_cfg) => cfg &= new_cfg, Err(e) => sess.span_err(e.span, e.msg), } @@ -934,7 +934,7 @@ impl<'a> FromIterator<&'a DocFragment> for String { T: IntoIterator<Item = &'a DocFragment>, { iter.into_iter().fold(String::new(), |mut acc, frag| { - add_doc_fragment(&mut acc, &frag); + add_doc_fragment(&mut acc, frag); acc }) } @@ -1061,12 +1061,12 @@ impl Attributes { let ori = iter.next()?; let mut out = String::new(); - add_doc_fragment(&mut out, &ori); - while let Some(new_frag) = iter.next() { + add_doc_fragment(&mut out, ori); + for new_frag in iter { if new_frag.kind != ori.kind || new_frag.parent_module != ori.parent_module { break; } - add_doc_fragment(&mut out, &new_frag); + add_doc_fragment(&mut out, new_frag); } if out.is_empty() { None } else { Some(out) } } @@ -1079,7 +1079,7 @@ impl Attributes { for new_frag in self.doc_strings.iter() { let out = ret.entry(new_frag.parent_module).or_default(); - add_doc_fragment(out, &new_frag); + add_doc_fragment(out, new_frag); } ret } @@ -1219,13 +1219,13 @@ crate enum GenericParamDefKind { Type { did: DefId, bounds: Vec<GenericBound>, - default: Option<Type>, + default: Option<Box<Type>>, synthetic: Option<hir::SyntheticTyParamKind>, }, Const { did: DefId, - ty: Type, - default: Option<String>, + ty: Box<Type>, + default: Option<Box<String>>, }, } @@ -1239,8 +1239,8 @@ impl GenericParamDefKind { // any embedded types, but `get_type` seems to be the wrong name for that. crate fn get_type(&self) -> Option<Type> { match self { - GenericParamDefKind::Type { default, .. } => default.clone(), - GenericParamDefKind::Const { ty, .. } => Some(ty.clone()), + GenericParamDefKind::Type { default, .. } => default.as_deref().cloned(), + GenericParamDefKind::Const { ty, .. } => Some((&**ty).clone()), GenericParamDefKind::Lifetime { .. } => None, } } @@ -1252,6 +1252,10 @@ crate struct GenericParamDef { crate kind: GenericParamDefKind, } +// `GenericParamDef` is used in many places. Make sure it doesn't unintentionally get bigger. +#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +rustc_data_structures::static_assert_size!(GenericParamDef, 56); + impl GenericParamDef { crate fn is_synthetic_type_param(&self) -> bool { match self.kind { @@ -1366,17 +1370,10 @@ crate enum FnRetTy { DefaultReturn, } -impl GetDefId for FnRetTy { - fn def_id(&self) -> Option<DefId> { - match *self { - Return(ref ty) => ty.def_id(), - DefaultReturn => None, - } - } - - fn def_id_full(&self, cache: &Cache) -> Option<DefId> { - match *self { - Return(ref ty) => ty.def_id_full(cache), +impl FnRetTy { + crate fn as_return(&self) -> Option<&Type> { + match self { + Return(ret) => Some(ret), DefaultReturn => None, } } @@ -1450,33 +1447,9 @@ crate enum Type { ImplTrait(Vec<GenericBound>), } -crate trait GetDefId { - /// Use this method to get the [`DefId`] of a [`clean`] AST node. - /// This will return [`None`] when called on a primitive [`clean::Type`]. - /// Use [`Self::def_id_full`] if you want to include primitives. - /// - /// [`clean`]: crate::clean - /// [`clean::Type`]: crate::clean::Type - // FIXME: get rid of this function and always use `def_id_full` - fn def_id(&self) -> Option<DefId>; - - /// Use this method to get the [DefId] of a [clean] AST node, including [PrimitiveType]s. - /// - /// See [`Self::def_id`] for more. - /// - /// [clean]: crate::clean - fn def_id_full(&self, cache: &Cache) -> Option<DefId>; -} - -impl<T: GetDefId> GetDefId for Option<T> { - fn def_id(&self) -> Option<DefId> { - self.as_ref().and_then(|d| d.def_id()) - } - - fn def_id_full(&self, cache: &Cache) -> Option<DefId> { - self.as_ref().and_then(|d| d.def_id_full(cache)) - } -} +// `Type` is used a lot. Make sure it doesn't unintentionally get bigger. +#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +rustc_data_structures::static_assert_size!(Type, 72); impl Type { crate fn primitive_type(&self) -> Option<PrimitiveType> { @@ -1556,17 +1529,27 @@ impl Type { QPath { ref self_type, .. } => return self_type.inner_def_id(cache), Generic(_) | Infer | ImplTrait(_) => return None, }; - cache.and_then(|c| Primitive(t).def_id_full(c)) + cache.and_then(|c| Primitive(t).def_id(c)) } -} -impl GetDefId for Type { - fn def_id(&self) -> Option<DefId> { - self.inner_def_id(None) + /// Use this method to get the [DefId] of a [clean] AST node, including [PrimitiveType]s. + /// + /// See [`Self::def_id_no_primitives`] for more. + /// + /// [clean]: crate::clean + crate fn def_id(&self, cache: &Cache) -> Option<DefId> { + self.inner_def_id(Some(cache)) } - fn def_id_full(&self, cache: &Cache) -> Option<DefId> { - self.inner_def_id(Some(cache)) + /// Use this method to get the [`DefId`] of a [`clean`] AST node. + /// This will return [`None`] when called on a primitive [`clean::Type`]. + /// Use [`Self::def_id`] if you want to include primitives. + /// + /// [`clean`]: crate::clean + /// [`clean::Type`]: crate::clean::Type + // FIXME: get rid of this function and always use `def_id` + crate fn def_id_no_primitives(&self) -> Option<DefId> { + self.inner_def_id(None) } } @@ -2084,16 +2067,6 @@ crate struct Typedef { crate item_type: Option<Type>, } -impl GetDefId for Typedef { - fn def_id(&self) -> Option<DefId> { - self.type_.def_id() - } - - fn def_id_full(&self, cache: &Cache) -> Option<DefId> { - self.type_.def_id_full(cache) - } -} - #[derive(Clone, Debug)] crate struct OpaqueTy { crate bounds: Vec<GenericBound>, diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index de43daff6f0..0573a1ada3a 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -171,8 +171,8 @@ crate fn strip_path_generics(path: Path) -> Path { crate fn qpath_to_string(p: &hir::QPath<'_>) -> String { let segments = match *p { - hir::QPath::Resolved(_, ref path) => &path.segments, - hir::QPath::TypeRelative(_, ref segment) => return segment.ident.to_string(), + hir::QPath::Resolved(_, path) => &path.segments, + hir::QPath::TypeRelative(_, segment) => return segment.ident.to_string(), hir::QPath::LangItem(lang_item, ..) => return lang_item.name().to_string(), }; @@ -217,15 +217,15 @@ crate fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { PatKind::Wild | PatKind::Struct(..) => return kw::Underscore, PatKind::Binding(_, _, ident, _) => return ident.name, PatKind::TupleStruct(ref p, ..) | PatKind::Path(ref p) => qpath_to_string(p), - PatKind::Or(ref pats) => { + PatKind::Or(pats) => { pats.iter().map(|p| name_from_pat(p).to_string()).collect::<Vec<String>>().join(" | ") } - PatKind::Tuple(ref elts, _) => format!( + PatKind::Tuple(elts, _) => format!( "({})", elts.iter().map(|p| name_from_pat(p).to_string()).collect::<Vec<String>>().join(", ") ), - PatKind::Box(ref p) => return name_from_pat(&**p), - PatKind::Ref(ref p, _) => return name_from_pat(&**p), + PatKind::Box(p) => return name_from_pat(&*p), + PatKind::Ref(p, _) => return name_from_pat(&*p), PatKind::Lit(..) => { warn!( "tried to get argument name from PatKind::Lit, which is silly in function arguments" @@ -233,7 +233,7 @@ crate fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { return Symbol::intern("()"); } PatKind::Range(..) => return kw::Underscore, - PatKind::Slice(ref begin, ref mid, ref end) => { + PatKind::Slice(begin, ref mid, end) => { let begin = begin.iter().map(|p| name_from_pat(p).to_string()); let mid = mid.as_ref().map(|p| format!("..{}", name_from_pat(&**p))).into_iter(); let end = end.iter().map(|p| name_from_pat(p).to_string()); @@ -507,7 +507,7 @@ crate fn has_doc_flag(attrs: ty::Attributes<'_>, flag: Symbol) -> bool { /// so that the channel is consistent. /// /// Set by `bootstrap::Builder::doc_rust_lang_org_channel` in order to keep tests passing on beta/stable. -crate const DOC_RUST_LANG_ORG_CHANNEL: &'static str = env!("DOC_RUST_LANG_ORG_CHANNEL"); +crate const DOC_RUST_LANG_ORG_CHANNEL: &str = env!("DOC_RUST_LANG_ORG_CHANNEL"); /// Render a sequence of macro arms in a format suitable for displaying to the user /// as part of an item declaration. diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index ac440a39515..493aa56fce6 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -25,6 +25,7 @@ use crate::html::render::StylePath; use crate::html::static_files; use crate::opts; use crate::passes::{self, Condition, DefaultPassOption}; +use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions}; use crate::theme; #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -158,6 +159,10 @@ crate struct Options { crate json_unused_externs: bool, /// Whether to skip capturing stdout and stderr of tests. crate nocapture: bool, + + /// Configuration for scraping examples from the current crate. If this option is Some(..) then + /// the compiler will scrape examples and not generate documentation. + crate scrape_examples_options: Option<ScrapeExamplesOptions>, } impl fmt::Debug for Options { @@ -202,6 +207,7 @@ impl fmt::Debug for Options { .field("run_check", &self.run_check) .field("no_run", &self.no_run) .field("nocapture", &self.nocapture) + .field("scrape_examples_options", &self.scrape_examples_options) .finish() } } @@ -280,6 +286,7 @@ crate struct RenderOptions { crate emit: Vec<EmitType>, /// If `true`, HTML source pages will generate links for items to their definition. crate generate_link_to_definition: bool, + crate call_locations: AllCallLocations, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -314,13 +321,13 @@ impl Options { /// been printed, returns `Err` with the exit code. crate fn from_matches(matches: &getopts::Matches) -> Result<Options, i32> { // Check for unstable options. - nightly_options::check_nightly_options(&matches, &opts()); + nightly_options::check_nightly_options(matches, &opts()); if matches.opt_present("h") || matches.opt_present("help") { 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); } @@ -356,10 +363,10 @@ impl Options { return Err(0); } - let color = config::parse_color(&matches); + let color = config::parse_color(matches); let config::JsonConfig { json_rendered, json_unused_externs, .. } = - config::parse_json(&matches); - let error_format = config::parse_error_format(&matches, color, json_rendered); + config::parse_json(matches); + let error_format = config::parse_error_format(matches, color, json_rendered); let codegen_options = CodegenOptions::build(matches, error_format); let debugging_opts = DebuggingOptions::build(matches, error_format); @@ -367,7 +374,7 @@ impl Options { let diag = new_handler(error_format, None, &debugging_opts); // check for deprecated options - check_deprecated_options(&matches, &diag); + check_deprecated_options(matches, &diag); let mut emit = Vec::new(); for list in matches.opt_strs("emit") { @@ -433,8 +440,8 @@ impl Options { .iter() .map(|s| SearchPath::from_cli_opt(s, error_format)) .collect(); - let externs = parse_externs(&matches, &debugging_opts, error_format); - let extern_html_root_urls = match parse_extern_html_roots(&matches) { + let externs = parse_externs(matches, &debugging_opts, error_format); + let extern_html_root_urls = match parse_extern_html_roots(matches) { Ok(ex) => ex, Err(err) => { diag.struct_err(err).emit(); @@ -553,7 +560,7 @@ impl Options { } } - let edition = config::parse_crate_edition(&matches); + let edition = config::parse_crate_edition(matches); let mut id_map = html::markdown::IdMap::new(); let external_html = match ExternalHtml::load( @@ -562,7 +569,7 @@ impl Options { &matches.opt_strs("html-after-content"), &matches.opt_strs("markdown-before-content"), &matches.opt_strs("markdown-after-content"), - nightly_options::match_is_nightly_build(&matches), + nightly_options::match_is_nightly_build(matches), &diag, &mut id_map, edition, @@ -671,6 +678,10 @@ impl Options { return Err(1); } + let scrape_examples_options = ScrapeExamplesOptions::new(&matches, &diag)?; + let with_examples = matches.opt_strs("with-examples"); + let call_locations = crate::scrape_examples::load_call_locations(with_examples, &diag)?; + let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format); Ok(Options { @@ -737,10 +748,12 @@ impl Options { ), emit, generate_link_to_definition, + call_locations, }, crate_name, output_format, json_unused_externs, + scrape_examples_options, }) } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 074744b3d11..b7251e8f571 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -85,7 +85,7 @@ crate struct DocContext<'tcx> { impl<'tcx> DocContext<'tcx> { crate fn sess(&self) -> &'tcx Session { - &self.tcx.sess + self.tcx.sess } crate fn with_param_env<T, F: FnOnce(&mut Self) -> T>(&mut self, def_id: DefId, f: F) -> T { @@ -464,7 +464,7 @@ crate fn run_global_ctxt( _ => continue, }; for name in value.as_str().split_whitespace() { - let span = attr.name_value_literal_span().unwrap_or(attr.span()); + let span = attr.name_value_literal_span().unwrap_or_else(|| attr.span()); manual_passes.extend(parse_pass(name, Some(span))); } } diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 9e64d200b43..9b32ad979e3 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -73,7 +73,7 @@ crate fn run(options: Options) -> Result<(), ErrorReported> { search_paths: options.libs.clone(), crate_types, lint_opts: if !options.display_doctest_warnings { lint_opts } else { vec![] }, - lint_cap: Some(options.lint_cap.unwrap_or_else(|| lint::Forbid)), + lint_cap: Some(options.lint_cap.unwrap_or(lint::Forbid)), cg: options.codegen_options.clone(), externs: options.externs.clone(), unstable_features: options.render_options.unstable_features, @@ -176,7 +176,7 @@ crate fn run(options: Options) -> Result<(), ErrorReported> { .iter() .map(|uexts| uexts.unused_extern_names.iter().collect::<FxHashSet<&String>>()) .fold(extern_names, |uextsa, uextsb| { - uextsa.intersection(&uextsb).map(|v| *v).collect::<FxHashSet<&String>>() + uextsa.intersection(&uextsb).copied().collect::<FxHashSet<&String>>() }) .iter() .map(|v| (*v).clone()) @@ -423,7 +423,7 @@ fn run_test( // Add a \n to the end to properly terminate the last line, // but only if there was output to be printed - if out_lines.len() > 0 { + if !out_lines.is_empty() { out_lines.push(""); } @@ -1124,7 +1124,7 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> { let mut attrs = Attributes::from_ast(ast_attrs, None); if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default()) { - if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features_untracked())) { + if !cfg.matches(&self.sess.parse_sess, Some(self.sess.features_untracked())) { return; } } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 8b883ffaaf0..6b9c9a9669b 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -6,7 +6,7 @@ use rustc_middle::middle::privacy::AccessLevels; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::sym; -use crate::clean::{self, GetDefId, ItemId, PrimitiveType}; +use crate::clean::{self, ItemId, PrimitiveType}; use crate::config::RenderOptions; use crate::fold::DocFolder; use crate::formats::item_type::ItemType; @@ -206,7 +206,9 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { || i.trait_ .as_ref() .map_or(false, |t| self.cache.masked_crates.contains(&t.def_id().krate)) - || i.for_.def_id().map_or(false, |d| self.cache.masked_crates.contains(&d.krate)) + || i.for_ + .def_id(self.cache) + .map_or(false, |d| self.cache.masked_crates.contains(&d.krate)) { return None; } @@ -292,7 +294,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // inserted later on when serializing the search-index. if item.def_id.index().map_or(false, |idx| idx != CRATE_DEF_INDEX) { let desc = item.doc_value().map_or_else(String::new, |x| { - short_markdown_summary(&x.as_str(), &item.link_names(&self.cache)) + short_markdown_summary(x.as_str(), &item.link_names(self.cache)) }); self.cache.search_index.push(IndexItem { ty: item.type_(), @@ -454,7 +456,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { if let Some(generics) = i.trait_.as_ref().and_then(|t| t.generics()) { for bound in generics { - if let Some(did) = bound.def_id() { + if let Some(did) = bound.def_id(self.cache) { dids.insert(did); } } @@ -462,7 +464,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { let impl_item = Impl { impl_item: item }; if impl_item.trait_did().map_or(true, |d| self.cache.traits.contains_key(&d)) { for did in dids { - self.cache.impls.entry(did).or_insert(vec![]).push(impl_item.clone()); + self.cache.impls.entry(did).or_insert_with(Vec::new).push(impl_item.clone()); } } else { let trait_did = impl_item.trait_did().expect("no trait did"); diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index f2751947c7e..c51bda60b73 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -597,7 +597,7 @@ crate fn href_relative_parts<'a>(fqp: &'a [String], relative_to_fqp: &'a [String /// Used when rendering a `ResolvedPath` structure. This invokes the `path` /// rendering function with the necessary arguments for linking to a local path. -fn resolved_path<'a, 'cx: 'a>( +fn resolved_path<'cx>( w: &mut fmt::Formatter<'_>, did: DefId, path: &clean::Path, @@ -696,7 +696,7 @@ fn primitive_link( /// Helper to render type parameters fn tybounds<'a, 'tcx: 'a>( - bounds: &'a Vec<clean::PolyTrait>, + bounds: &'a [clean::PolyTrait], lt: &'a Option<clean::Lifetime>, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx> { @@ -886,7 +886,7 @@ fn fmt_type<'cx>( if bounds.len() > 1 || trait_lt.is_some() => { write!(f, "{}{}{}(", amp, lt, m)?; - fmt_type(&ty, f, use_absolute, cx)?; + fmt_type(ty, f, use_absolute, cx)?; write!(f, ")") } clean::Generic(..) => { @@ -896,11 +896,11 @@ fn fmt_type<'cx>( &format!("{}{}{}", amp, lt, m), cx, )?; - fmt_type(&ty, f, use_absolute, cx) + fmt_type(ty, f, use_absolute, cx) } _ => { write!(f, "{}{}{}", amp, lt, m)?; - fmt_type(&ty, f, use_absolute, cx) + fmt_type(ty, f, use_absolute, cx) } } } diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 8ed69962875..a33bb3479ce 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -12,6 +12,7 @@ use crate::html::render::Context; use std::collections::VecDeque; use std::fmt::{Display, Write}; +use rustc_data_structures::fx::FxHashMap; use rustc_lexer::{LiteralKind, TokenKind}; use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; @@ -30,6 +31,10 @@ crate struct ContextInfo<'a, 'b, 'c> { crate root_path: &'c str, } +/// Decorations are represented as a map from CSS class to vector of character ranges. +/// Each range will be wrapped in a span with that class. +crate struct DecorationInfo(crate FxHashMap<&'static str, Vec<(u32, u32)>>); + /// Highlights `src`, returning the HTML output. crate fn render_with_highlighting( src: &str, @@ -40,6 +45,7 @@ crate fn render_with_highlighting( edition: Edition, extra_content: Option<Buffer>, context_info: Option<ContextInfo<'_, '_, '_>>, + decoration_info: Option<DecorationInfo>, ) { debug!("highlighting: ================\n{}\n==============", src); if let Some((edition_info, class)) = tooltip { @@ -56,7 +62,7 @@ crate fn render_with_highlighting( } write_header(out, class, extra_content); - write_code(out, &src, edition, context_info); + write_code(out, src, edition, context_info, decoration_info); write_footer(out, playground_button); } @@ -89,17 +95,23 @@ fn write_code( src: &str, edition: Edition, context_info: Option<ContextInfo<'_, '_, '_>>, + decoration_info: Option<DecorationInfo>, ) { // This replace allows to fix how the code source with DOS backline characters is displayed. let src = src.replace("\r\n", "\n"); - Classifier::new(&src, edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP)) - .highlight(&mut |highlight| { - match highlight { - Highlight::Token { text, class } => string(out, Escape(text), class, &context_info), - Highlight::EnterSpan { class } => enter_span(out, class), - Highlight::ExitSpan => exit_span(out), - }; - }); + Classifier::new( + &src, + edition, + context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP), + decoration_info, + ) + .highlight(&mut |highlight| { + match highlight { + Highlight::Token { text, class } => string(out, Escape(text), class, &context_info), + Highlight::EnterSpan { class } => enter_span(out, class), + Highlight::ExitSpan => exit_span(out), + }; + }); } fn write_footer(out: &mut Buffer, playground_button: Option<&str>) { @@ -127,6 +139,7 @@ enum Class { PreludeTy, PreludeVal, QuestionMark, + Decoration(&'static str), } impl Class { @@ -150,6 +163,7 @@ impl Class { Class::PreludeTy => "prelude-ty", Class::PreludeVal => "prelude-val", Class::QuestionMark => "question-mark", + Class::Decoration(kind) => kind, } } @@ -248,6 +262,24 @@ impl Iterator for PeekIter<'a> { } } +/// Custom spans inserted into the source. Eg --scrape-examples uses this to highlight function calls +struct Decorations { + starts: Vec<(u32, &'static str)>, + ends: Vec<u32>, +} + +impl Decorations { + fn new(info: DecorationInfo) -> Self { + let (starts, ends) = info + .0 + .into_iter() + .map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi))) + .flatten() + .unzip(); + Decorations { starts, ends } + } +} + /// Processes program tokens, classifying strings of text by highlighting /// category (`Class`). struct Classifier<'a> { @@ -259,13 +291,20 @@ struct Classifier<'a> { byte_pos: u32, file_span: Span, src: &'a str, + decorations: Option<Decorations>, } impl<'a> Classifier<'a> { /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code /// file span which will be used later on by the `span_correspondance_map`. - fn new(src: &str, edition: Edition, file_span: Span) -> Classifier<'_> { + fn new( + src: &str, + edition: Edition, + file_span: Span, + decoration_info: Option<DecorationInfo>, + ) -> Classifier<'_> { let tokens = PeekIter::new(TokenIter { src }); + let decorations = decoration_info.map(Decorations::new); Classifier { tokens, in_attribute: false, @@ -275,6 +314,7 @@ impl<'a> Classifier<'a> { byte_pos: 0, file_span, src, + decorations, } } @@ -356,6 +396,19 @@ impl<'a> Classifier<'a> { /// token is used. fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) { loop { + if let Some(decs) = self.decorations.as_mut() { + let byte_pos = self.byte_pos; + let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count(); + for (_, kind) in decs.starts.drain(0..n_starts) { + sink(Highlight::EnterSpan { class: Class::Decoration(kind) }); + } + + let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count(); + for _ in decs.ends.drain(0..n_ends) { + sink(Highlight::ExitSpan); + } + } + if self .tokens .peek() @@ -416,22 +469,37 @@ impl<'a> Classifier<'a> { // Assume that '&' or '*' is the reference or dereference operator // or a reference or pointer type. Unless, of course, it looks like // a logical and or a multiplication operator: `&&` or `* `. - TokenKind::Star => match self.peek() { - Some(TokenKind::Whitespace) => Class::Op, + TokenKind::Star => match self.tokens.peek() { + Some((TokenKind::Whitespace, _)) => Class::Op, + Some((TokenKind::Ident, "mut")) => { + self.next(); + sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) }); + return; + } + Some((TokenKind::Ident, "const")) => { + self.next(); + sink(Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) }); + return; + } _ => Class::RefKeyWord, }, - TokenKind::And => match lookahead { - Some(TokenKind::And) => { + TokenKind::And => match self.tokens.peek() { + Some((TokenKind::And, _)) => { self.next(); sink(Highlight::Token { text: "&&", class: Some(Class::Op) }); return; } - Some(TokenKind::Eq) => { + Some((TokenKind::Eq, _)) => { self.next(); sink(Highlight::Token { text: "&=", class: Some(Class::Op) }); return; } - Some(TokenKind::Whitespace) => Class::Op, + Some((TokenKind::Whitespace, _)) => Class::Op, + Some((TokenKind::Ident, "mut")) => { + self.next(); + sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) }); + return; + } _ => Class::RefKeyWord, }, @@ -657,7 +725,7 @@ fn string<T: Display>( // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338 match href { LinkFromSrc::Local(span) => context - .href_from_span(*span) + .href_from_span(*span, true) .map(|s| format!("{}{}", context_info.root_path, s)), LinkFromSrc::External(def_id) => { format::href_with_root_path(*def_id, context, Some(context_info.root_path)) @@ -665,7 +733,7 @@ fn string<T: Display>( .map(|(url, _, _)| url) } LinkFromSrc::Primitive(prim) => format::href_with_root_path( - PrimitiveType::primitive_locations(context.tcx())[&prim], + PrimitiveType::primitive_locations(context.tcx())[prim], context, Some(context_info.root_path), ) diff --git a/src/librustdoc/html/highlight/fixtures/decorations.html b/src/librustdoc/html/highlight/fixtures/decorations.html new file mode 100644 index 00000000000..45f567880c9 --- /dev/null +++ b/src/librustdoc/html/highlight/fixtures/decorations.html @@ -0,0 +1,2 @@ +<span class="example"><span class="kw">let</span> <span class="ident">x</span> <span class="op">=</span> <span class="number">1</span>;</span> +<span class="kw">let</span> <span class="ident">y</span> <span class="op">=</span> <span class="number">2</span>; \ No newline at end of file diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html index 22e650af7e2..b117a12e39f 100644 --- a/src/librustdoc/html/highlight/fixtures/sample.html +++ b/src/librustdoc/html/highlight/fixtures/sample.html @@ -15,11 +15,11 @@ <span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">target_os</span> <span class="op">=</span> <span class="string">"linux"</span>)]</span> <span class="kw">fn</span> <span class="ident">main</span>() -> () { <span class="kw">let</span> <span class="ident">foo</span> <span class="op">=</span> <span class="bool-val">true</span> <span class="op">&&</span> <span class="bool-val">false</span> <span class="op">|</span><span class="op">|</span> <span class="bool-val">true</span>; - <span class="kw">let</span> <span class="kw">_</span>: <span class="kw-2">*</span><span class="kw">const</span> () <span class="op">=</span> <span class="number">0</span>; + <span class="kw">let</span> <span class="kw">_</span>: <span class="kw-2">*const</span> () <span class="op">=</span> <span class="number">0</span>; <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">&</span><span class="ident">foo</span>; <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="op">&&</span><span class="ident">foo</span>; <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">*</span><span class="ident">foo</span>; - <span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&</span><span class="kw-2">mut</span> <span class="ident">bar</span>); + <span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&mut</span> <span class="ident">bar</span>); <span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op"><</span> <span class="ident">N</span> <span class="op">&&</span> <span class="ident">index</span> <span class="op"><</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>); <span class="ident">::std::env::var</span>(<span class="string">"gateau"</span>).<span class="ident">is_ok</span>(); <span class="attribute">#[<span class="ident">rustfmt::skip</span>]</span> diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index 450bbfea1ea..1fea7e983b4 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -1,6 +1,7 @@ -use super::write_code; +use super::{write_code, DecorationInfo}; use crate::html::format::Buffer; use expect_test::expect_file; +use rustc_data_structures::fx::FxHashMap; use rustc_span::create_default_session_globals_then; use rustc_span::edition::Edition; @@ -22,7 +23,7 @@ fn test_html_highlighting() { let src = include_str!("fixtures/sample.rs"); let html = { let mut out = Buffer::new(); - write_code(&mut out, src, Edition::Edition2018, None); + write_code(&mut out, src, Edition::Edition2018, None, None); format!("{}<pre><code>{}</code></pre>\n", STYLE, out.into_inner()) }; expect_file!["fixtures/sample.html"].assert_eq(&html); @@ -36,7 +37,7 @@ fn test_dos_backline() { println!(\"foo\");\r\n\ }\r\n"; let mut html = Buffer::new(); - write_code(&mut html, src, Edition::Edition2018, None); + write_code(&mut html, src, Edition::Edition2018, None, None); expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner()); }); } @@ -50,7 +51,7 @@ let x = super::b::foo; let y = Self::whatever;"; let mut html = Buffer::new(); - write_code(&mut html, src, Edition::Edition2018, None); + write_code(&mut html, src, Edition::Edition2018, None, None); expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner()); }); } @@ -60,7 +61,21 @@ fn test_union_highlighting() { create_default_session_globals_then(|| { let src = include_str!("fixtures/union.rs"); let mut html = Buffer::new(); - write_code(&mut html, src, Edition::Edition2018, None); + write_code(&mut html, src, Edition::Edition2018, None, None); expect_file!["fixtures/union.html"].assert_eq(&html.into_inner()); }); } + +#[test] +fn test_decorations() { + create_default_session_globals_then(|| { + let src = "let x = 1; +let y = 2;"; + let mut decorations = FxHashMap::default(); + decorations.insert("example", vec![(0, 10)]); + + let mut html = Buffer::new(); + write_code(&mut html, src, Edition::Edition2018, None, Some(DecorationInfo(decorations))); + expect_file!["fixtures/decorations.html"].assert_eq(&html.into_inner()); + }); +} diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 6ed603c96bb..71d7cc1a09d 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -22,6 +22,8 @@ crate struct Layout { /// If false, the `select` element to have search filtering by crates on rendered docs /// won't be generated. crate generate_search_filter: bool, + /// If true, then scrape-examples.js will be included in the output HTML file + crate scrape_examples_extension: bool, } #[derive(Serialize)] @@ -66,10 +68,8 @@ crate fn render<T: Print, S: Print>( let krate_with_trailing_slash = ensure_trailing_slash(&layout.krate).to_string(); let style_files = style_files .iter() - .filter_map(|t| { - if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None } - }) - .filter_map(|t| if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None }) + .filter_map(|t| t.path.file_stem().map(|stem| (stem, t.disabled))) + .filter_map(|t| t.0.to_str().map(|path| (path, t.1))) .map(|t| { format!( r#"<link rel="stylesheet" type="text/css" href="{}.css" {} {}>"#, diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 9f2e282fce1..47772651bf9 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -178,7 +178,7 @@ fn map_line(s: &str) -> Line<'_> { Line::Shown(Cow::Owned(s.replacen("##", "#", 1))) } else if let Some(stripped) = trimmed.strip_prefix("# ") { // # text - Line::Hidden(&stripped) + Line::Hidden(stripped) } else if trimmed == "#" { // We cannot handle '#text' because it could be #[attr]. Line::Hidden("") @@ -258,7 +258,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> { let parse_result = match kind { CodeBlockKind::Fenced(ref lang) => { let parse_result = - LangString::parse_without_check(&lang, self.check_error_codes, false); + LangString::parse_without_check(lang, self.check_error_codes, false); if !parse_result.rust { return Some(Event::Html( format!( @@ -360,6 +360,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> { edition, None, None, + None, ); Some(Event::Html(s.into_inner().into())) } @@ -668,7 +669,7 @@ impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> { loop { match self.inner.next() { Some((Event::FootnoteReference(ref reference), range)) => { - let entry = self.get_entry(&reference); + let entry = self.get_entry(reference); let reference = format!( "<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}</a></sup>", (*entry).1 @@ -765,7 +766,7 @@ crate fn find_testable_code<T: doctest::Tester>( // If there are characters between the preceding line ending and // this code block, `str::lines` will return an additional line, // which we subtract here. - if nb_lines != 0 && !&doc[prev_offset..offset.start].ends_with("\n") { + if nb_lines != 0 && !&doc[prev_offset..offset.start].ends_with('\n') { nb_lines -= 1; } let line = tests.get_line() + nb_lines + 1; @@ -903,7 +904,7 @@ impl LangString { string .split(|c| c == ',' || c == ' ' || c == '\t') .map(str::trim) - .map(|token| if token.chars().next() == Some('.') { &token[1..] } else { token }) + .map(|token| token.strip_prefix('.').unwrap_or(token)) .filter(|token| !token.is_empty()) } @@ -973,7 +974,10 @@ impl LangString { } x if extra.is_some() => { let s = x.to_lowercase(); - match if s == "compile-fail" || s == "compile_fail" || s == "compilefail" { + if let Some((flag, help)) = if s == "compile-fail" + || s == "compile_fail" + || s == "compilefail" + { Some(( "compile_fail", "the code block will either not be tested if not marked as a rust one \ @@ -1006,15 +1010,12 @@ impl LangString { } else { None } { - Some((flag, help)) => { - if let Some(ref extra) = extra { - extra.error_invalid_codeblock_attr( - &format!("unknown attribute `{}`. Did you mean `{}`?", x, flag), - help, - ); - } + if let Some(extra) = extra { + extra.error_invalid_codeblock_attr( + &format!("unknown attribute `{}`. Did you mean `{}`?", x, flag), + help, + ); } - None => {} } seen_other_tags = true; } @@ -1050,13 +1051,10 @@ impl Markdown<'_> { return String::new(); } let mut replacer = |broken_link: BrokenLink<'_>| { - if let Some(link) = - links.iter().find(|link| &*link.original_text == broken_link.reference) - { - Some((link.href.as_str().into(), link.new_text.as_str().into())) - } else { - None - } + links + .iter() + .find(|link| &*link.original_text == broken_link.reference) + .map(|link| (link.href.as_str().into(), link.new_text.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer)); @@ -1134,13 +1132,10 @@ impl MarkdownSummaryLine<'_> { } let mut replacer = |broken_link: BrokenLink<'_>| { - if let Some(link) = - links.iter().find(|link| &*link.original_text == broken_link.reference) - { - Some((link.href.as_str().into(), link.new_text.as_str().into())) - } else { - None - } + links + .iter() + .find(|link| &*link.original_text == broken_link.reference) + .map(|link| (link.href.as_str().into(), link.new_text.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); @@ -1171,13 +1166,10 @@ fn markdown_summary_with_limit( } 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 { - None - } + link_names + .iter() + .find(|link| &*link.original_text == broken_link.reference) + .map(|link| (link.href.as_str().into(), link.new_text.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); @@ -1412,7 +1404,7 @@ crate fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<RustCodeB CodeBlockKind::Indented => { // The ending of the offset goes too far sometime so we reduce it by one in // these cases. - if offset.end > offset.start && md.get(offset.end..=offset.end) == Some(&"\n") { + if offset.end > offset.start && md.get(offset.end..=offset.end) == Some("\n") { ( LangString::default(), offset.start, diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs index 9c05c80d55d..0bbc510f7cb 100644 --- a/src/librustdoc/html/render/cache.rs +++ b/src/librustdoc/html/render/cache.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::Entry; use std::collections::BTreeMap; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -6,9 +7,7 @@ use rustc_span::symbol::Symbol; use serde::ser::{Serialize, SerializeStruct, Serializer}; use crate::clean; -use crate::clean::types::{ - FnDecl, FnRetTy, GenericBound, Generics, GetDefId, Type, WherePredicate, -}; +use crate::clean::types::{FnDecl, FnRetTy, GenericBound, Generics, Type, WherePredicate}; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::markdown::short_markdown_summary; @@ -36,7 +35,7 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< if let Some(&(ref fqp, _)) = cache.paths.get(&did) { let desc = item .doc_value() - .map_or_else(String::new, |s| short_markdown_summary(&s, &item.link_names(&cache))); + .map_or_else(String::new, |s| short_markdown_summary(&s, &item.link_names(cache))); cache.search_index.push(IndexItem { ty: item.type_(), name: item.name.unwrap().to_string(), @@ -44,7 +43,7 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< desc, parent: Some(did), parent_idx: None, - search_type: get_index_search_type(&item, tcx), + search_type: get_index_search_type(item, tcx), aliases: item.attrs.get_doc_aliases(), }); } @@ -53,7 +52,7 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< let crate_doc = krate .module .doc_value() - .map_or_else(String::new, |s| short_markdown_summary(&s, &krate.module.link_names(&cache))); + .map_or_else(String::new, |s| short_markdown_summary(&s, &krate.module.link_names(cache))); let Cache { ref mut search_index, ref paths, .. } = *cache; @@ -72,7 +71,7 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< // Set up alias indexes. for (i, item) in search_index.iter().enumerate() { for alias in &item.aliases[..] { - aliases.entry(alias.to_lowercase()).or_insert(Vec::new()).push(i); + aliases.entry(alias.to_lowercase()).or_insert_with(Vec::new).push(i); } } @@ -82,12 +81,11 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< let mut lastpathid = 0usize; for item in search_index { - item.parent_idx = item.parent.and_then(|defid| { - if defid_to_pathid.contains_key(&defid) { - defid_to_pathid.get(&defid).copied() - } else { + item.parent_idx = item.parent.and_then(|defid| match defid_to_pathid.entry(defid) { + Entry::Occupied(entry) => Some(*entry.get()), + Entry::Vacant(entry) => { let pathid = lastpathid; - defid_to_pathid.insert(defid, pathid); + entry.insert(pathid); lastpathid += 1; if let Some(&(ref fqp, short)) = paths.get(&defid) { @@ -203,12 +201,12 @@ crate fn get_index_search_type<'tcx>( let inputs = all_types .iter() - .map(|(ty, kind)| TypeWithKind::from((get_index_type(&ty), *kind))) + .map(|(ty, kind)| TypeWithKind::from((get_index_type(ty), *kind))) .filter(|a| a.ty.name.is_some()) .collect(); let output = ret_types .iter() - .map(|(ty, kind)| TypeWithKind::from((get_index_type(&ty), *kind))) + .map(|(ty, kind)| TypeWithKind::from((get_index_type(ty), *kind))) .filter(|a| a.ty.name.is_some()) .collect::<Vec<_>>(); let output = if output.is_empty() { None } else { Some(output) }; @@ -278,7 +276,7 @@ crate fn get_real_types<'tcx>( res: &mut FxHashSet<(Type, ItemType)>, ) -> usize { fn insert(res: &mut FxHashSet<(Type, ItemType)>, tcx: TyCtxt<'_>, ty: Type) -> usize { - if let Some(kind) = ty.def_id().map(|did| tcx.def_kind(did).into()) { + if let Some(kind) = ty.def_id_no_primitives().map(|did| tcx.def_kind(did).into()) { res.insert((ty, kind)); 1 } else if ty.is_primitive() { @@ -296,9 +294,11 @@ crate fn get_real_types<'tcx>( } let mut nb_added = 0; - if let &Type::Generic(arg_s) = arg { + if let Type::Generic(arg_s) = *arg { if let Some(where_pred) = generics.where_predicates.iter().find(|g| match g { - WherePredicate::BoundPredicate { ty, .. } => ty.def_id() == arg.def_id(), + WherePredicate::BoundPredicate { ty, .. } => { + ty.def_id_no_primitives() == arg.def_id_no_primitives() + } _ => false, }) { let bounds = where_pred.get_bounds().unwrap_or_else(|| &[]); @@ -365,7 +365,8 @@ crate fn get_all_types<'tcx>( if !args.is_empty() { all_types.extend(args); } else { - if let Some(kind) = arg.type_.def_id().map(|did| tcx.def_kind(did).into()) { + if let Some(kind) = arg.type_.def_id_no_primitives().map(|did| tcx.def_kind(did).into()) + { all_types.insert((arg.type_.clone(), kind)); } } @@ -374,9 +375,11 @@ crate fn get_all_types<'tcx>( let ret_types = match decl.output { FnRetTy::Return(ref return_type) => { let mut ret = FxHashSet::default(); - get_real_types(generics, &return_type, tcx, 0, &mut ret); + get_real_types(generics, return_type, tcx, 0, &mut ret); if ret.is_empty() { - if let Some(kind) = return_type.def_id().map(|did| tcx.def_kind(did).into()) { + if let Some(kind) = + return_type.def_id_no_primitives().map(|did| tcx.def_kind(did).into()) + { ret.insert((return_type.clone(), kind)); } } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 011d3cfcf72..0e29cc85f9e 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -34,6 +34,7 @@ use crate::html::escape::Escape; use crate::html::format::Buffer; use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; use crate::html::{layout, sources}; +use crate::scrape_examples::AllCallLocations; /// Major driving force in all rustdoc rendering. This contains information /// about where in the tree-like hierarchy rendering is occurring and controls @@ -123,6 +124,8 @@ crate struct SharedContext<'tcx> { crate span_correspondance_map: FxHashMap<rustc_span::Span, LinkFromSrc>, /// The [`Cache`] used during rendering. crate cache: Cache, + + crate call_locations: AllCallLocations, } impl SharedContext<'_> { @@ -157,7 +160,7 @@ impl<'tcx> Context<'tcx> { } pub(super) fn sess(&self) -> &'tcx Session { - &self.shared.tcx.sess + self.shared.tcx.sess } pub(super) fn derive_id(&self, id: String) -> String { @@ -185,7 +188,7 @@ impl<'tcx> Context<'tcx> { }; title.push_str(" - Rust"); let tyname = it.type_(); - let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc)); + let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(doc)); let desc = if let Some(desc) = desc { desc } else if it.is_crate() { @@ -291,10 +294,10 @@ impl<'tcx> Context<'tcx> { /// may happen, for example, with externally inlined items where the source /// of their crate documentation isn't known. pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> { - self.href_from_span(item.span(self.tcx())) + self.href_from_span(item.span(self.tcx()), true) } - crate fn href_from_span(&self, span: clean::Span) -> Option<String> { + crate fn href_from_span(&self, span: clean::Span, with_lines: bool) -> Option<String> { if span.is_dummy() { return None; } @@ -341,16 +344,26 @@ impl<'tcx> Context<'tcx> { (&*symbol, &path) }; - let loline = span.lo(self.sess()).line; - let hiline = span.hi(self.sess()).line; - let lines = - if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) }; + let anchor = if with_lines { + let loline = span.lo(self.sess()).line; + let hiline = span.hi(self.sess()).line; + format!( + "#{}", + if loline == hiline { + loline.to_string() + } else { + format!("{}-{}", loline, hiline) + } + ) + } else { + "".to_string() + }; Some(format!( - "{root}src/{krate}/{path}#{lines}", + "{root}src/{krate}/{path}{anchor}", root = Escape(&root), krate = krate, path = path, - lines = lines + anchor = anchor )) } } @@ -388,6 +401,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { generate_redirect_map, show_type_layout, generate_link_to_definition, + call_locations, .. } = options; @@ -412,6 +426,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { krate: krate.name.to_string(), css_file_extension: extension_css, generate_search_filter, + scrape_examples_extension: !call_locations.is_empty(), }; let mut issue_tracker_base_url = None; let mut include_sources = true; @@ -474,6 +489,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { templates, span_correspondance_map: matches, cache, + call_locations, }; // Add the default themes to the `Vec` of stylepaths diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index dc5aec3b084..a1b86c87d3a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -40,24 +40,29 @@ crate use span_map::{collect_spans_and_sources, LinkFromSrc}; use std::collections::VecDeque; use std::default::Default; use std::fmt; +use std::fs; +use std::iter::Peekable; use std::path::PathBuf; use std::str; use std::string::ToString; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; use rustc_hir::Mutability; use rustc_middle::middle::stability; use rustc_middle::ty::TyCtxt; -use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::{ + symbol::{kw, sym, Symbol}, + BytePos, FileName, RealFileName, +}; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; -use crate::clean::{self, GetDefId, ItemId, RenderedLink, SelfTy}; +use crate::clean::{self, ItemId, RenderedLink, SelfTy}; use crate::docfs::PathError; use crate::error::Error; use crate::formats::cache::Cache; @@ -68,7 +73,10 @@ use crate::html::format::{ href, print_abi_with_space, print_constness_with_space, print_default_space, print_generic_bounds, print_where_clause, Buffer, HrefError, PrintWithSpace, }; +use crate::html::highlight; use crate::html::markdown::{HeadingOffset, Markdown, MarkdownHtml, MarkdownSummaryLine}; +use crate::html::sources; +use crate::scrape_examples::CallData; /// A pair of name and its optional document. crate type NameDoc = (String, Option<String>); @@ -118,8 +126,8 @@ impl Serialize for IndexItemFunctionType { // If we couldn't figure out a type, just write `null`. let mut iter = self.inputs.iter(); if match self.output { - Some(ref output) => iter.chain(output.iter()).any(|ref i| i.ty.name.is_none()), - None => iter.any(|ref i| i.ty.name.is_none()), + Some(ref output) => iter.chain(output.iter()).any(|i| i.ty.name.is_none()), + None => iter.any(|i| i.ty.name.is_none()), } { serializer.serialize_none() } else { @@ -585,6 +593,14 @@ fn document_full_inner( render_markdown(w, cx, &s, item.links(cx), heading_offset); } } + + let kind = match &*item.kind { + clean::ItemKind::StrippedItem(box kind) | kind => kind, + }; + + if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind { + render_call_locations(w, cx, item); + } } /// Add extra information about an item such as: @@ -890,7 +906,7 @@ fn render_assoc_item( AssocItemLink::GotoSource(did, provided_methods) => { // We're creating a link from an impl-item to the corresponding // trait-item and need to map the anchored type accordingly. - let ty = if provided_methods.contains(&name) { + let ty = if provided_methods.contains(name) { ItemType::Method } else { ItemType::TyMethod @@ -949,7 +965,7 @@ fn render_assoc_item( name = name, generics = g.print(cx), decl = d.full_print(header_len, indent, header.asyncness, cx), - notable_traits = notable_traits_decl(&d, cx), + notable_traits = notable_traits_decl(d, cx), where_clause = print_where_clause(g, cx, indent, end_newline), ) } @@ -992,7 +1008,7 @@ fn attributes(it: &clean::Item) -> Vec<String> { .iter() .filter_map(|attr| { if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) { - Some(pprust::attribute_to_string(&attr).replace("\n", "").replace(" ", " ")) + Some(pprust::attribute_to_string(attr).replace("\n", "").replace(" ", " ")) } else { None } @@ -1025,7 +1041,7 @@ enum AssocItemLink<'a> { impl<'a> AssocItemLink<'a> { fn anchor(&self, id: &'a str) -> Self { match *self { - AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(&id)), + AssocItemLink::Anchor(_) => AssocItemLink::Anchor(Some(id)), ref other => *other, } } @@ -1104,7 +1120,7 @@ fn render_assoc_items( let (blanket_impl, concrete): (Vec<&&Impl>, _) = concrete.into_iter().partition(|t| t.inner_impl().blanket_impl.is_some()); - let mut impls = Buffer::empty_from(&w); + let mut impls = Buffer::empty_from(w); render_impls(cx, &mut impls, &concrete, containing_item); let impls = impls.into_inner(); if !impls.is_empty() { @@ -1168,8 +1184,8 @@ fn render_deref_methods( debug!("Render deref methods for {:#?}, target {:#?}", impl_.inner_impl().for_, target); let what = AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut }; - if let Some(did) = target.def_id_full(cache) { - if let Some(type_did) = impl_.inner_impl().for_.def_id_full(cache) { + if let Some(did) = target.def_id(cache) { + if let Some(type_did) = impl_.inner_impl().for_.def_id(cache) { // `impl Deref<Target = S> for S` if did == type_did { // Avoid infinite cycles @@ -1215,7 +1231,7 @@ 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(); - if let Some(did) = decl.output.def_id_full(cx.cache()) { + if let Some(did) = decl.output.as_return().and_then(|t| t.def_id(cx.cache())) { if let Some(impls) = cx.cache().impls.get(&did) { for i in impls { let impl_ = i.inner_impl(); @@ -1317,7 +1333,7 @@ fn render_impl( && match render_mode { RenderMode::Normal => true, RenderMode::ForDeref { mut_: deref_mut_ } => { - should_render_item(&item, deref_mut_, cx.tcx()) + should_render_item(item, deref_mut_, cx.tcx()) } }; @@ -1550,7 +1566,7 @@ fn render_impl( &mut impl_items, cx, &t.trait_, - &i.inner_impl(), + i.inner_impl(), &i.impl_item, parent, render_mode, @@ -1811,23 +1827,53 @@ fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String { format!("{}-{}", url, add) } +struct SidebarLink { + name: Symbol, + url: String, +} + +impl fmt::Display for SidebarLink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "<a href=\"#{}\">{}</a>", self.url, self.name) + } +} + +impl PartialEq for SidebarLink { + fn eq(&self, other: &Self) -> bool { + self.url == other.url + } +} + +impl Eq for SidebarLink {} + +impl PartialOrd for SidebarLink { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for SidebarLink { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.url.cmp(&other.url) + } +} + fn get_methods( i: &clean::Impl, for_deref: bool, used_links: &mut FxHashSet<String>, deref_mut: bool, tcx: TyCtxt<'_>, -) -> Vec<String> { +) -> Vec<SidebarLink> { i.items .iter() .filter_map(|item| match item.name { - Some(ref name) if !name.is_empty() && item.is_method() => { + Some(name) if !name.is_empty() && item.is_method() => { if !for_deref || should_render_item(item, deref_mut, tcx) { - Some(format!( - "<a href=\"#{}\">{}</a>", - get_next_url(used_links, format!("method.{}", name)), - name - )) + Some(SidebarLink { + name, + url: get_next_url(used_links, format!("method.{}", name)), + }) } else { None } @@ -1837,6 +1883,22 @@ fn get_methods( .collect::<Vec<_>>() } +fn get_associated_constants( + i: &clean::Impl, + used_links: &mut FxHashSet<String>, +) -> Vec<SidebarLink> { + i.items + .iter() + .filter_map(|item| match item.name { + Some(name) if !name.is_empty() && item.is_associated_const() => Some(SidebarLink { + name, + url: get_next_url(used_links, format!("associatedconstant.{}", name)), + }), + _ => None, + }) + .collect::<Vec<_>>() +} + // The point is to url encode any potential character from a type with genericity. fn small_url_encode(s: String) -> String { let mut st = String::new(); @@ -1881,23 +1943,40 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { { let used_links_bor = &mut used_links; - let mut ret = v + let mut assoc_consts = v + .iter() + .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)) + .collect::<Vec<_>>(); + if !assoc_consts.is_empty() { + // We want links' order to be reproducible so we don't use unstable sort. + assoc_consts.sort(); + + out.push_str( + "<h3 class=\"sidebar-title\">\ + <a href=\"#implementations\">Associated Constants</a>\ + </h3>\ + <div class=\"sidebar-links\">", + ); + for line in assoc_consts { + write!(out, "{}", line); + } + out.push_str("</div>"); + } + let mut methods = v .iter() .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(move |i| { - get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx()) - }) + .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())) .collect::<Vec<_>>(); - if !ret.is_empty() { + if !methods.is_empty() { // We want links' order to be reproducible so we don't use unstable sort. - ret.sort(); + methods.sort(); out.push_str( "<h3 class=\"sidebar-title\"><a href=\"#implementations\">Methods</a></h3>\ <div class=\"sidebar-links\">", ); - for line in ret { - out.push_str(&line); + for line in methods { + write!(out, "{}", line); } out.push_str("</div>"); } @@ -1981,7 +2060,7 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { } } -fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &Vec<Impl>) { +fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[Impl]) { let c = cx.cache(); debug!("found Deref: {:?}", impl_); @@ -1995,8 +2074,8 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &V }) { debug!("found target, real_target: {:?} {:?}", target, real_target); - if let Some(did) = target.def_id_full(c) { - if let Some(type_did) = impl_.inner_impl().for_.def_id_full(c) { + if let Some(did) = target.def_id(c) { + if let Some(type_did) = impl_.inner_impl().for_.def_id(c) { // `impl Deref<Target = S> for S` if did == type_did { // Avoid infinite cycles @@ -2006,7 +2085,7 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &V } let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); let inner_impl = target - .def_id_full(c) + .def_id(c) .or_else(|| { target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned()) }) @@ -2032,7 +2111,7 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &V ret.sort(); out.push_str("<div class=\"sidebar-links\">"); for link in ret { - out.push_str(&link); + write!(out, "{}", link); } out.push_str("</div>"); } @@ -2080,16 +2159,14 @@ fn get_id_for_impl_on_foreign_type( fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { match *item.kind { clean::ItemKind::ImplItem(ref i) => { - if let Some(ref trait_) = i.trait_ { + i.trait_.as_ref().map(|trait_| { // Alternative format produces no URLs, // so this parameter does nothing. - Some(( + ( format!("{:#}", i.for_.print(cx)), get_id_for_impl_on_foreign_type(&i.for_, trait_, cx), - )) - } else { - None - } + ) + }) } _ => None, } @@ -2169,10 +2246,7 @@ fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean let mut res = implementors .iter() .filter(|i| { - i.inner_impl() - .for_ - .def_id_full(cache) - .map_or(false, |d| !cache.paths.contains_key(&d)) + i.inner_impl().for_.def_id(cache).map_or(false, |d| !cache.paths.contains_key(&d)) }) .filter_map(|i| extract_for_impl_name(&i.impl_item, cx)) .collect::<Vec<_>>(); @@ -2264,9 +2338,10 @@ fn sidebar_enum(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, e: &clean: let mut variants = e .variants .iter() - .filter_map(|v| match v.name { - Some(ref name) => Some(format!("<a href=\"#variant.{name}\">{name}</a>", name = name)), - _ => None, + .filter_map(|v| { + v.name + .as_ref() + .map(|name| format!("<a href=\"#variant.{name}\">{name}</a>", name = name)) }) .collect::<Vec<_>>(); if !variants.is_empty() { @@ -2427,3 +2502,221 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> { } out } + +const MAX_FULL_EXAMPLES: usize = 5; +const NUM_VISIBLE_LINES: usize = 10; + +/// Generates the HTML for example call locations generated via the --scrape-examples flag. +fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { + let tcx = cx.tcx(); + let def_id = item.def_id.expect_def_id(); + let key = tcx.def_path_hash(def_id); + let call_locations = match cx.shared.call_locations.get(&key) { + Some(call_locations) => call_locations, + _ => { + return; + } + }; + + // Generate a unique ID so users can link to this section for a given method + let id = cx.id_map.borrow_mut().derive("scraped-examples"); + write!( + w, + "<div class=\"docblock scraped-example-list\">\ + <span></span>\ + <h5 id=\"{id}\" class=\"section-header\">\ + <a href=\"#{id}\">Examples found in repository</a>\ + </h5>", + id = id + ); + + // Generate the HTML for a single example, being the title and code block + let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| -> bool { + let contents = match fs::read_to_string(&path) { + Ok(contents) => contents, + Err(err) => { + let span = item.span(tcx).inner(); + tcx.sess + .span_err(span, &format!("failed to read file {}: {}", path.display(), err)); + return false; + } + }; + + // To reduce file sizes, we only want to embed the source code needed to understand the example, not + // the entire file. So we find the smallest byte range that covers all items enclosing examples. + assert!(!call_data.locations.is_empty()); + let min_loc = + call_data.locations.iter().min_by_key(|loc| loc.enclosing_item.byte_span.0).unwrap(); + let byte_min = min_loc.enclosing_item.byte_span.0; + let line_min = min_loc.enclosing_item.line_span.0; + let max_loc = + call_data.locations.iter().max_by_key(|loc| loc.enclosing_item.byte_span.1).unwrap(); + let byte_max = max_loc.enclosing_item.byte_span.1; + let line_max = max_loc.enclosing_item.line_span.1; + + // The output code is limited to that byte range. + let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)]; + + // The call locations need to be updated to reflect that the size of the program has changed. + // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point. + let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data + .locations + .iter() + .map(|loc| { + let (byte_lo, byte_hi) = loc.call_expr.byte_span; + let (line_lo, line_hi) = loc.call_expr.line_span; + let byte_range = (byte_lo - byte_min, byte_hi - byte_min); + let line_range = (line_lo - line_min, line_hi - line_min); + let (anchor, line_title) = if line_lo == line_hi { + (format!("{}", line_lo + 1), format!("line {}", line_lo + 1)) + } else { + ( + format!("{}-{}", line_lo + 1, line_hi + 1), + format!("lines {}-{}", line_lo + 1, line_hi + 1), + ) + }; + let line_url = format!("{}{}#{}", cx.root_path(), call_data.url, anchor); + + (byte_range, (line_range, line_url, line_title)) + }) + .unzip(); + + let (_, init_url, init_title) = &line_ranges[0]; + let needs_expansion = line_max - line_min > NUM_VISIBLE_LINES; + let locations_encoded = serde_json::to_string(&line_ranges).unwrap(); + + write!( + w, + "<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\ + <div class=\"scraped-example-title\">\ + {name} (<a href=\"{url}\">{title}</a>)\ + </div>\ + <div class=\"code-wrapper\">", + expanded_cls = if needs_expansion { "" } else { "expanded" }, + name = call_data.display_name, + url = init_url, + title = init_title, + // The locations are encoded as a data attribute, so they can be read + // later by the JS for interactions. + locations = Escape(&locations_encoded) + ); + + 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>"#); + } + + // Look for the example file in the source map if it exists, otherwise return a dummy span + let file_span = (|| { + let source_map = tcx.sess.source_map(); + let crate_src = tcx.sess.local_crate_source_file.as_ref()?; + let abs_crate_src = crate_src.canonicalize().ok()?; + let crate_root = abs_crate_src.parent()?.parent()?; + let rel_path = path.strip_prefix(crate_root).ok()?; + let files = source_map.files(); + let file = files.iter().find(|file| match &file.name { + FileName::Real(RealFileName::LocalPath(other_path)) => rel_path == other_path, + _ => false, + })?; + Some(rustc_span::Span::with_root_ctxt( + file.start_pos + BytePos(byte_min), + file.start_pos + BytePos(byte_max), + )) + })() + .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); + + sources::print_src( + w, + contents_subset, + call_data.edition, + file_span, + cx, + &root_path, + Some(highlight::DecorationInfo(decoration_info)), + sources::SourceContext::Embedded { offset: line_min }, + ); + write!(w, "</div></div>"); + + true + }; + + // 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. + let ordered_locations = { + let sort_criterion = |(_, call_data): &(_, &CallData)| { + // 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 + }; + + let mut locs = call_locations.into_iter().collect::<Vec<_>>(); + locs.sort_by_key(sort_criterion); + locs + }; + + let mut it = ordered_locations.into_iter().peekable(); + + // An example may fail to write if its source can't be read for some reason, so this method + // continues iterating until a write suceeds + let write_and_skip_failure = |w: &mut Buffer, it: &mut Peekable<_>| { + while let Some(example) = it.next() { + if write_example(&mut *w, example) { + break; + } + } + }; + + // Write just one example that's visible by default in the method's description. + write_and_skip_failure(w, &mut it); + + // Then add the remaining examples in a hidden section. + if it.peek().is_some() { + write!( + w, + "<details class=\"rustdoc-toggle more-examples-toggle\">\ + <summary class=\"hideme\">\ + <span>More examples</span>\ + </summary>\ + <div class=\"more-scraped-examples\">\ + <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>\ + <div class=\"more-scraped-examples-inner\">" + ); + + // Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could + // make the page arbitrarily huge! + for _ in 0..MAX_FULL_EXAMPLES { + write_and_skip_failure(w, &mut it); + } + + // For the remaining examples, generate a <ul> containing links to the source files. + if it.peek().is_some() { + write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#); + it.for_each(|(_, call_data)| { + write!( + w, + r#"<li><a href="{root}{url}">{name}</a></li>"#, + root = cx.root_path(), + url = call_data.url, + name = call_data.display_name + ); + }); + write!(w, "</ul></div>"); + } + + write!(w, "</div></div></details>"); + } + + write!(w, "</div>"); +} diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 4cfc57ac995..1677b4adf82 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -21,7 +21,7 @@ use super::{ render_impl, render_stability_since_raw, write_srclink, AssocItemLink, Context, ImplRenderingParameters, }; -use crate::clean::{self, GetDefId}; +use crate::clean; use crate::formats::item_type::ItemType; use crate::formats::{AssocItemRender, Impl, RenderMode}; use crate::html::escape::Escape; @@ -34,10 +34,10 @@ use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine}; use serde::Serialize; -const ITEM_TABLE_OPEN: &'static str = "<div class=\"item-table\">"; -const ITEM_TABLE_CLOSE: &'static str = "</div>"; -const ITEM_TABLE_ROW_OPEN: &'static str = "<div class=\"item-row\">"; -const ITEM_TABLE_ROW_CLOSE: &'static str = "</div>"; +const ITEM_TABLE_OPEN: &str = "<div class=\"item-table\">"; +const ITEM_TABLE_CLOSE: &str = "</div>"; +const ITEM_TABLE_ROW_OPEN: &str = "<div class=\"item-row\">"; +const ITEM_TABLE_ROW_CLOSE: &str = "</div>"; // A component in a `use` path, like `string` in std::string::ToString #[derive(Serialize)] @@ -482,24 +482,26 @@ fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean:: + name.as_str().len() + generics_len; - wrap_item(w, "fn", |w| { - render_attributes_in_pre(w, it, ""); - w.reserve(header_len); - write!( - w, - "{vis}{constness}{asyncness}{unsafety}{abi}fn \ - {name}{generics}{decl}{notable_traits}{where_clause}", - vis = vis, - constness = constness, - asyncness = asyncness, - unsafety = unsafety, - abi = abi, - name = name, - generics = f.generics.print(cx), - where_clause = print_where_clause(&f.generics, cx, 0, true), - decl = f.decl.full_print(header_len, 0, f.header.asyncness, cx), - notable_traits = notable_traits_decl(&f.decl, cx), - ); + wrap_into_docblock(w, |w| { + wrap_item(w, "fn", |w| { + render_attributes_in_pre(w, it, ""); + w.reserve(header_len); + write!( + w, + "{vis}{constness}{asyncness}{unsafety}{abi}fn \ + {name}{generics}{decl}{notable_traits}{where_clause}", + vis = vis, + constness = constness, + asyncness = asyncness, + unsafety = unsafety, + abi = abi, + name = name, + generics = f.generics.print(cx), + where_clause = print_where_clause(&f.generics, cx, 0, true), + decl = f.decl.full_print(header_len, 0, f.header.asyncness, cx), + notable_traits = notable_traits_decl(&f.decl, cx), + ); + }); }); document(w, cx, it, None, HeadingOffset::H2) } @@ -740,7 +742,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra } let (local, foreign) = implementors.iter().partition::<Vec<_>, _>(|i| { - i.inner_impl().for_.def_id_full(cache).map_or(true, |d| cache.paths.contains_key(&d)) + i.inner_impl().for_.def_id(cache).map_or(true, |d| cache.paths.contains_key(&d)) }); let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) = @@ -759,7 +761,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra render_impl( w, cx, - &implementor, + implementor, it, assoc_link, RenderMode::Normal, @@ -844,16 +846,18 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra } fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TraitAlias) { - wrap_item(w, "trait-alias", |w| { - render_attributes_in_pre(w, it, ""); - write!( - w, - "trait {}{}{} = {};", - it.name.as_ref().unwrap(), - t.generics.print(cx), - print_where_clause(&t.generics, cx, 0, true), - bounds(&t.bounds, true, cx) - ); + wrap_into_docblock(w, |w| { + wrap_item(w, "trait-alias", |w| { + render_attributes_in_pre(w, it, ""); + write!( + w, + "trait {}{}{} = {};", + it.name.as_ref().unwrap(), + t.generics.print(cx), + print_where_clause(&t.generics, cx, 0, true), + bounds(&t.bounds, true, cx) + ); + }); }); document(w, cx, it, None, HeadingOffset::H2); @@ -866,16 +870,18 @@ fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clea } fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) { - wrap_item(w, "opaque", |w| { - render_attributes_in_pre(w, it, ""); - write!( - w, - "type {}{}{where_clause} = impl {bounds};", - it.name.as_ref().unwrap(), - t.generics.print(cx), - where_clause = print_where_clause(&t.generics, cx, 0, true), - bounds = bounds(&t.bounds, false, cx), - ); + wrap_into_docblock(w, |w| { + wrap_item(w, "opaque", |w| { + render_attributes_in_pre(w, it, ""); + write!( + w, + "type {}{}{where_clause} = impl {bounds};", + it.name.as_ref().unwrap(), + t.generics.print(cx), + where_clause = print_where_clause(&t.generics, cx, 0, true), + bounds = bounds(&t.bounds, false, cx), + ); + }); }); document(w, cx, it, None, HeadingOffset::H2); @@ -894,20 +900,37 @@ fn item_typedef( t: &clean::Typedef, is_associated: bool, ) { - wrap_item(w, "typedef", |w| { - render_attributes_in_pre(w, it, ""); - if !is_associated { - write!(w, "{}", it.visibility.print_with_space(it.def_id, cx)); - } - write!( - w, - "type {}{}{where_clause} = {type_};", - it.name.as_ref().unwrap(), - t.generics.print(cx), - where_clause = print_where_clause(&t.generics, cx, 0, true), - type_ = t.type_.print(cx), - ); - }); + fn write_content( + w: &mut Buffer, + cx: &Context<'_>, + it: &clean::Item, + t: &clean::Typedef, + is_associated: bool, + ) { + wrap_item(w, "typedef", |w| { + render_attributes_in_pre(w, it, ""); + if !is_associated { + write!(w, "{}", it.visibility.print_with_space(it.def_id, cx)); + } + write!( + w, + "type {}{}{where_clause} = {type_};", + it.name.as_ref().unwrap(), + t.generics.print(cx), + where_clause = print_where_clause(&t.generics, cx, 0, true), + type_ = t.type_.print(cx), + ); + }); + } + + // If this is an associated typedef, we don't want to wrap it into a docblock. + if is_associated { + write_content(w, cx, it, t, is_associated); + } else { + wrap_into_docblock(w, |w| { + write_content(w, cx, it, t, is_associated); + }); + } document(w, cx, it, None, HeadingOffset::H2); @@ -1136,38 +1159,41 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac it.span(cx.tcx()).inner().edition(), None, None, + None, ); }); document(w, cx, it, None, HeadingOffset::H2) } fn item_proc_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, m: &clean::ProcMacro) { - let name = it.name.as_ref().expect("proc-macros always have names"); - match m.kind { - MacroKind::Bang => { - wrap_item(w, "macro", |w| { - write!(w, "{}!() {{ /* proc-macro */ }}", name); - }); - } - MacroKind::Attr => { - wrap_item(w, "attr", |w| { - write!(w, "#[{}]", name); - }); - } - MacroKind::Derive => { - wrap_item(w, "derive", |w| { - write!(w, "#[derive({})]", name); - if !m.helpers.is_empty() { - w.push_str("\n{\n"); - w.push_str(" // Attributes available to this derive:\n"); - for attr in &m.helpers { - writeln!(w, " #[{}]", attr); + wrap_into_docblock(w, |w| { + let name = it.name.as_ref().expect("proc-macros always have names"); + match m.kind { + MacroKind::Bang => { + wrap_item(w, "macro", |w| { + write!(w, "{}!() {{ /* proc-macro */ }}", name); + }); + } + MacroKind::Attr => { + wrap_item(w, "attr", |w| { + write!(w, "#[{}]", name); + }); + } + MacroKind::Derive => { + wrap_item(w, "derive", |w| { + write!(w, "#[derive({})]", name); + if !m.helpers.is_empty() { + w.push_str("\n{\n"); + w.push_str(" // Attributes available to this derive:\n"); + for attr in &m.helpers { + writeln!(w, " #[{}]", attr); + } + w.push_str("}\n"); } - w.push_str("}\n"); - } - }); + }); + } } - } + }); document(w, cx, it, None, HeadingOffset::H2) } @@ -1177,38 +1203,40 @@ fn item_primitive(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { } fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean::Constant) { - wrap_item(w, "const", |w| { - render_attributes_in_code(w, it); + wrap_into_docblock(w, |w| { + wrap_item(w, "const", |w| { + render_attributes_in_code(w, it); - write!( - w, - "{vis}const {name}: {typ}", - vis = it.visibility.print_with_space(it.def_id, cx), - name = it.name.as_ref().unwrap(), - typ = c.type_.print(cx), - ); + write!( + w, + "{vis}const {name}: {typ}", + vis = it.visibility.print_with_space(it.def_id, cx), + name = it.name.as_ref().unwrap(), + typ = c.type_.print(cx), + ); - let value = c.value(cx.tcx()); - let is_literal = c.is_literal(cx.tcx()); - let expr = c.expr(cx.tcx()); - if value.is_some() || is_literal { - write!(w, " = {expr};", expr = Escape(&expr)); - } else { - w.write_str(";"); - } + let value = c.value(cx.tcx()); + let is_literal = c.is_literal(cx.tcx()); + let expr = c.expr(cx.tcx()); + if value.is_some() || is_literal { + write!(w, " = {expr};", expr = Escape(&expr)); + } else { + w.write_str(";"); + } - if !is_literal { - if let Some(value) = &value { - let value_lowercase = value.to_lowercase(); - let expr_lowercase = expr.to_lowercase(); + if !is_literal { + if let Some(value) = &value { + let value_lowercase = value.to_lowercase(); + let expr_lowercase = expr.to_lowercase(); - if value_lowercase != expr_lowercase - && value_lowercase.trim_end_matches("i32") != expr_lowercase - { - write!(w, " // {value}", value = Escape(value)); + if value_lowercase != expr_lowercase + && value_lowercase.trim_end_matches("i32") != expr_lowercase + { + write!(w, " // {value}", value = Escape(value)); + } } } - } + }); }); document(w, cx, it, None, HeadingOffset::H2) @@ -1268,30 +1296,34 @@ fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::St } fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Static) { - wrap_item(w, "static", |w| { - render_attributes_in_code(w, it); - write!( - w, - "{vis}static {mutability}{name}: {typ}", - vis = it.visibility.print_with_space(it.def_id, cx), - mutability = s.mutability.print_with_space(), - name = it.name.as_ref().unwrap(), - typ = s.type_.print(cx) - ); + wrap_into_docblock(w, |w| { + wrap_item(w, "static", |w| { + render_attributes_in_code(w, it); + write!( + w, + "{vis}static {mutability}{name}: {typ}", + vis = it.visibility.print_with_space(it.def_id, cx), + mutability = s.mutability.print_with_space(), + name = it.name.as_ref().unwrap(), + typ = s.type_.print(cx) + ); + }); }); document(w, cx, it, None, HeadingOffset::H2) } fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { - wrap_item(w, "foreigntype", |w| { - w.write_str("extern {\n"); - render_attributes_in_code(w, it); - write!( - w, - " {}type {};\n}}", - it.visibility.print_with_space(it.def_id, cx), - it.name.as_ref().unwrap(), - ); + wrap_into_docblock(w, |w| { + wrap_item(w, "foreigntype", |w| { + w.write_str("extern {\n"); + render_attributes_in_code(w, it); + write!( + w, + " {}type {};\n}}", + it.visibility.print_with_space(it.def_id, cx), + it.name.as_ref().unwrap(), + ); + }); }); document(w, cx, it, None, HeadingOffset::H2); @@ -1374,7 +1406,7 @@ fn wrap_into_docblock<F>(w: &mut Buffer, f: F) where F: FnOnce(&mut Buffer), { - w.write_str("<div class=\"docblock type-decl\">"); + w.write_str("<div class=\"docblock item-decl\">"); f(w); w.write_str("</div>") } @@ -1465,7 +1497,7 @@ fn render_union( ); if let Some(g) = g { write!(w, "{}", g.print(cx)); - write!(w, "{}", print_where_clause(&g, cx, 0, true)); + write!(w, "{}", print_where_clause(g, cx, 0, true)); } write!(w, " {{\n{}", tab); diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index d517f3ac0e3..1a8562d05ea 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -105,7 +105,7 @@ impl Visitor<'tcx> for SpanMapVisitor<'tcx> { } for bound in p.bounds { if let Some(trait_ref) = bound.trait_ref() { - self.handle_path(&trait_ref.path, None); + self.handle_path(trait_ref.path, None); } } } @@ -121,42 +121,33 @@ impl Visitor<'tcx> for SpanMapVisitor<'tcx> { if !span.overlaps(m.inner) { // Now that we confirmed it's a file import, we want to get the span for the module // name only and not all the "mod foo;". - if let Some(node) = self.tcx.hir().find(id) { - match node { - Node::Item(item) => { - self.matches - .insert(item.ident.span, LinkFromSrc::Local(clean::Span::new(m.inner))); - } - _ => {} - } + if let Some(Node::Item(item)) = self.tcx.hir().find(id) { + self.matches.insert(item.ident.span, LinkFromSrc::Local(clean::Span::new(m.inner))); } } intravisit::walk_mod(self, m, id); } fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) { - match expr.kind { - ExprKind::MethodCall(segment, method_span, _, _) => { - if let Some(hir_id) = segment.hir_id { - let hir = self.tcx.hir(); - let body_id = hir.enclosing_body_owner(hir_id); - let typeck_results = self.tcx.sess.with_disabled_diagnostic(|| { - self.tcx.typeck_body( - hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"), - ) - }); - if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { - self.matches.insert( - method_span, - match hir.span_if_local(def_id) { - Some(span) => LinkFromSrc::Local(clean::Span::new(span)), - None => LinkFromSrc::External(def_id), - }, - ); - } + if let ExprKind::MethodCall(segment, method_span, _, _) = expr.kind { + if let Some(hir_id) = segment.hir_id { + let hir = self.tcx.hir(); + let body_id = hir.enclosing_body_owner(hir_id); + let typeck_results = self.tcx.sess.with_disabled_diagnostic(|| { + self.tcx.typeck_body( + hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"), + ) + }); + if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { + self.matches.insert( + method_span, + match hir.span_if_local(def_id) { + Some(span) => LinkFromSrc::Local(clean::Span::new(span)), + None => LinkFromSrc::External(def_id), + }, + ); } } - _ => {} } intravisit::walk_expr(self, expr); } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index c1a83ad5820..34f1b4cd684 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -39,8 +39,9 @@ static FILES_UNVERSIONED: Lazy<FxHashMap<&str, &[u8]>> = Lazy::new(|| { "SourceCodePro-Semibold.ttf.woff" => static_files::source_code_pro::SEMIBOLD, "SourceCodePro-It.ttf.woff" => static_files::source_code_pro::ITALIC, "SourceCodePro-LICENSE.txt" => static_files::source_code_pro::LICENSE, - "noto-sans-kr-v13-korean-regular.woff" => static_files::noto_sans_kr::REGULAR, - "noto-sans-kr-v13-korean-regular-LICENSE.txt" => static_files::noto_sans_kr::LICENSE, + "NanumBarunGothic.ttf.woff2" => static_files::nanum_barun_gothic::REGULAR2, + "NanumBarunGothic.ttf.woff" => 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, @@ -127,7 +128,7 @@ impl Context<'_> { ) -> Result<(), Error> { if minify { let contents = contents.as_ref(); - let contents = if resource.extension() == Some(&OsStr::new("css")) { + 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)) })? @@ -303,6 +304,15 @@ pub(super) fn write_shared( )?; } + 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, + )?; + } + 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. diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 71c64231a21..667bbc24ba5 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -67,7 +67,7 @@ impl LocalSourcesCollector<'_, '_> { } let mut href = String::new(); - clean_path(&self.src_root, &p, false, |component| { + clean_path(self.src_root, &p, false, |component| { href.push_str(&component.to_string_lossy()); href.push('/'); }); @@ -168,7 +168,7 @@ impl SourceCollector<'_, 'tcx> { }; // Remove the utf-8 BOM if any - let contents = if contents.starts_with('\u{feff}') { &contents[3..] } else { &contents }; + let contents = contents.strip_prefix('\u{feff}').unwrap_or(&contents); // Create the intermediate directories let mut cur = self.dst.clone(); @@ -204,7 +204,16 @@ impl SourceCollector<'_, 'tcx> { &page, "", |buf: &mut _| { - print_src(buf, contents, self.cx.shared.edition(), file_span, &self.cx, &root_path) + print_src( + buf, + contents, + self.cx.shared.edition(), + file_span, + self.cx, + &root_path, + None, + SourceContext::Standalone, + ) }, &self.cx.shared.style_files, ); @@ -241,15 +250,22 @@ where } } +crate enum SourceContext { + Standalone, + Embedded { offset: usize }, +} + /// Wrapper struct to render the source code of a file. This will do things like /// adding line numbers to the left-hand side. -fn print_src( +crate fn print_src( buf: &mut Buffer, s: &str, edition: Edition, file_span: rustc_span::Span, context: &Context<'_>, root_path: &str, + decoration_info: Option<highlight::DecorationInfo>, + source_context: SourceContext, ) { let lines = s.lines().count(); let mut line_numbers = Buffer::empty_from(buf); @@ -261,7 +277,14 @@ fn print_src( } line_numbers.write_str("<pre class=\"line-numbers\">"); for i in 1..=lines { - writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols); + match source_context { + SourceContext::Standalone => { + writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols) + } + SourceContext::Embedded { offset } => { + writeln!(line_numbers, "<span>{0:1$}</span>", i + offset, cols) + } + } } line_numbers.write_str("</pre>"); highlight::render_with_highlighting( @@ -273,5 +296,6 @@ fn print_src( edition, Some(line_numbers), Some(highlight::ContextInfo { context, file_span, root_path }), + decoration_info, ); } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 962af66368d..b98dae85ba0 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -75,12 +75,13 @@ font-display: swap; } -/* Avoid using legacy CJK serif fonts in Windows like Batang */ +/* Avoid using legacy CJK serif fonts in Windows like Batang. */ @font-face { - font-family: 'Noto Sans KR'; - src: url("noto-sans-kr-v13-korean-regular.woff") format("woff"); + font-family: 'NanumBarunGothic'; + src: url("NanumBarunGothic.ttf.woff2") format("woff2"), + url("NanumBarunGothic.ttf.woff") format("woff"); font-display: swap; - unicode-range: U+A960-A97F, U+AC00-D7AF, U+D7B0-D7FF; + unicode-range: U+AC00-D7AF, U+1100-11FF, U+3130-318F, U+A960-A97F, U+D7B0-D7FF; } * { @@ -107,7 +108,7 @@ html { /* General structure and fonts */ body { - font: 16px/1.4 "Source Serif 4", "Noto Sans KR", serif; + font: 16px/1.4 "Source Serif 4", NanumBarunGothic, serif; margin: 0; position: relative; padding: 10px 15px 20px 15px; @@ -128,9 +129,14 @@ h3 { } h1, h2, h3, h4, h5, h6 { font-weight: 500; +} +h1, h2, h3, h4 { margin: 20px 0 15px 0; padding-bottom: 6px; } +h5, h6 { + margin: 15px 0 5px 0; +} h1.fqn { display: flex; border-bottom: 1px dashed; @@ -146,10 +152,15 @@ h1.fqn > .in-band > a:hover { h2, h3, h4 { border-bottom: 1px solid; } -h3.code-header, h4.code-header { +h3.code-header { + font-size: 1.1em; +} +h4.code-header { font-size: 1em; +} +h3.code-header, h4.code-header { font-weight: 600; - border: none; + border-bottom-style: none; padding: 0; margin: 0; } @@ -168,12 +179,6 @@ h3.code-header, h4.code-header { margin-bottom: 10px; position: relative; } -.impl, .method.trait-impl, -.type.trait-impl, -.associatedconstant.trait-impl, -.associatedtype.trait-impl { - padding-left: 15px; -} div.impl-items > div { padding-left: 0; @@ -191,7 +196,7 @@ div.impl-items > div:not(.docblock):not(.item-info), .content ul.crate a.crate, a.srclink, /* This selector is for the items listed in the "all items" page. */ #main > ul.docblock > li > a { - font-family: "Fira Sans", Arial, sans-serif; + font-family: "Fira Sans", Arial, NanumBarunGothic, sans-serif; } .content ul.crate a.crate { @@ -253,7 +258,10 @@ code, pre, a.test-arrow, .code-header { pre { padding: 14px; } -.type-decl pre { +.docblock.item-decl { + margin-left: 0; +} +.item-decl pre { overflow-x: auto; } @@ -458,6 +466,11 @@ nav.sub { overflow-x: auto; } +.rustdoc:not(.source) .example-wrap > pre.line-numbers { + width: auto; + overflow-x: visible; +} + .rustdoc .example-wrap > pre { margin: 0; } @@ -501,14 +514,12 @@ nav.sub { white-space: pre-wrap; } -.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5, .docblock h6 { - border-bottom: 1px solid; -} - .top-doc .docblock h2 { font-size: 1.3em; } .top-doc .docblock h3 { font-size: 1.15em; } .top-doc .docblock h4, -.top-doc .docblock h5, +.top-doc .docblock h5 { + font-size: 1.1em; +} .top-doc .docblock h6 { font-size: 1em; } @@ -521,7 +532,7 @@ nav.sub { position: relative; } -.docblock > * { +.docblock > :not(.information) { max-width: 100%; overflow-x: auto; } @@ -549,6 +560,7 @@ nav.sub { flex-grow: 1; margin: 0px; padding: 0px; + overflow-wrap: anywhere; } .in-band > code, .in-band > .code-header { @@ -661,13 +673,6 @@ nav.sub { left: -19px; } -.content .impl-items .method, .content .impl-items > .type, .impl-items > .associatedconstant, -.impl-items > .associatedtype, .content .impl-items details > summary > .type, -.impl-items details > summary > .associatedconstant, -.impl-items details > summary > .associatedtype { - margin-left: 20px; -} - .content .impl-items .docblock, .content .impl-items .item-info { margin-bottom: .6em; } @@ -738,7 +743,7 @@ a { .anchor { display: none; position: absolute; - left: 0; + left: -0.5em; background: none !important; } .anchor.field { @@ -1571,14 +1576,14 @@ details.rustdoc-toggle > summary.hideme::before { details.rustdoc-toggle > summary:not(.hideme)::before { position: absolute; - left: -23px; + left: -24px; top: 3px; } .impl-items > details.rustdoc-toggle > summary:not(.hideme)::before, .undocumented > details.rustdoc-toggle > summary:not(.hideme)::before { position: absolute; - left: -2px; + left: -24px; } /* When a "hideme" summary is open and the "Expand description" or "Show @@ -1972,3 +1977,166 @@ details.undocumented[open] > summary::before { overflow-wrap: anywhere; } } + + +/* Begin: styles for --scrape-examples feature */ + +.scraped-example-title { + font-family: 'Fira Sans'; +} + +.scraped-example:not(.expanded) .code-wrapper pre.line-numbers { + overflow: hidden; + max-height: 240px; +} + +.scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust { + overflow-y: hidden; + max-height: 240px; + padding-bottom: 0; +} + +.scraped-example .code-wrapper .prev { + position: absolute; + top: 0.25em; + 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 .code-wrapper { + position: relative; + display: flex; + flex-direction: row; + flex-wrap: wrap; + width: 100%; +} + +.scraped-example:not(.expanded) .code-wrapper:before { + content: " "; + width: 100%; + height: 5px; + position: absolute; + z-index: 100; + top: 0; + background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); +} + +.scraped-example:not(.expanded) .code-wrapper:after { + content: " "; + width: 100%; + height: 5px; + position: absolute; + z-index: 100; + bottom: 0; + background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); +} + +.scraped-example:not(.expanded) .code-wrapper { + overflow: hidden; + max-height: 240px; +} + +.scraped-example .code-wrapper .line-numbers { + margin: 0; + padding: 14px 0; +} + +.scraped-example .code-wrapper .line-numbers span { + padding: 0 14px; +} + +.scraped-example .code-wrapper .example-wrap { + flex: 1; + overflow-x: auto; + overflow-y: hidden; + margin-bottom: 0; +} + +.scraped-example .code-wrapper .example-wrap pre.rust { + overflow-x: inherit; + width: inherit; + overflow-y: hidden; +} + +.scraped-example .example-wrap .rust span.highlight { + background: #fcffd6; +} + +.scraped-example .example-wrap .rust span.highlight.focus { + background: #f6fdb0; +} + +.more-examples-toggle { + margin-top: 10px; +} + +.more-examples-toggle summary { + color: #999; + font-family: 'Fira Sans'; +} + +.more-scraped-examples { + margin-left: 25px; + display: flex; + flex-direction: row; + width: calc(100% - 25px); +} + +.more-scraped-examples-inner { + /* 20px is width of toggle-line + toggle-line-inner */ + width: calc(100% - 20px); +} + +.toggle-line { + align-self: stretch; + margin-right: 10px; + margin-top: 5px; + padding: 0 4px; + cursor: pointer; +} + +.toggle-line:hover .toggle-line-inner { + background: #aaa; +} + +.toggle-line-inner { + min-width: 2px; + background: #ddd; + height: 100%; +} + +.more-scraped-examples .scraped-example { + margin-bottom: 20px; +} + +.more-scraped-examples .scraped-example:last-child { + margin-bottom: 0; +} + +.example-links a { + margin-top: 20px; + font-family: 'Fira Sans'; +} + +.example-links ul { + margin-bottom: 0; +} + +/* End: styles for --scrape-examples feature */ diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 0fd6462a8f5..f9c84dc3e31 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -220,7 +220,7 @@ body.source .example-wrap pre.rust a { background: #333; } -.docblock:not(.type-decl) a:not(.srclink):not(.test-arrow), +.docblock:not(.item-decl) a:not(.srclink):not(.test-arrow), .docblock-short a:not(.srclink):not(.test-arrow), .item-info a, #help a { color: #39AFD7; @@ -613,3 +613,22 @@ div.files > .selected { input:checked + .slider { background-color: #ffb454 !important; } + +.scraped-example .example-wrap .rust span.highlight { + background: rgb(91, 59, 1); +} +.scraped-example .example-wrap .rust span.highlight.focus { + background: rgb(124, 75, 15); +} +.scraped-example:not(.expanded) .code-wrapper:before { + background: linear-gradient(to bottom, rgba(15, 20, 25, 1), rgba(15, 20, 25, 0)); +} +.scraped-example:not(.expanded) .code-wrapper:after { + background: linear-gradient(to top, rgba(15, 20, 25, 1), rgba(15, 20, 25, 0)); +} +.toggle-line-inner { + background: #616161; +} +.toggle-line:hover .toggle-line-inner { + background: ##898989; +} diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index d863701dd73..9a38277d559 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -181,7 +181,7 @@ body.source .example-wrap pre.rust a { background: #333; } -.docblock:not(.type-decl) a:not(.srclink):not(.test-arrow), +.docblock:not(.item-decl) a:not(.srclink):not(.test-arrow), .docblock-short a:not(.srclink):not(.test-arrow), .item-info a, #help a { color: #D2991D; @@ -485,3 +485,22 @@ div.files > .selected { .setting-line > .title { border-bottom-color: #ddd; } + +.scraped-example .example-wrap .rust span.highlight { + background: rgb(91, 59, 1); +} +.scraped-example .example-wrap .rust span.highlight.focus { + background: rgb(124, 75, 15); +} +.scraped-example:not(.expanded) .code-wrapper:before { + background: linear-gradient(to bottom, rgba(53, 53, 53, 1), rgba(53, 53, 53, 0)); +} +.scraped-example:not(.expanded) .code-wrapper:after { + background: linear-gradient(to top, rgba(53, 53, 53, 1), rgba(53, 53, 53, 0)); +} +.toggle-line-inner { + background: #616161; +} +.toggle-line:hover .toggle-line-inner { + background: ##898989; +} diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index 28d2e99a3d0..fba8231caac 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -176,7 +176,7 @@ body.source .example-wrap pre.rust a { background: #eee; } -.docblock:not(.type-decl) a:not(.srclink):not(.test-arrow), +.docblock:not(.item-decl) a:not(.srclink):not(.test-arrow), .docblock-short a:not(.srclink):not(.test-arrow), .item-info a, #help a { color: #3873AD; diff --git a/src/librustdoc/html/static/fonts/noto-sans-kr-v13-korean-regular-LICENSE.txt b/src/librustdoc/html/static/fonts/NanumBarunGothic-LICENSE.txt index 922d5fdc18d..0bf46682b5b 100644 --- a/src/librustdoc/html/static/fonts/noto-sans-kr-v13-korean-regular-LICENSE.txt +++ b/src/librustdoc/html/static/fonts/NanumBarunGothic-LICENSE.txt @@ -1,8 +1,14 @@ -Copyright 2014, 2015 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. +Copyright (c) 2010, NAVER Corporation (https://www.navercorp.com/), -This Font Software is licensed under the SIL Open Font License, Version 1.1. +with Reserved Font Name Nanum, Naver Nanum, NanumGothic, Naver NanumGothic, +NanumMyeongjo, Naver NanumMyeongjo, NanumBrush, Naver NanumBrush, NanumPen, +Naver NanumPen, Naver NanumGothicEco, NanumGothicEco, Naver NanumMyeongjoEco, +NanumMyeongjoEco, Naver NanumGothicLight, NanumGothicLight, NanumBarunGothic, +Naver NanumBarunGothic, NanumSquareRound, NanumBarunPen, MaruBuri -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL ----------------------------------------------------------- diff --git a/src/librustdoc/html/static/fonts/NanumBarunGothic.ttf.woff b/src/librustdoc/html/static/fonts/NanumBarunGothic.ttf.woff new file mode 100644 index 00000000000..fb063e8fb7d --- /dev/null +++ b/src/librustdoc/html/static/fonts/NanumBarunGothic.ttf.woff Binary files differdiff --git a/src/librustdoc/html/static/fonts/NanumBarunGothic.ttf.woff2 b/src/librustdoc/html/static/fonts/NanumBarunGothic.ttf.woff2 new file mode 100644 index 00000000000..1866ad4bcea --- /dev/null +++ b/src/librustdoc/html/static/fonts/NanumBarunGothic.ttf.woff2 Binary files differdiff --git a/src/librustdoc/html/static/fonts/noto-sans-kr-v13-korean-regular.woff b/src/librustdoc/html/static/fonts/noto-sans-kr-v13-korean-regular.woff deleted file mode 100644 index 01d6b6b5466..00000000000 --- a/src/librustdoc/html/static/fonts/noto-sans-kr-v13-korean-regular.woff +++ /dev/null Binary files differdiff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js new file mode 100644 index 00000000000..664b187e33e --- /dev/null +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -0,0 +1,86 @@ +/* global addClass, hasClass, removeClass, onEach */ + +(function () { + // Scroll code block to put the given code location in the middle of the viewer + function scrollToLoc(elt, loc) { + var wrapper = elt.querySelector(".code-wrapper"); + var halfHeight = wrapper.offsetHeight / 2; + var lines = elt.querySelector('.line-numbers'); + var offsetMid = (lines.children[loc[0]].offsetTop + + lines.children[loc[1]].offsetTop) / 2; + var scrollOffset = offsetMid - halfHeight; + lines.scrollTo(0, scrollOffset); + elt.querySelector(".rust").scrollTo(0, scrollOffset); + } + + function updateScrapedExample(example) { + var locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); + var locIndex = 0; + var highlights = example.querySelectorAll('.highlight'); + var link = example.querySelector('.scraped-example-title a'); + + if (locs.length > 1) { + // Toggle through list of examples in a given file + var onChangeLoc = function(changeIndex) { + removeClass(highlights[locIndex], 'focus'); + changeIndex(); + scrollToLoc(example, locs[locIndex][0]); + addClass(highlights[locIndex], 'focus'); + + var url = locs[locIndex][1]; + var title = locs[locIndex][2]; + + link.href = url; + link.innerHTML = title; + }; + + example.querySelector('.prev') + .addEventListener('click', function() { + onChangeLoc(function() { + locIndex = (locIndex - 1 + locs.length) % locs.length; + }); + }); + + example.querySelector('.next') + .addEventListener('click', function() { + onChangeLoc(function() { + locIndex = (locIndex + 1) % locs.length; + }); + }); + } + + var expandButton = example.querySelector('.expand'); + if (expandButton) { + expandButton.addEventListener('click', function () { + if (hasClass(example, "expanded")) { + removeClass(example, "expanded"); + scrollToLoc(example, locs[0][0]); + } else { + addClass(example, "expanded"); + } + }); + } + + // Start with the first example in view + scrollToLoc(example, locs[0][0]); + } + + var firstExamples = document.querySelectorAll('.scraped-example-list > .scraped-example'); + onEach(firstExamples, updateScrapedExample); + onEach(document.querySelectorAll('.more-examples-toggle'), function(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. + toggle.querySelector('.toggle-line').addEventListener('click', function() { + toggle.open = false; + }); + + var moreExamples = toggle.querySelectorAll('.scraped-example'); + toggle.querySelector('summary').addEventListener('click', function() { + // Wrapping in setTimeout ensures the update happens after the elements are actually + // visible. This is necessary since updateScrapedExample calls scrollToLoc which + // depends on offsetHeight, a property that requires an element to be visible to + // compute correctly. + setTimeout(function() { onEach(moreExamples, updateScrapedExample); }); + }, {once: true}); + }); +})(); diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index ccc25e6cc49..56c5399d074 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -35,6 +35,10 @@ crate static SETTINGS_JS: &str = include_str!("static/js/settings.js"); /// Storage, used to store documentation settings. 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. +crate static SCRAPE_EXAMPLES_JS: &str = include_str!("static/js/scrape-examples.js"); + /// The file contents of `brush.svg`, the icon used for the theme-switch button. crate static BRUSH_SVG: &[u8] = include_bytes!("static/images/brush.svg"); @@ -156,16 +160,35 @@ crate mod source_code_pro { crate static LICENSE: &[u8] = include_bytes!("static/fonts/SourceCodePro-LICENSE.txt"); } -crate mod noto_sans_kr { - /// The file `noto-sans-kr-v13-korean-regular.woff`, the Regular variant of the Noto Sans KR - /// font. - crate static REGULAR: &[u8] = - include_bytes!("static/fonts/noto-sans-kr-v13-korean-regular.woff"); - - /// The file `noto-sans-kr-v13-korean-regular-LICENSE.txt`, the license text of the Noto Sans KR - /// font. - crate static LICENSE: &[u8] = - include_bytes!("static/fonts/noto-sans-kr-v13-korean-regular-LICENSE.txt"); +/// 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.woff --flavor=woff +/// ``` +/// ```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 +/// ``` +crate mod nanum_barun_gothic { + /// The file `NanumBarunGothic.ttf.woff`, the Regular variant of the Nanum Barun Gothic font. + crate static REGULAR: &[u8] = include_bytes!("static/fonts/NanumBarunGothic.ttf.woff"); + + /// The file `NanumBarunGothic.ttf.woff2`, the Regular variant of the Nanum Barun Gothic font. + crate static REGULAR2: &[u8] = include_bytes!("static/fonts/NanumBarunGothic.ttf.woff2"); + + /// The file `NanumBarunGothic-LICENSE.txt`, the license text of the Nanum Barun Gothic font. + crate static LICENSE: &[u8] = include_bytes!("static/fonts/NanumBarunGothic-LICENSE.txt"); } /// Files related to the sidebar in rustdoc sources. diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index 38dc3b30e72..b0174d59a7b 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -109,6 +109,9 @@ data-search-js="{{static_root_path | safe}}search{{page.resource_suffix}}.js"> {#- -#} </div> <script src="{{static_root_path | safe}}main{{page.resource_suffix}}.js"></script> {#- -#} + {%- if layout.scrape_examples_extension -%} + <script src="{{static_root_path | safe}}scrape-examples{{page.resource_suffix}}.js"></script> {#- -#} + {%- endif -%} {%- for script in page.static_extra_scripts -%} <script src="{{static_root_path | safe}}{{script}}.js"></script> {#- -#} {% endfor %} diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 4098f17db81..443ac282134 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -330,10 +330,10 @@ impl FromWithTcx<clean::GenericParamDefKind> for GenericParamDefKind { }, Type { did: _, bounds, default, synthetic: _ } => GenericParamDefKind::Type { bounds: bounds.into_iter().map(|x| x.into_tcx(tcx)).collect(), - default: default.map(|x| x.into_tcx(tcx)), + default: default.map(|x| (*x).into_tcx(tcx)), }, Const { did: _, ty, default } => { - GenericParamDefKind::Const { ty: ty.into_tcx(tcx), default } + GenericParamDefKind::Const { ty: (*ty).into_tcx(tcx), default: default.map(|x| *x) } } } } @@ -412,7 +412,7 @@ impl FromWithTcx<clean::Type> for Type { .map(|t| { clean::GenericBound::TraitBound(t, rustc_hir::TraitBoundModifier::None) }) - .chain(lt.into_iter().map(|lt| clean::GenericBound::Outlives(lt))) + .chain(lt.map(clean::GenericBound::Outlives)) .map(|bound| bound.into_tcx(tcx)) .collect(), } diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 637e5f2288d..0031e3915fa 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -255,7 +255,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { ) }) .collect(), - format_version: 9, + format_version: types::FORMAT_VERSION, }; let mut p = self.out_path.clone(); p.push(output.index.get(&output.root).unwrap().name.clone().unwrap()); diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index ff0a6ef6cb7..93dffc27659 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -48,11 +48,13 @@ extern crate rustc_interface; extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_lint_defs; +extern crate rustc_macros; extern crate rustc_metadata; extern crate rustc_middle; extern crate rustc_parse; extern crate rustc_passes; extern crate rustc_resolve; +extern crate rustc_serialize; extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; @@ -120,6 +122,7 @@ mod json; crate mod lint; mod markdown; mod passes; +mod scrape_examples; mod theme; mod visit_ast; mod visit_lib; @@ -619,6 +622,30 @@ fn opts() -> Vec<RustcOptGroup> { "Make the identifiers in the HTML source code pages navigable", ) }), + unstable("scrape-examples-output-path", |o| { + o.optopt( + "", + "scrape-examples-output-path", + "", + "collect function call information and output at the given path", + ) + }), + unstable("scrape-examples-target-crate", |o| { + o.optmulti( + "", + "scrape-examples-target-crate", + "", + "collect function call information for functions from the target crate", + ) + }), + unstable("with-examples", |o| { + o.optmulti( + "", + "with-examples", + "", + "path to function call information (for displaying examples in the documentation)", + ) + }), ] } @@ -732,6 +759,7 @@ fn main_options(options: config::Options) -> MainResult { // FIXME: fix this clone (especially render_options) let manual_passes = options.manual_passes.clone(); let render_options = options.render_options.clone(); + let scrape_examples_options = options.scrape_examples_options.clone(); let config = core::create_config(options); interface::create_compiler_and_run(config, |compiler| { @@ -747,7 +775,7 @@ fn main_options(options: config::Options) -> MainResult { // We need to hold on to the complete resolver, so we cause everything to be // cloned for the analysis passes to use. Suboptimal, but necessary in the // current architecture. - let resolver = core::create_resolver(queries, &sess); + let resolver = core::create_resolver(queries, sess); if sess.has_errors() { sess.fatal("Compilation failed, aborting rustdoc"); @@ -768,6 +796,10 @@ fn main_options(options: config::Options) -> MainResult { }); info!("finished with rustc"); + if let Some(options) = scrape_examples_options { + return scrape_examples::run(krate, render_opts, cache, tcx, options); + } + cache.crate_version = crate_version; if show_coverage { diff --git a/src/librustdoc/passes/bare_urls.rs b/src/librustdoc/passes/bare_urls.rs index 37faa674292..4501914fe0c 100644 --- a/src/librustdoc/passes/bare_urls.rs +++ b/src/librustdoc/passes/bare_urls.rs @@ -39,7 +39,7 @@ impl<'a, 'tcx> BareUrlsLinter<'a, 'tcx> { ) { 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) { + for match_ in URL_REGEX.find_iter(text) { let url = match_.as_str(); let url_range = match_.range(); f( diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs index d2b3c5239c7..b18208d26e2 100644 --- a/src/librustdoc/passes/check_code_block_syntax.rs +++ b/src/librustdoc/passes/check_code_block_syntax.rs @@ -36,7 +36,7 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { 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(self.cx.tcx.sess.edition()); + 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, @@ -77,7 +77,7 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { // The span and whether it is precise or not. let (sp, precise_span) = match super::source_span_for_markdown_range( self.cx.tcx, - &dox, + dox, &code_block.range, &item.attrs, ) { @@ -123,7 +123,7 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> { // FIXME(#67563): Provide more context for these errors by displaying the spans inline. for message in buffer.messages.iter() { - diag.note(&message); + diag.note(message); } diag.emit(); @@ -150,8 +150,8 @@ impl<'a, 'tcx> DocFolder for SyntaxChecker<'a, 'tcx> { item.def_id.expect_def_id(), sp, ); - for code_block in markdown::rust_code_blocks(&dox, &extra) { - self.check_rust_syntax(&item, &dox, code_block); + for code_block in markdown::rust_code_blocks(dox, &extra) { + self.check_rust_syntax(&item, dox, code_block); } } diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 1f7d6054233..69a526d4618 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -115,10 +115,10 @@ crate fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) { let mut tests = Tests { found_tests: 0 }; - find_testable_code(&dox, &mut tests, ErrorCodes::No, false, None); + find_testable_code(dox, &mut tests, ErrorCodes::No, false, None); if tests.found_tests == 0 && cx.tcx.sess.is_nightly_build() { - if should_have_doc_example(cx, &item) { + if should_have_doc_example(cx, item) { debug!("reporting error for {:?} (hir_id={:?})", item, hir_id); let sp = item.attr_span(cx.tcx); cx.tcx.struct_span_lint_hir( diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 318c897bcbd..9b2fe0c77e6 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -289,7 +289,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { ) -> Result<(Res, Option<String>), ErrorKind<'path>> { let tcx = self.cx.tcx; let no_res = || ResolutionFailure::NotResolved { - module_id: module_id, + module_id, partial_res: None, unresolved: path_str.into(), }; @@ -437,7 +437,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { fn resolve_path(&self, path_str: &str, ns: Namespace, module_id: DefId) -> Option<Res> { let result = self.cx.enter_resolver(|resolver| { resolver - .resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id) + .resolve_str_path_error(DUMMY_SP, path_str, ns, module_id) .and_then(|(_, res)| res.try_into()) }); debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns); @@ -543,7 +543,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { ty::Uint(uty) => Res::Primitive(uty.into()), ty::Float(fty) => Res::Primitive(fty.into()), ty::Str => Res::Primitive(Str), - ty::Tuple(ref tys) if tys.is_empty() => Res::Primitive(Unit), + ty::Tuple(tys) if tys.is_empty() => Res::Primitive(Unit), ty::Tuple(_) => Res::Primitive(Tuple), ty::Array(..) => Res::Primitive(Array), ty::Slice(_) => Res::Primitive(Slice), @@ -978,13 +978,13 @@ fn preprocess_link<'a>( } // Parse and strip the disambiguator from the link, if present. - let (disambiguator, path_str, link_text) = match Disambiguator::from_str(&link) { + let (disambiguator, path_str, link_text) = match Disambiguator::from_str(link) { Ok(Some((d, path, link_text))) => (Some(d), path.trim(), link_text.trim()), Ok(None) => (None, link.trim(), link.trim()), Err((err_msg, relative_range)) => { // Only report error if we would not have ignored this link. See issue #83859. if !should_ignore_link_with_disambiguators(link) { - let no_backticks_range = range_between_backticks(&ori_link); + let no_backticks_range = range_between_backticks(ori_link); let disambiguator_range = (no_backticks_range.start + relative_range.start) ..(no_backticks_range.start + relative_range.end); return Some(Err(PreprocessingError::Disambiguator(disambiguator_range, err_msg))); @@ -1000,7 +1000,7 @@ fn preprocess_link<'a>( // Strip generics from the path. let path_str = if path_str.contains(['<', '>'].as_slice()) { - match strip_generics_from_path(&path_str) { + match strip_generics_from_path(path_str) { Ok(path) => path, Err(err_kind) => { debug!("link has malformed generics: {}", path_str); @@ -1228,7 +1228,7 @@ impl LinkCollector<'_, '_> { if self.cx.tcx.privacy_access_levels(()).is_exported(src_id) && !self.cx.tcx.privacy_access_levels(()).is_exported(dst_id) { - privacy_error(self.cx, &diag_info, &path_str); + privacy_error(self.cx, &diag_info, path_str); } } @@ -1766,8 +1766,8 @@ fn report_diagnostic( let span = super::source_span_for_markdown_range(tcx, dox, link_range, &item.attrs).map(|sp| { - if dox.bytes().nth(link_range.start) == Some(b'`') - && dox.bytes().nth(link_range.end - 1) == Some(b'`') + if dox.as_bytes().get(link_range.start) == Some(&b'`') + && dox.as_bytes().get(link_range.end - 1) == Some(&b'`') { sp.with_lo(sp.lo() + BytePos(1)).with_hi(sp.hi() - BytePos(1)) } else { @@ -1868,8 +1868,7 @@ fn resolution_failure( }; name = start; for ns in [TypeNS, ValueNS, MacroNS] { - if let Some(res) = - collector.check_full_res(ns, &start, module_id, &None) + if let Some(res) = collector.check_full_res(ns, start, module_id, &None) { debug!("found partial_res={:?}", res); *partial_res = Some(res); diff --git a/src/librustdoc/passes/collect_intra_doc_links/early.rs b/src/librustdoc/passes/collect_intra_doc_links/early.rs index cd90528ab9c..565bcb8bd13 100644 --- a/src/librustdoc/passes/collect_intra_doc_links/early.rs +++ b/src/librustdoc/passes/collect_intra_doc_links/early.rs @@ -34,7 +34,7 @@ impl IntraLinkCrateLoader { let attrs = crate::clean::Attributes::from_ast(attrs, None); for (parent_module, doc) in attrs.collapsed_doc_value_by_module_level() { debug!(?doc); - for link in markdown_links(&doc.as_str()) { + for link in markdown_links(doc.as_str()) { debug!(?link.link); let path_str = if let Some(Ok(x)) = preprocess_link(&link) { x.path_str @@ -46,7 +46,7 @@ impl IntraLinkCrateLoader { span, &path_str, TypeNS, - parent_module.unwrap_or(self.current_mod.to_def_id()), + parent_module.unwrap_or_else(|| self.current_mod.to_def_id()), ); }); } diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 319dd7b42b0..91a0cb413eb 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -70,7 +70,7 @@ crate fn collect_trait_impls(krate: Crate, cx: &mut DocContext<'_>) -> Crate { if let Some(prim) = target.primitive_type() { cleaner.prims.insert(prim); - } else if let Some(did) = target.def_id() { + } else if let Some(did) = target.def_id(&cx.cache) { cleaner.items.insert(did.into()); } } @@ -187,7 +187,7 @@ impl BadImplStripper { true } else if let Some(prim) = ty.primitive_type() { self.prims.contains(&prim) - } else if let Some(did) = ty.def_id() { + } else if let Some(did) = ty.def_id_no_primitives() { self.keep_impl_with_def_id(did.into()) } else { false diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 8b1fd662f85..74a9a2da06d 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -2,7 +2,7 @@ use rustc_hir::def_id::DefId; use rustc_middle::middle::privacy::AccessLevels; use std::mem; -use crate::clean::{self, GetDefId, Item, ItemIdSet}; +use crate::clean::{self, Item, ItemIdSet}; use crate::fold::{strip_item, DocFolder}; crate struct Stripper<'a> { @@ -127,7 +127,7 @@ impl<'a> DocFolder for ImplStripper<'a> { if imp.trait_.is_none() && imp.items.is_empty() { return None; } - if let Some(did) = imp.for_.def_id() { + if let Some(did) = imp.for_.def_id_no_primitives() { if did.is_local() && !imp.for_.is_assoc_ty() && !self.retained.contains(&did.into()) { debug!("ImplStripper: impl item for stripped type; removing"); @@ -142,7 +142,7 @@ impl<'a> DocFolder for ImplStripper<'a> { } if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) { for typaram in generics { - if let Some(did) = typaram.def_id() { + if let Some(did) = typaram.def_id_no_primitives() { if did.is_local() && !self.retained.contains(&did.into()) { debug!( "ImplStripper: stripped item in trait's generics; removing impl" diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs new file mode 100644 index 00000000000..fc54e55b876 --- /dev/null +++ b/src/librustdoc/scrape_examples.rs @@ -0,0 +1,266 @@ +//! This module analyzes crates to find call sites that can serve as examples in the documentation. + +use crate::clean; +use crate::config; +use crate::formats; +use crate::formats::renderer::FormatRenderer; +use crate::html::render::Context; + +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{ + self as hir, + intravisit::{self, Visitor}, + HirId, +}; +use rustc_interface::interface; +use rustc_macros::{Decodable, Encodable}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_serialize::{ + opaque::{Decoder, FileEncoder}, + Decodable, Encodable, +}; +use rustc_session::getopts; +use rustc_span::{ + def_id::{CrateNum, DefPathHash, LOCAL_CRATE}, + edition::Edition, + BytePos, FileName, SourceFile, +}; + +use std::fs; +use std::path::PathBuf; + +#[derive(Debug, Clone)] +crate struct ScrapeExamplesOptions { + output_path: PathBuf, + target_crates: Vec<String>, +} + +impl ScrapeExamplesOptions { + crate fn new( + matches: &getopts::Matches, + diag: &rustc_errors::Handler, + ) -> Result<Option<Self>, i32> { + let output_path = matches.opt_str("scrape-examples-output-path"); + let target_crates = matches.opt_strs("scrape-examples-target-crate"); + match (output_path, !target_crates.is_empty()) { + (Some(output_path), true) => Ok(Some(ScrapeExamplesOptions { + output_path: PathBuf::from(output_path), + target_crates, + })), + (Some(_), false) | (None, true) => { + diag.err(&format!("must use --scrape-examples-output-path and --scrape-examples-target-crate together")); + Err(1) + } + (None, false) => Ok(None), + } + } +} + +#[derive(Encodable, Decodable, Debug, Clone)] +crate struct SyntaxRange { + crate byte_span: (u32, u32), + crate line_span: (usize, usize), +} + +impl SyntaxRange { + fn new(span: rustc_span::Span, file: &SourceFile) -> Self { + let get_pos = |bytepos: BytePos| file.original_relative_byte_pos(bytepos).0; + let get_line = |bytepos: BytePos| file.lookup_line(bytepos).unwrap(); + + SyntaxRange { + byte_span: (get_pos(span.lo()), get_pos(span.hi())), + line_span: (get_line(span.lo()), get_line(span.hi())), + } + } +} + +#[derive(Encodable, Decodable, Debug, Clone)] +crate struct CallLocation { + crate call_expr: SyntaxRange, + crate enclosing_item: SyntaxRange, +} + +impl CallLocation { + fn new( + tcx: TyCtxt<'_>, + expr_span: rustc_span::Span, + expr_id: HirId, + source_file: &SourceFile, + ) -> Self { + let enclosing_item_span = + tcx.hir().span_with_body(tcx.hir().get_parent_item(expr_id)).source_callsite(); + assert!(enclosing_item_span.contains(expr_span)); + + CallLocation { + call_expr: SyntaxRange::new(expr_span, source_file), + enclosing_item: SyntaxRange::new(enclosing_item_span, source_file), + } + } +} + +#[derive(Encodable, Decodable, Debug, Clone)] +crate struct CallData { + crate locations: Vec<CallLocation>, + crate url: String, + crate display_name: String, + crate edition: Edition, +} + +crate type FnCallLocations = FxHashMap<PathBuf, CallData>; +crate type AllCallLocations = FxHashMap<DefPathHash, FnCallLocations>; + +/// Visitor for traversing a crate and finding instances of function calls. +struct FindCalls<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + map: Map<'tcx>, + cx: Context<'tcx>, + target_crates: Vec<CrateNum>, + calls: &'a mut AllCallLocations, +} + +impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx> +where + 'tcx: 'a, +{ + type Map = Map<'tcx>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { + intravisit::NestedVisitorMap::OnlyBodies(self.map) + } + + fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { + intravisit::walk_expr(self, ex); + + // Get type of function if expression is a function call + let tcx = self.tcx; + let (ty, span) = match ex.kind { + hir::ExprKind::Call(f, _) => { + let types = tcx.typeck(ex.hir_id.owner); + (types.node_type(f.hir_id), ex.span) + } + hir::ExprKind::MethodCall(_, _, _, span) => { + let types = tcx.typeck(ex.hir_id.owner); + let def_id = types.type_dependent_def_id(ex.hir_id).unwrap(); + (tcx.type_of(def_id), span) + } + _ => { + return; + } + }; + + // If this span comes from a macro expansion, then the source code may not actually show + // a use of the given item, so it would be a poor example. Hence, we skip all uses in macros. + if span.from_expansion() { + return; + } + + // Save call site if the function resolves to a concrete definition + if let ty::FnDef(def_id, _) = ty.kind() { + // Ignore functions not from the crate being documented + if self.target_crates.iter().all(|krate| *krate != def_id.krate) { + return; + } + + let file = tcx.sess.source_map().lookup_char_pos(span.lo()).file; + let file_path = match file.name.clone() { + FileName::Real(real_filename) => real_filename.into_local_path(), + _ => None, + }; + + if let Some(file_path) = file_path { + let abs_path = fs::canonicalize(file_path.clone()).unwrap(); + let cx = &self.cx; + let mk_call_data = || { + let clean_span = crate::clean::types::Span::new(span); + let url = cx.href_from_span(clean_span, false).unwrap(); + let display_name = file_path.display().to_string(); + let edition = span.edition(); + CallData { locations: Vec::new(), url, display_name, edition } + }; + + let fn_key = tcx.def_path_hash(*def_id); + let fn_entries = self.calls.entry(fn_key).or_default(); + + let location = CallLocation::new(tcx, span, ex.hir_id, &file); + fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location); + } + } + } +} + +crate fn run( + krate: clean::Crate, + renderopts: config::RenderOptions, + cache: formats::cache::Cache, + tcx: TyCtxt<'_>, + options: ScrapeExamplesOptions, +) -> interface::Result<()> { + let inner = move || -> Result<(), String> { + // Generates source files for examples + let (cx, _) = Context::init(krate, renderopts, cache, tcx).map_err(|e| e.to_string())?; + + // Collect CrateIds corresponding to provided target crates + // If two different versions of the crate in the dependency tree, then examples will be collcted from both. + let all_crates = tcx + .crates(()) + .iter() + .chain([&LOCAL_CRATE]) + .map(|crate_num| (crate_num, tcx.crate_name(*crate_num))) + .collect::<Vec<_>>(); + let target_crates = options + .target_crates + .into_iter() + .map(|target| all_crates.iter().filter(move |(_, name)| name.as_str() == target)) + .flatten() + .map(|(crate_num, _)| **crate_num) + .collect::<Vec<_>>(); + + debug!("All crates in TyCtxt: {:?}", all_crates); + debug!("Scrape examples target_crates: {:?}", target_crates); + + // 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 }; + tcx.hir().visit_all_item_likes(&mut finder.as_deep_visitor()); + + // Save output to provided path + let mut encoder = FileEncoder::new(options.output_path).map_err(|e| e.to_string())?; + calls.encode(&mut encoder).map_err(|e| e.to_string())?; + encoder.flush().map_err(|e| e.to_string())?; + + Ok(()) + }; + + if let Err(e) = inner() { + tcx.sess.fatal(&e); + } + + Ok(()) +} + +// Note: the Handler must be passed in explicitly because sess isn't available while parsing options +crate fn load_call_locations( + with_examples: Vec<String>, + diag: &rustc_errors::Handler, +) -> Result<AllCallLocations, i32> { + let inner = || { + let mut all_calls: AllCallLocations = FxHashMap::default(); + for path in with_examples { + let bytes = fs::read(&path).map_err(|e| format!("{} (for path {})", e, path))?; + let mut decoder = Decoder::new(&bytes, 0); + let calls = AllCallLocations::decode(&mut decoder)?; + + for (function, fn_calls) in calls.into_iter() { + all_calls.entry(function).or_default().extend(fn_calls.into_iter()); + } + } + + Ok(all_calls) + }; + + inner().map_err(|e: String| { + diag.err(&format!("failed to load examples: {}", e)); + 1 + }) +} diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 36b1a14f6c1..5d1f934240f 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -9,7 +9,6 @@ use rustc_hir::Node; use rustc_hir::CRATE_HIR_ID; use rustc_middle::middle::privacy::AccessLevel; use rustc_middle::ty::TyCtxt; -use rustc_span; use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Symbol}; @@ -87,13 +86,21 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // the rexport defines the path that a user will actually see. Accordingly, // we add the rexport as an item here, and then skip over the original // definition in `visit_item()` below. + // + // We also skip `#[macro_export] macro_rules!` that have already been inserted, + // it can happen if within the same module a `#[macro_export] macro_rules!` + // is declared but also a reexport of itself producing two exports of the same + // macro in the same module. + let mut inserted = FxHashSet::default(); for export in self.cx.tcx.module_exports(CRATE_DEF_ID).unwrap_or(&[]) { if let Res::Def(DefKind::Macro(_), def_id) = export.res { if let Some(local_def_id) = def_id.as_local() { if self.cx.tcx.has_attr(def_id, sym::macro_export) { - let hir_id = self.cx.tcx.hir().local_def_id_to_hir_id(local_def_id); - let item = self.cx.tcx.hir().expect_item(hir_id); - top_level_module.items.push((item, None)); + if inserted.insert(def_id) { + let hir_id = self.cx.tcx.hir().local_def_id_to_hir_id(local_def_id); + let item = self.cx.tcx.hir().expect_item(hir_id); + top_level_module.items.push((item, None)); + } } } } @@ -113,11 +120,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { .unwrap_or(&[]) .iter() .filter_map(|attr| { - Some( - Cfg::parse(attr.meta_item()?) - .map_err(|e| self.cx.sess().diagnostic().span_err(e.span, e.msg)) - .ok()?, - ) + Cfg::parse(attr.meta_item()?) + .map_err(|e| self.cx.sess().diagnostic().span_err(e.span, e.msg)) + .ok() }) .collect::<Vec<_>>() }) @@ -271,7 +276,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { _ if self.inlining && !is_pub => {} hir::ItemKind::GlobalAsm(..) => {} hir::ItemKind::Use(_, hir::UseKind::ListStem) => {} - hir::ItemKind::Use(ref path, kind) => { + hir::ItemKind::Use(path, kind) => { let is_glob = kind == hir::UseKind::Glob; // Struct and variant constructors and proc macro stubs always show up alongside |
